Shiro 安全管理器 SecurityManager
可以配置缓存,就我使用, Shiro 可以缓存两类信息,一类是用户认证登录后,登录成功后的用户的信息,它们使用SessionManager
管理。
Shiro 默认使用 Session 会话管理技术,第一次访问被拦截要求认证登录时,会生成
JSESSIONID
在响应头中,可以自行 F12 查看
所以,最简洁的代码是这样的
public DefaultWebSessionManager sessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
return sessionManager;
}
@Bean
public SecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setSessionManager(sessionManager());
securityManager.setRealm(shiroRealm());
return securityManager;
}
但使用运行后会发现一个小 Bug,第一次访问浏览器会提示
Whitelabel Error Page
HTTP Status 400 – Bad Request
这是因为 Shiro 跳转登录认证,302 重定向 URL 中带 JESSIONID
导致。
解决办法也很简单,禁止就行。
public DefaultWebSessionManager sessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setSessionIdUrlRewritingEnabled(false);
return sessionManager;
}
这是 Shiro 的 Session 缓存功能,接下来,认证成功后,会将用户信息缓存在服务器里,使用 JESSIONID
获取,这就是我们说的会话技术。
如果是单体项目,不需要负载均衡,上面这种方案其实也还好,也不是不能用,但还不够好。更好的方案是用 Redis 缓存用户登录后的个人信息,一样可以解决重定向 URL 带 JESSIONID
的问题,而且这种方案还能实现分布式架构中 Session 共享,所以现在我们一般使用 Shiro + Redis 缓存数据。
用的比较多的框架是 Shiro-Redis
开源项目。
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>3.3.1</version>
<exclusions>
<exclusion>
<artifactId>shiro-core</artifactId>
<groupId>org.apache.shiro</groupId>
</exclusion>
<exclusion>
<artifactId>jedis</artifactId>
<groupId>redis.clients</groupId>
</exclusion>
</exclusions>
</dependency>
还记得我前面提到的缓存管理器是谁吗?SessionManager
,这个逻辑要理清楚,因为这个插件还可以缓存其他东西,也就是开头说的第二类。
@Bean
public SecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setSessionManager(sessionManager());
securityManager.setRealm(shiroRealm());
return securityManager;
}
从如上代码可知,这里需要sessionManager()
。行,那我们就来写一个。
public DefaultWebSessionManager sessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setSessionDAO(redisSessionDAO());
return sessionManager;
}
这里相比上面代码多了一行SessionDAO
,它的参数 redisSessionDAO()
就是插件的内容,就是在这里引入 Redis
缓存 Session 的。
/**
* RedisSessionDAO shiro sessionDao层的实现 通过redis
* 使用的是shiro-redis开源插件
*/
public RedisSessionDAO redisSessionDAO() {
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setRedisManager(redisManager());
redisSessionDAO.setExpire(redisProperties.getExpire());
return redisSessionDAO;
}
RedisSessionDAO
设置的两个参数,第二个是 Session 的过期时间,这个时间插件有要求,至少要比默认的半小时(1800000ms)要多,当然还可以设置成特殊的 -1 和 -2 ,具体意义看教程。
另外一个参数是 RedisManager
,这里面设置的参数属性就是 Redis
的配置信息。
public RedisManager redisManager(){
RedisManager redisManager = new RedisManager();
redisManager.setHost(redisProperties.getHost()+":"+redisProperties.getPort());
redisManager.setDatabase(redisProperties.getDatabase());
redisManager.setTimeout(redisProperties.getTimeout().getNano() * 1000);
redisManager.setPassword(redisProperties.getPassword());
return redisManager;
}
Shiro-Redis
插件的配置差不多完成,使用后,连接 Redis
就能发现有存储信息。
这里还要说个事,可能有些人使用这个插件,或者说这个框架时会碰到下面的错。
tried to access method redis.clients.jedis.JedisPool.returnResource
网上解决方案都说降版本,也就是降低依赖Shiro-Redis
的版本,其实升版本一样可以,而且会向下兼容,建议使用如上我贴出的版本。
再来看第二类信息缓存,也就是用户权限信息缓存,使用CacheManager
管理。
如果没给 Shiro 配置授权缓存,每次访问接口都会授权,也就是执行doGetAuthorizationInfo
这个函数方法。但如果加了缓存,会先从缓存中查询是否有权限信息,如果有,就不再执行如上方法。如果没有,就执行然后缓存到 Redis 。
如果你手上有案例,可以测试对比,先不配置缓存,然后修改数据库的权限信息,doGetAuthorizationInfo
的授权规则马上就会生效,如果打断点,应该每次访问接口都会进入这个方法。不加缓存代码如下:
@Bean
public SecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(shiroRealm());
return securityManager;
}
怎样配置授权的缓存呢?
Shiro 自带一个内存缓存MemoryConstrainedCacheManager
,其原理就是利用 HashMap 缓存数据。
@Bean
public SecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(shiroRealm());
securityManager.setCacheManager(new MemoryConstrainedCacheManager());
return securityManager;
}
然后再试验如上代码,修改权限信息,就不会立刻生效,因为有缓存,就不会执行doGetAuthorizationInfo
。
上面是系统自带的缓存类,Shiro 也支持自己实现拓展,支持用 Redis
缓存。
怎么实现呢?上面的Shiro-Redis
插件一样可以做到。
@Bean
public SecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(shiroRealm());
securityManager.setCacheManager(cacheManager());
return securityManager;
}
这里的cacheManager
也就是我们需要补全的
@Bean
public RedisCacheManager cacheManager() {
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(redisManager());
redisCacheManager.setPrincipalIdFieldName("userId");
return redisCacheManager;
}
简单解释一下上面俩参数,setRedisManager
不用多说,就上面配置的Redis
配置信息,如果上面用了,这里直接复用,而setPrincipalIdFieldName
是用来设置主键字段名称的。这里有点奇怪,Redis
缓存用户信息时,竟然没法识别我用@Id注解的主键,我记得之前版本是可以做到的。如果不加上这行设置,会报如下错误:
org.crazycake.shiro.exception.PrincipalInstanceException:
基本就这样,记得将实体类序列化,这里不用多说。
如果两类缓存都设置,Redis
缓存的数据,完整应该是这样的。
@Bean
public SecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setSessionManager(sessionManager());
securityManager.setRealm(shiroRealm());
securityManager.setCacheManager(cacheManager());
return securityManager;
}
这个插件使用下来,唯一遗憾的地方是缓存的对象是乱码,没法看清楚缓存的数据是啥,因为默认使用序列化是 ObjectSerializer
,虽然官方教程提到可以自己实现org.crazycake.shiro.serializer.RedisSerializer
,做其他形式的序列化和反序列化,比如用FastJsonRedisSerializer
或者GenericJackson2JsonRedisSerializer
。但我尝试时,还是会碰到一些问题,这里就不分享了。
ok,就这样,希望如上教程能帮到你,虽然我个人挺喜欢Shiro
,但以后用到机会不会很多,当前大家都喜欢用新的技术和玩法,比如Spring Security Oauth2
,这篇文章也算是一个总结,万一以后用到还能想起。
本文由老郭种树原创,转载请注明:https://guozh.net/springboot-shiro-redis/