Apache Shiro是一个Java安全框架,执行身份验证、授权、密码和会话管理。
三个核心组件:Subject, SecurityManager 和 Realms.
- Subject:即“当前操作用户”。主体,代表了当前“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫,机器人等;即一个抽象概念;所有Subject都绑定到SecurityManager,与Subject的所有交互都会委托给SecurityManager;可以把Subject认为是一个门面;SecurityManager才是实际的执行者;。
- SecurityManager:安全管理器;它是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。
- Realm:域, Shiro从从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。Realm实质上是一个安全相关的DAO:它封装了数据源的连接细节,并在需要时将相关数据提供给Shiro。当配置Shiro时,你必须至少指定一个Realm,用于认证和(或)授权。配置多个Realm是可以的,但是至少需要一个。Shiro内置了可以连接大量安全数据源(又名目录)的Realm,如LDAP、关系数据库(JDBC)、类似INI的文本配置资源以及属性文件等。如果缺省的Realm不能满足需求,你还可以插入代表自定义数据源的自己的Realm实现。
Shiro不会去维护用户、维护权限;这些需要我们自己去设计/提供;然后通过相应的接口注入给Shiro即可。
(1)使用用户的登录信息创建令牌
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
token可以理解为用户令牌,登录的过程被抽象为Shiro验证令牌是否具有合法身份以及相关权限。
(2)执行登陆动作
SecurityUtils.setSecurityManager(securityManager); // 注入SecurityManager
Subject subject = SecurityUtils.getSubject(); // 获取Subject单例对象
subject.login(token); // 登陆
Shiro的核心部分是SecurityManager,它负责安全认证与授权。Shiro本身已经实现了所有的细节,用户可以完全把它当做一个黑盒来使用。SecurityUtils对象,本质上就是一个工厂类似Spring中的ApplicationContext。Subject是初学者比较难于理解的对象,很多人以为它可以等同于User,其实不然。Subject中文翻译:项目,而正确的理解也恰恰如此。它是你目前所设计的需要通过Shiro保护的项目的一个抽象概念。通过令牌(token)与项目(subject)的登陆(login)关系,Shiro保证了项目整体的安全。
(3)判断用户
Shiro本身无法知道所持有令牌的用户是否合法,因为除了项目的设计人员恐怕谁都无法得知。因此Realm是整个框架中为数不多的必须由设计者自行实现的模块,当然Shiro提供了多种实现的途径,本文只介绍最常见也最重要的一种实现方式——数据库查询。AuthenticationInfo代表了用户的角色信息集合,AuthorizationInfo代表了角色的权限信息集合。
Shiro通过一系列filter来控制访问权限,内部为我们预先定义了多个过滤器,我们可以直接通过字符串配置这些过滤器。
authc:所有已登陆用户可访问
roles:有指定角色的用户可访问,通过[ ]指定具体角色,这里的角色名称与数据库中配置一致
perms:有指定权限的用户可访问,通过[ ]指定具体权限,这里的权限名称与数据库中配置一致
anon:所有用户可访问,通常作为指定页面的静态资源时使用、
user:如果使用RememberMe的功能可以直接访问
ShiroConfig:
@Configuration
public class ShiroConfig {
/**
* 创建ShiroFilterFactoryBean
*/
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("defaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//设置安全管理器
shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
//添加Shiro过滤器
Map<String,String> map = new LinkedHashMap<>();
//添加授权信息,为每个请求地址设置访问权限
//perms后括号内的参数设置:为该请求设置一个名称,后期将权限名称保存在数据库中,通过查询用户是否有这些名称来判断用户是否拥有此类权限
map.put("/","anon");
map.put("/index","authc");
map.put("/delDepart","perms[depart:del]");
map.put("/editDepart","perms[depart:edit]");
map.put("/addDepart","perms[depart:add]");
map.put("/delUser","perms[user:del]");
map.put("/editUser","perms[user:edit]");
//设置未授权跳转页面
shiroFilterFactoryBean.setUnauthorizedUrl("/noPerm");
//设置未登录跳转页面
shiroFilterFactoryBean.setLoginUrl("/");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
return shiroFilterFactoryBean;
}
/**
* 创建DefaultWebSecurityManager安全管理器
*/
@Bean(name = "defaultWebSecurityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
//关联Reaml
defaultWebSecurityManager.setRealm(userRealm);
return defaultWebSecurityManager;
}
/**
* 创建Realm
*/
@Bean(name = "userRealm")
public UserRealm getReaml(){
return new UserRealm();
}
}
Realm实现:
//Realm:用于连接数据
public class UserRealm extends AuthorizingRealm {
//用户service
@Autowired
UserService userService;
//角色service
@Autowired
RoleService roleService;
//用户权限service
@Autowired
RolePermService rolePermService;
/**
* 重写执行授权逻辑的方法
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行授权逻辑!");
//给资源进行授权
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//创建一个用户主体,来获取登录的用户信息
Subject subject = SecurityUtils.getSubject();
//user对象是通过楼下认证逻辑方法中SimpleAuthenticationInfo传递的
Users user1 = (Users) subject.getPrincipal();
//通过获取到的用户对象,使用用户对象中的角色id去用户权限表(rolePerm)中或获取该角色的权限
List<String> list = new ArrayList<>();
list = rolePermService.findAllPermCode(user1.getRole_id());
//添加资源的授权字段,此时该用户以及完成被授权的所有操作
info.addStringPermissions(list);
return info;
}
/**
* 重写执行认证逻辑的方法:主要用来做用户登录认证
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行认证逻辑!");
//编写shiro判断逻辑,验证用户名和密码
//判断用户名(通过controller层发送过来的用户token中封装的用户的账号密码)
UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken;
Users user = new Users();
user = userService.findUserByName(token.getUsername());
if (user==null){
return null;//shiro底层会抛出UnknownAccountException
}
//判断密码,第一个参数:传一个user对象给楼上授权逻辑的subject.getPrincipal();第二个参数:获取数据库中的密码,与传来的token中的密码对比;第三个参数:getName() 当前的realm名,可不写
return new SimpleAuthenticationInfo(user,user.getPassword(),"");
}
}
shiro登录(controller):
@RequestMapping("/doLogin")
public String doLogin(String username, String password, HttpServletRequest request){
/**
* Shrio认证
*/
//1、获取Subject
Subject subject = SecurityUtils.getSubject();
//2、封装用户数据
UsernamePasswordToken token = new UsernamePasswordToken(username,password);
//3、执行登录方法
try{
//登录验证,会去找Reaml的doGetAuthenticationInfo进行逻辑认证
subject.login(token);
//验证成功跳转到index
return "redirect:/index";
}catch (UnknownAccountException e){
//UnknownAccountException:用户名找不到异常,return到登录页面
return "redirect:/";
}catch (IncorrectCredentialsException e){
//IncorrectCredentialsException:密码错误异常,return到登录页面
return "redirect:/";
}
}
ps:
-
Authenticator:认证器,负责Subject的认证操作,认证过程就是根据Subject提供的信息通过Realm查询到相关信息,然后做对比,支持扩展
-
Authorizer:授权器,控制着Subject对服务资源的访问权限
-
SessionManager:用于管理Session,这个Session可以是web的也可以不是web的。
-
SessionDao:把Session的 CRUD和存储介质联系起来的工具,存储介质可以是数据库,也可以是缓存,比如把session放到redis里面
-
CacheManager:缓存控制器,Realm管理的数据(用户、角色、权限)可以放到缓存里由CacheManager管理,提高认证授权等的速度
-
Cryptography:加密组件,Shiro提供了很多加解密算法的组件
Tips:
• [urls] 部分的配置,其格式是: “url=拦截器[参数],拦截器[参数]”;
• 如果当前请求的 url 匹配 [urls] 部分的某个 url 模式,将会执行其配置的拦截器。
• anon(anonymous) 拦截器表示匿名访问(即不需要登录即可访问)
• authc (authentication)拦截器表示需要身份认证通过后才能访问
2)shiro中的默认过滤器
3)URL 匹配模式
• url 模式使用 Ant 风格模式
• Ant 路径通配符支持 ?、*、**,注意通配符匹配不包括目录分隔符“/”:
? :匹配一个字符,如 /admin? 将匹配 /admin1,但不匹配 /admin 或 /admin/;
* :匹配零个或多个字符串,如 /admin 将匹配 /admin、/admin123,但不匹配 /admin/1;
** :匹配路径中的零个或多个路径,如 /admin/** 将匹配 /admin/a 或 /admin/a/b
4)URL 匹配顺序
• URL 权限采取第一次匹配优先的 方式,即从头开始使用第一个匹配的 url 模式对应的拦截器链。
• 如:
– /bb/**=filter1
– /bb/aa=filter2
– /**=filter3
– 如果请求的url是“/bb/aa”,因为按照声明顺序进行匹配,那么将使用 filter1 进行拦截。