Shiro提供了类似Spring的Cache抽象,即Shiro本身不实现Cache,但是对Cache进行了又抽象,方便更换不同的底层Cache实现,而Shiro官方提供了对于Ehcache缓存的支持,并提供了shiro-ehcache.jar的包。但是我们想要用Redis来作为缓存管理器,Ehcache和Redis的优劣可以去自行百度。后面我也会通过查资料总结一下的。
public interface Cache {
//根据Key获取缓存中的值
public V get(K key) throws CacheException;
//往缓存中放入key-value,返回缓存中之前的值
public V put(K key, V value) throws CacheException;
//移除缓存中key对应的值,返回该值
public V remove(K key) throws CacheException;
//清空整个缓存
public void clear() throws CacheException;
//返回缓存大小
public int size();
//获取缓存中所有的key
public Set keys();
//获取缓存中所有的value
public Collection values();
}
public interface CacheManager {
//根据缓存名字获取一个Cache
public Cache getCache(String name) throws CacheException;
}
public interface CacheManagerAware {
//注入CacheManager
void setCacheManager(CacheManager cacheManager);
}
Shiro内部响应的组件(DefaultSecurityManager)会自动检测响应的对象(如Realm)是否实现了CacheManagerAware并自动注入响应的CacheManager。
我们要使用Redis作为缓存管理器,那么我们就需要实现这些接口,但是前提是我们要在Spring-Shiro.xml中配置缓存管理器。
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="sessionManager" ref="sessionManager"/>
<property name="cacheManager" ref="customCacheManager"/>
<property name="realms">
<list>
<ref bean="shiroRealm"/>
list>
property>
bean>
<bean id="shiroRealm" class="com.why.authority.realms.ShiroRealm">
<property name="credentialsMatcher" ref="credentialsMatcher"/>
<property name="cachingEnabled" value="true"/>
<property name="authenticationCachingEnabled" value="true"/>
<property name="authorizationCachingEnabled" value="true"/>
bean>
<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<property name="sessionIdUrlRewritingEnabled" value="false" />
<property name="globalSessionTimeout" value="1800000"/>
<property name="deleteInvalidSessions" value="true"/>
<property name="sessionValidationSchedulerEnabled" value="true"/>
<property name="sessionIdCookieEnabled" value="true"/>
bean>
<bean id="customCacheManager" class="com.why.authority.cache.CustomCacheManager"/>
package com.why.authority.cache;
import com.why.utils.jedis.JedisClientSingle;
import com.why.utils.serializable.ByteSourceUtils;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import redis.clients.jedis.Jedis;
import java.io.Serializable;
import java.util.*;
/**
* @author: 王洪玉
* @decsription: 缓存管理器
* @create: 2017/12/5 21:18
* @modified By:
*/
public class RedisCache<K,V> implements Cache<K,V> {
public String getKeyPrefix() {
return keyPrefix;
}
public void setKeyPrefix(String keyPrefix) {
this.keyPrefix = keyPrefix;
}
private String keyPrefix = "shiro_redis_session:";
/**
* 获得byte[]型的key
* @param key
* @return
*/
private byte[] getByteKey(Object key){
if(key instanceof String){
String preKey = this.keyPrefix + key;
return preKey.getBytes();
}else{
return ByteSourceUtils.serialize((Serializable) key);
}
}
@Override
public Object get(Object key) throws CacheException {
byte[] bytes = getByteKey(key);
byte[] value = JedisClientSingle.getJedis().get(bytes);
if(value == null){
return null;
}
return ByteSourceUtils.deserialize(value);
}
/**
* 将shiro的缓存保存到redis中
*/
@Override
public Object put(Object key, Object value) throws CacheException {
Jedis jedis = JedisClientSingle.getJedis();
jedis.set(getByteKey(key), ByteSourceUtils.serialize((Serializable)value));
byte[] bytes = jedis.get(getByteKey(key));
Object object = ByteSourceUtils.deserialize(bytes);
return object;
}
@Override
public Object remove(Object key) throws CacheException {
Jedis jedis = JedisClientSingle.getJedis();
byte[] bytes = jedis.get(getByteKey(key));
jedis.del(getByteKey(key));
return ByteSourceUtils.deserialize(bytes);
}
/**
* 清空所有缓存
*/
@Override
public void clear() throws CacheException {
JedisClientSingle.getJedis().flushDB();
}
/**
* 缓存的个数
*/
@Override
public int size() {
Long size = JedisClientSingle.getJedis().dbSize();
return size.intValue();
}
/**
* 获取所有的key
*/
@Override
public Set keys() {
Set keys = JedisClientSingle.getJedis().keys(new String("*").getBytes());
Set
package com.why.authority.cache;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.apache.shiro.cache.CacheManager;
/**
* @author: 王洪玉
* @decsription: 自定义缓存管理器
* @create: 2017/12/5 21:43
* @modified By:
*
*/
public class CustomCacheManager implements CacheManager {
@Override
public Cache getCache(String name) throws CacheException {
return new RedisCache();
}
}
package com.why.utils.jedis;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
/**
* 单机版
* @author why
* @Time 2017年10月31日
*/
public class JedisClientSingle {
private static JedisPool jedisPool;
static {
JedisPoolConfig jedisConfig = new JedisPoolConfig();
jedisConfig.setMaxTotal(100);
jedisConfig.setMaxIdle(10);
jedisConfig.setMaxWaitMillis(100);
//主机名称和端口号,开启redis的服务器和端口号
jedisPool = new JedisPool(jedisConfig, "192.168.131.128", 6379);
}
public static Jedis getJedis() {
return jedisPool.getResource();
}
public static void close(Jedis jedis) {
jedis.close();
}
}
由于在Spring-Shiro.xml文件中已经配置开启登录时开启缓存,那么当用户登录的时候,会默认先调用RedisCache的get方法从缓存中获取,如果没有该用户的缓存的话,则执行登录的业务查询数据库,然后将查询出来的数据存入到缓存中。
自定义Realm实现登录验证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//1.把AuthenticationToken转换为UsernamePasswordToken
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
//2.从UsernamePasswordToken中获取userCode
String userCode = usernamePasswordToken.getUsername();
//3.获取用户信息userEntity
List userEntityList = userFacade.findByUserCode(userCode);
UserEntity userEntity=userEntityList.get(0);
//4.根据用户的情况,来构建AuthenticationInfo对象并返回
String credentials = userEntity.getPassword();
//使用ByteSource.Util.bytes()来计算盐值
//ByteSource credentialsSalt = ByteSource.Util.bytes(userCode);
return new SimpleAuthenticationInfo(userEntity, credentials,new MySimpleByteSource(userCode),getName());
}
注意:这里最初用的是下面的代码,但是会报java.io.NotSerializableException:org.apache.shiro.util.SimpleByteSource异常,出现这个问题的原因就是在将用户信息存入Redis之前,需要将SimpleAuthenticationInfo信息进行序列化,但是SimpleByteSource没有是实现Serializable接口
解决办法:自定义一个类继承SimpleByteSource实现Serializable接口或实现ByteSource接口和Serializable接口,具体请看我的这篇博客:点击这里
ByteSource credentialsSalt = ByteSource.Util.bytes(userCode);
return new SimpleAuthenticationInfo(userEntity, credentials,credentialsSalt,getName());
总结:到此为止,我们的Shiro的缓存管理器就成功的切换成Redis了,过程中学习到了很多,遇到了各种BUG,但是通过不断的调试和查资料,还是都解决了,看到功能成功实现,还是很开心的。