如果单机,使用EHCache就可以的,单如果多节点部署时就不行了,本文主要将Shiro和Redis缓存集成,在上一篇文章Shiro功能应用(六)–登陆失败重试次数控制代码基础进行添加Redis缓存。
代码地址:
https://github.com/OooooOz/SpringBoot-Shiro
首先将ShiroConfig关于EHCache的和SessionManager的配置去掉。
ShiroConfig的安全管理器SecurityManager:
@Bean(name="securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("shiroRealm") MyShiroRealm shiroRealm){
... ... ... ...
securityManager.setCacheManager(redisCacheManager()); //配置 redis缓存管理器
securityManager.setSessionManager(redisSessionManager()); //配置 redissession管理
return securityManager;
}
ShiroConfig的Redis缓存管理器:
//import org.crazycake.shiro.RedisCacheManager;包的RedisCacheManager的对象
@Bean("redisCacheManager")
public RedisCacheManager redisCacheManager(){
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(redisManager());
return redisCacheManager;
}
ShiroConfig的Redis配置管理器:
@Bean
public RedisManager redisManager(){
RedisManager redisManager = new RedisManager();
redisManager.setHost("192.168.2.104");
redisManager.setPort(6379);
//redisManager.setPassword("123456");
return redisManager;
}
ShiroConfig的Redis会话管理器:
@Bean("redisSessionManager")
public SessionManager redisSessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
Collection<SessionListener> listeners = new ArrayList<SessionListener>();
//配置监听
listeners.add(sessionListener());
sessionManager.setSessionListeners(listeners);
sessionManager.setSessionIdCookie(sessionIdCookie());
sessionManager.setSessionDAO(redisSessionDAO());
sessionManager.setCacheManager(redisCacheManager());
//sessionManager.setGlobalSessionTimeout(60000); //全局会话超时时间(单位毫秒),默认30分钟 暂时设置为10秒钟 用来测试
sessionManager.setDeleteInvalidSessions(true);
//取消url 后面的 JSESSIONID
sessionManager.setSessionIdUrlRewritingEnabled(false);
return sessionManager;
}
ShiroConfig的redisSessionDao:
@Bean("redisSessionDAO")
public SessionDAO redisSessionDAO() {
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setRedisManager(redisManager());
redisSessionDAO.setExpire(3000);//session在redis中的保存时间,最好大于session会话超时时间,单位s
return redisSessionDAO;
}
自定义Realm类修改认证方法返回的info:
SimpleAuthenticationInfo authenticationInfo =
// new SimpleAuthenticationInfo(user,user.getPassword(),ByteSource.Util.bytes(user.getSalt()),"shiroRealm");
new SimpleAuthenticationInfo(user,user.getPassword(),new MySimpleByteSource(user.getSalt()),"shiroRealm");
新增的MySimpleByteSource类:
/**
* 解决:
* shiro 使用缓存时出现:java.io.NotSerializableException: org.apache.shiro.util.SimpleByteSource
* 序列化后,无法反序列化的问题
*/
public class MySimpleByteSource implements ByteSource, Serializable {
private static final long serialVersionUID = 5175082362119580768L;
private byte[] bytes;
private String cachedHex;
private String cachedBase64;
public MySimpleByteSource(){
}
public MySimpleByteSource(byte[] bytes) {
this.bytes = bytes;
}
public MySimpleByteSource(char[] chars) {
this.bytes = CodecSupport.toBytes(chars);
}
public MySimpleByteSource(String string) {
this.bytes = CodecSupport.toBytes(string);
}
public MySimpleByteSource(ByteSource source) {
this.bytes = source.getBytes();
}
public MySimpleByteSource(File file) {
this.bytes = (new BytesHelper()).getBytes(file);
}
public MySimpleByteSource(InputStream stream) {
this.bytes = (new BytesHelper()).getBytes(stream);
}
public static boolean isCompatible(Object o) {
return o instanceof byte[] || o instanceof char[] || o instanceof String || o instanceof ByteSource || o instanceof File || o instanceof InputStream;
}
public void setBytes(byte[] bytes) {
this.bytes = bytes;
}
@Override
public byte[] getBytes() {
return this.bytes;
}
@Override
public String toHex() {
if(this.cachedHex == null) {
this.cachedHex = Hex.encodeToString(this.getBytes());
}
return this.cachedHex;
}
@Override
public String toBase64() {
if(this.cachedBase64 == null) {
this.cachedBase64 = Base64.encodeToString(this.getBytes());
}
return this.cachedBase64;
}
@Override
public boolean isEmpty() {
return this.bytes == null || this.bytes.length == 0;
}
@Override
public String toString() {
return this.toBase64();
}
@Override
public int hashCode() {
return this.bytes != null && this.bytes.length != 0? Arrays.hashCode(this.bytes):0;
}
@Override
public boolean equals(Object o) {
if(o == this) {
return true;
} else if(o instanceof ByteSource) {
ByteSource bs = (ByteSource)o;
return Arrays.equals(this.getBytes(), bs.getBytes());
} else {
return false;
}
}
private static final class BytesHelper extends CodecSupport {
private BytesHelper() {
}
public byte[] getBytes(File file) {
return this.toBytes(file);
}
public byte[] getBytes(InputStream stream) {
return this.toBytes(stream);
}
}
1).java.io.NotSerializableException: org.apache.shiro.util.SimpleByteSource异常:
因为认证方法返回info对象第三个参数ByteSource类型,并没有实现序列化接口,所以序列化的时候出现异常,
解决: 自定义一个类实现ByteSource和Serializable接口,见上文MySimpleByteSource
2).com.demo.entity.User cannot be cast to com.demo.entity.User转换异常:
一看,同样类型为什么转换不成呢?因为SpringBoot项目配了热部署。
解决: 注释掉热部署依赖。Clear一下就好了。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
<scope>true</scope>
</dependency>
3). org.crazycake.shiro.exception.PrincipalInstanceException: class com.demo.entity.User must has getter for field: authCacheKey or id无法获取缓存中权限redisKey。
因为shiro-redis3.1.0里面,有个条件写死的。本人的User类不满足条件。
可见,User类要有AuthCacheKey或者Id属性,也就是要有getAuthCacheKey()或getId()方法,条件才能满足,下面会通过映射获取属性值。不满足就抛异常了。
解决: 也行换个包可以解决,要么就修改User类,但改动会有点大,要么就换一种Redis的实现方式,也就是不用Shiro-Redis的包。可查看下一篇Shiro功能应用(八)–Shiro集成RedisTemplate(SDR)
4).RedisCache才是主要操作redis缓存的操作类, 代码中任何出现操作缓存(get、put)之类的,都会到这个类操作