SpringBoot集成shiro使用Redis实现缓存机制
解决MD5+盐值加密反序列化失败的问题
-
首先需要实现一个RedisCacheManager类即CacheManager,可以实现CacheManager接口,具体代码如下:
package com.dk.config.shiro.cache; import org.apache.shiro.cache.Cache; import org.apache.shiro.cache.CacheException; import org.apache.shiro.cache.CacheManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * 自定义shiro缓存管理器 * @author MJH on 2021/6/1. * @version 1.0 */ public class RedisCacheManager implements CacheManager { //获取日志 private Logger logger = LoggerFactory.getLogger(this.getClass()); //参数1:认证或者是授权缓存的统一名称 @Override public <K, V> Cache<K, V> getCache(String cacheName) throws CacheException { logger.info("输出的cacheName是什么呢???================"+cacheName); return new RedisCache<K, V>(cacheName); } }
-
RedisManager需要返回一个Cache类型的数据,这里我们需要自定义实现一个RedisCache类,可以实现Cache接口,具体代码如下:
package com.dk.config.shiro.cache; import com.dk.util.function.ApplicationContextUtils; import com.sun.org.apache.regexp.internal.RE; import org.apache.shiro.cache.Cache; import org.apache.shiro.cache.CacheException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; import org.springframework.web.context.ContextLoader; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.support.WebApplicationContextUtils; import java.util.Collection; import java.util.Set; /** * 自定义redis缓存的实现 * @author MJH on 2021/6/1. * @version 1.0 */ public class RedisCache<K, V> implements Cache<K, V> { private String cacheName; //获取日志 private Logger logger = LoggerFactory.getLogger(this.getClass()); public RedisCache() { } public RedisCache(String cacheName) { this.cacheName = cacheName; } @Override public V get(K k) throws CacheException { logger.info("从"+this.cacheName+"中get key:" + k); return (V) getRedisTemplate().opsForHash().get(this.cacheName, k.toString()); } @Override public V put(K k, V v) throws CacheException { logger.info("向"+this.cacheName+"中put key:"+k + " put value:"+k); getRedisTemplate().opsForHash().put(this.cacheName, k.toString(), v); return null; } @Override public V remove(K k) throws CacheException { logger.info("退出时执行的是remove函数"); return (V) getRedisTemplate().opsForHash().delete(this.cacheName, k.toString()); } @Override public void clear() throws CacheException { getRedisTemplate().delete(this.cacheName); } @Override public int size() { return getRedisTemplate().opsForHash().size(this.cacheName).intValue(); } @Override public Set<K> keys() { return getRedisTemplate().opsForHash().keys(this.cacheName); } @Override public Collection<V> values() { return getRedisTemplate().opsForHash().values(this.cacheName); } private RedisTemplate getRedisTemplate(){ RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate"); redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setHashKeySerializer(new StringRedisSerializer()); //redisconfig中将值序列化为Jackson了,但是这种序列化存储shiro的信息的话,反序列化会失败,所以这里单独设置下 redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer()); redisTemplate.setHashValueSerializer(new JdkSerializationRedisSerializer()); return redisTemplate; } }
-
在ShiroConfig中UserRealm中设置下,具体代码如下:
//创建Realm对象, 需要自定义类:1 @Bean public UserRealm userRealm(@Qualifier("credentialsMatcher") HashedCredentialsMatcher credentialsMatcher){ UserRealm userRealm = new UserRealm(); userRealm.setCredentialsMatcher(credentialsMatcher); //设置加密方式 //开启缓存管理 //userRealm继承的AuthorizingRealm中默认开启了授权缓存,修改缓存名需要setCacheManager之前设置后,关闭也是 userRealm.setAuthorizationCacheName("authorizationCache"); //设置授权缓存名称 userRealm.setCacheManager(new RedisCacheManager()); userRealm.setCachingEnabled(true); //开启全局缓存 userRealm.setAuthenticationCachingEnabled(true); //开启认证缓存 userRealm.setAuthenticationCacheName("authenticationCache"); return userRealm; }
-
进行了以上操作后就实现了用redis作为shiro的缓存实现机制,但是如果你存储的密码是MD5+盐值加密,在序列化和反序列化时会出现问题,这是因为你的盐值没有序列化的原因,修改认证时生成盐值
ByteSource credentialsSalt = ByteSource.Util.bytes(user.getUsername());
为ByteSource credentialsSalt = new MyByteSurce(user.getUsername());
,其中反序列的话问题是因为生成盐值的函数中没有默认的空构造函数,供反序列化时使用,所以,这里要自己实现一个MyByteSurce来生成序列化后的盐值,继承Serializable
,且里面要包含空构造函数。,具体代码如下:package com.dk.config.shiro.salt; import org.apache.shiro.codec.Base64; import org.apache.shiro.codec.CodecSupport; import org.apache.shiro.codec.Hex; import org.apache.shiro.util.ByteSource; import org.apache.shiro.util.SimpleByteSource; import java.io.File; import java.io.InputStream; import java.io.Serializable; import java.util.Arrays; /** * 自定义salt实现 实现序列化接口 * @author MJH on 2021/6/1. * @version 1.0 */ public class MyByteSurce implements ByteSource, Serializable { private byte[] bytes; private String cachedHex; private String cachedBase64; //这个是空构造函数 public MyByteSurce(){ } public MyByteSurce(byte[] bytes) { this.bytes = bytes; } public MyByteSurce(char[] chars) { this.bytes = CodecSupport.toBytes(chars); } public MyByteSurce(String string) { this.bytes = CodecSupport.toBytes(string); } public MyByteSurce(ByteSource source) { this.bytes = source.getBytes(); } public MyByteSurce(File file) { this.bytes = (new MyByteSurce.BytesHelper()).getBytes(file); } public MyByteSurce(InputStream stream) { this.bytes = (new MyByteSurce.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 byte[] getBytes() { return this.bytes; } public boolean isEmpty() { return this.bytes == null || this.bytes.length == 0; } public String toHex() { if (this.cachedHex == null) { this.cachedHex = Hex.encodeToString(this.getBytes()); } return this.cachedHex; } public String toBase64() { if (this.cachedBase64 == null) { this.cachedBase64 = Base64.encodeToString(this.getBytes()); } return this.cachedBase64; } public String toString() { return this.toBase64(); } public int hashCode() { return this.bytes != null && this.bytes.length != 0 ? Arrays.hashCode(this.bytes) : 0; } 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); } } }
以上就是用redis实现shiro的缓存机制的代码,遇到问题多百度,总有符合解决自己问题的结果。
特别说明:以上代码是看bilibili上狂神说java和编程不良人两个up的视频进行学习的。