Shiro
Maven依赖:
<!-- 项目依赖,根据情况二选一 -->
<!-- 普通项目 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
</dependency>
<!-- SpringBoot整合 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
</dependency>
<!-- 缓存依赖,根据情况二选一,第二种是在springboot -->
<!-- shiro的默认缓存ehcache -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
</dependency>
<!-- redis整合springboot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
1.核心架构
核心为Security Manager,几乎所有功能均为其实现。
-
Authenticator:认证。
-
Authorizer:授权。
-
Session Manager:Web环境下,管理shiro会话。
-
Session DAO:对会话数据进行CRUD(对Session Manager中的数据)。
-
Cache Manager:缓存认证、授权的数据。
-
Realms:域。具体做认证、授权的标配
Cryptography:密码生成器,封装好了相应算法,用于对密码加密。SHA256、MD5等。一般采用MD5做哈希散列。
2.认证流程
主体(Subject)将身份信息(Principal)和凭证信息(Credential)传给shiro,shiro将其封装为token,token通过Security Manager进行验证(调用Authenticator,Authenticator调用Realm)。
2.1.基本Demo示例
核心代码:
// 1.创建安全管理器
DefaultSecurityManager securityManager = new DefaultSecurityManager();
// 2.给安全管理器设置Realm
securityManager.setRealm(new IniRealm("classpath:shiro.ini"));
// 3.SecurityUtils 给全局安全工具类设置安全管理器
SecurityUtils.setSecurityManager(securityManager);
// 4.关键对象 subject 主体
Subject subject = SecurityUtils.getSubject();
// 5.创建令牌
UsernamePasswordToken token = new UsernamePasswordToken("xiaochen", "123456");
// 6.用户认证
try {
//System.out.println("用户认证前状态:" + subject.isAuthenticated());
subject.login(token);
//System.out.println("用户认证后状态:" + subject.isAuthenticated());
} catch (Exception e) {
e.printStackTrace();
}
ps:login认证失败时,若为无当前账户会报UnknownAccountException,否则密码错误会报IncorrectCredentialsException。
3.2.主要源码解析
最终执行用户名比较是SimpleAccountRealm的doGetAuthenticationInfo方法,完成用户名校验。最终密码校验是在AuthenticatingRealm中的assertCredentialsMatch方法(SimpleCredentialsMatcher的doCredentialsMatch方法,拿到token和account的credentials后默认equal比较,后面需要修改为密码比较方法)。(重写Realm,需要我们实现doGetAuthenticationInfo方法,而密码不需要我们实现。)
AuthenticatingRealm中doGetAuthenticationInfo方法认证realm,AuthorizingRealm中doGetAuthorizationInfo方法授权realm。
3.3.自定义realm
实现AuthorizingRealm抽象类doGetAuthenticationInfo方法和doGetAuthorizationInfo方法。
-
doGetAuthenticationInfo方法实现
可返回SimpleAuthenticationInfo或者SimpleAccount。
返回null,login时报UnknownAccountException;返回空参SimpleAuthenticationInfo,login时报AuthenticationException。
举例实现:
/**
* 认证
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
-
密码加密
AuthenticatingRealm的credentialsMatcher属性为比较方法,默认equals,可以通过realm的setCredentialsMatcher方法进行设置,可设置以下凭证匹配器:
以下,实现对密码进行MD5+salt+hash散列存储(salt默认拼接在原字符串后面;hash散列默认1次,一般设置为1024或者2048次):
-
修改凭证匹配器
// 新建hash凭证匹配器
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
// 设置算法
hashedCredentialsMatcher.setHashAlgorithmName("md5");
// 设置realm使用hash凭证匹配器
realm.setCredentialsMatcher(hashedCredentialsMatcher);
-
添加随机盐salt
添加随机盐(随机盐保存至数据库,以下举例耦合死)需要在自定义realm当中,对返回的AuthenticationInfo设置credentialsSalt,即在第三个参数添加盐值,添加方法举例如下:
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(principal,
"c15be9a15a0a238084e0c5a846f3a7b4",
ByteSource.Util.bytes("x0*7ps"), // 盐值
this.getName());
-
添加散列次数
调用hash凭证匹配器的setHashIterations方法进行设置。
hashedCredentialsMatcher.setHashIterations(1024);
ps:shiro集成了获得md5加密后的值的算法,如下:
// 参数1加密明文,参数2盐,参数3散列次数
Md5Hash md5Hash = new Md5Hash("123", "x0*7ps", 1024);
// toHex 转为String
System.out.println(md5Hash.toHex());
3.授权流程
在完成shiro鉴权之后,就可进行授权了。
-
授权的关键对象:
-
主体(Subject)
-
资源(Resource):包括资源类型(一类资源)和资源实例(一类资源中的某一个)。
-
权限/许可(Permission)
-
授权方式:
-
RBAC基于角色的访问控制(Role-Based Access Control)是以角色为中心进行访问控制
if (subject.hasRole("admin")) {
// 操作什么资源
}
-
RBAC基于资源的访问控制(Resource-Based Access Control)是以资源为中心进行访问控制
if (subject.isPermission("user:update:01")) { // 资源实例
// 对01用户进行修改
}
if (subject.isPermission("user:update:*")) { // 资源类型
// 对所有用户进行修改
}
ps:权限字符串规则是资源标识符:操作:资源实例标识符
,意思是对哪个资源的那个实例有什么操作。
-
实现方式
-
编程式
if (subject.hasRole("admin")) {
// 有权限
} else {
// 无权限
}
-
注解式
-
标签式
<!- JSP/GSP标签:在JSP/GSP页面通过相应的标签完成 ->
<shiro:hasRole name="admin">
<!- 有权限 ->
</shiro:hasRole>
<!- ps:Thymeleaf中使用shiro需要额外集成!->
3.1.Demo示例
-
realm授权
在realm的doGetAuthorizationInfo方法中,返回AuthorizationInfo。
示例代码:
-
核心代码鉴权
// 认证用户进行授权
if (subject.isAuthenticated()) {
// 1.基于角色权限控制
// 单角色
System.out.println(subject.hasRole("admin"));
// 多角色所有
System.out.println(subject.hasAllRoles(Arrays.asList("admin", "user")));
// 多角色某个
boolean[] booleans = subject.hasRoles(Arrays.asList("admin", "super", "user"));
for (boolean aBoolean : booleans) {
System.out.println(aBoolean);
}
// 2.基于权限字符串的访问控制 资源标识符:操作:资源类型
// 单个
System.out.println(subject.isPermitted("user:update:01"));
System.out.println(subject.isPermitted("product:update"));
// 同时
System.out.println(subject.isPermittedAll("user:*:01", "order:*:10"));
// 分别
boolean[] permitted = subject.isPermitted("user:*:01", "order:*:10");
for (boolean b : permitted) {
System.out.println(b);
}
}
4.SpringBoot整合
4.1.项目权限管理流程
SpringBoot项目关键加入拦截器ShiroFilter。请求发出,ShiroFilter进行拦截,依赖SecurityManager进行认证,对资源进行相应访问。
4.2.Demo示例
首选需要创建自定义Realm,在这里面做我们的认证(登录鉴权)授权规则。随后,设置Shiro配置,主要完成ShiroFilter、SecurityManager、Realm的创建并交给Spring工厂管理。以后的应用就可以设定相应规则来进行鉴权。
具体权限一般保存至数据库之中,一般按照用户<->角色<->权限<->资源进行设计(以下代码示例均通过数据库访问来赋权/鉴权/认证)。
4.2.1.自定义Realm
示例如下:
/**
* 自定义realm
*/
public class CustomerRealm extends AuthorizingRealm {
示例与普通项目类似,但存在两点区别:1.自定义realm并未交给Spring工厂管理,获取bean时自己实现了ApplicationContextUtils.getBean(),详情参见其它。2.原本的shiro自带的用于加盐的ByteSource牵涉到使用Redis缓存时序列化问题,不可用,自己实现了MySimpleByteSource,详情参见其它。
4.2.2.ShiroConfig
示例代码:
/**
* 用来整合shiro框架相关配置类
*/
spring-boot-shiro的ShiroFilter依赖SecurityManager,SecurityManager依赖Realm。
对于Realm,配置为自定义CustomerRealm,与凭证校验匹配器修改普通项目类似,不同的是使用了缓存管理。shiro默认的缓存管理为EhCache,为是本地缓存,即在应用内部缓存,当前应用停止/宕机等,依然会重查数据库,不符合现实需求。所以,这里我使用了Redis(如果依旧使用EhCache只需将customerRealm.setCacheManager(new RedisCacheManager())改为customerRealm.setCacheManager(new EhCacheManager()))。RedisCacheManager为自己实现,需要实现CacheManager接口,其中getCache方法,需要返回Cache,这里也是自己实现RedisCache。RedisCache需要实现Cache接口,其中get方法实现从redis中获取值;put方法实现填入值到redis;remove在用户logout时调用,清理缓存;clear为清理所有缓存……
/**
* 自定义shiro缓存管理器
*/
public class RedisCacheManager implements CacheManager {
/**
*
* @param cacheName 认证或者授权缓存的统一名称
* @return
* @param <K>
* @param <V>
* @throws CacheException
*/