Shiro使用Session缓存

Shiro的Session缓存主要有两种方案,一种是使用Shiro自己的Session,不使用HttpSession,自己实现Shiro的Cache接口和Session缓存等;另外一种是直接使用spring boot的spring-session-data-redis的包,并且配置RedisTemplate和Redis的序列化方法就可以了。相对来说第二种方式非常简单,第一种还需要不少开发工作。下面主要来说第一种方式的思路。

Shiro的缓存

要缓存Session,最好要先集成Shiro的缓存。Shiro提供了类似于Spring的Cache抽象,即Shiro本身不实现Cache,但是对Cache进行了又抽象,方便更换不同的底层Cache实现。 Shiro提供了Cache接口和CacheManager接口,以及CacheManagerAware接口来注入CacheManager。

  • 实现Cache的缓存接口,shiro的Cache对进行Spring Cache包装
@SuppressWarnings("unchecked")
class SpringCacheWrapper <V> implements Cache<String, V> {

    private final String REDIS_SHIRO_CACHE = "shiro-cache#";
    private org.springframework.cache.Cache springCache;
    private CacheManager cacheManager;

    SpringCacheWrapper(CacheManager cacheManager, @NotNull org.springframework.cache.Cache springCache) {
        this.springCache = springCache;
        this.cacheManager = cacheManager;
    }

    @Override
    public V get(String key) throws CacheException {
    	ValueWrapper cacheValue = springCache.get(getCacheKey(key));        	
        return (V) Optional.ofNullable(cacheValue).map(p->p.get()).orElse(null);
    }

    @Override
    public V put(String key, V value) throws CacheException {
    	springCache.put(getCacheKey(key), value);
        return value;
    }

    @Override
    public V remove(String key) throws CacheException {
    	springCache.evict(getCacheKey(key));
        return null;
    }

    private String getCacheKey(String key) {
		return REDIS_SHIRO_CACHE + key;
	}

	@Override
    public void clear() throws CacheException {
    	springCache.clear();
    }

    @Override
    public int size() {
    	throw new UnsupportedOperationException("invoke spring cache size method not supported");
    }

    @Override
    public Set<String> () {
        throw new UnsupportedOperationException("invoke spring cache keys method not supported");
    }

    @Override
    public Collection<V> values() {
    	throw new UnsupportedOperationException("invoke spring cache values method not supported");
    }
}

实现CacheManager的缓存接口,shiro的CacheManager对进行Spring CacheManager的包装

public class SpringCacheManagerWrapper implements CacheManager {

    private org.springframework.cache.CacheManager cacheManager;
    
    public SpringCacheManagerWrapper(org.springframework.cache.CacheManager cacheManager) {
		this.cacheManager = cacheManager;
	}

	@Override
    public <K, V> Cache<K, V> getCache(String name) throws CacheException {
        org.springframework.cache.Cache springCache = cacheManager.getCache(name);
        return new SpringCacheWrapper(springCache);     
    }
}

Session的缓存

需要实现CacheSessionDAO接口,实现Session的缓存方法。

public class ShiroSessionDAO extends CachingSessionDAO {

    private Cache<String, Session> cache;

    public ShiroSessionDAO(CacheManager cacheManager) {
        String cacheName = getActiveSessionsCacheName();
        this.setCacheManager(cacheManager);
        this.cache = getCacheManager().getCache(cacheName);
    }

    @Override
    protected void doUpdate(Session session) {
        if(session==null) {
        	return;
        }
        cache.put(session.getId().toString(), session);
    }

    @Override
    protected void doDelete(Session session) {
        if(session==null){
        	return;
        }       
        cache.remove(session.getId().toString());
    }

    @Override
    protected Serializable doCreate(Session session) {
        if(session == null) {
        	return null;
        }      
        Serializable sessionId = this.generateSessionId(session);
        assignSessionId(session, sessionId);
        cache.put(sessionId.toString(), session);
        return sessionId;
    }


    @Override
    protected Session doReadSession(Serializable sessionId) {
        if(sessionId==null) {
        	return null;        	
        }
        
        Session session=(Session) cache.get(sessionId.toString());
        return session;
    }
}

配置Bean

配置Redis

@Configuration  
public class RedisConfiguration extends CachingConfigurerSupport {

	@Bean
	public KeyGenerator keyGenerator() {
		return new KeyGenerator() {
			@Override
			public Object generate(Object target, Method method, Object... params) {
				StringBuilder sb = new StringBuilder();
				sb.append(target.getClass().getName());
				sb.append("#" + method.getName());
				for (Object obj : params) {
					sb.append(obj.toString());
				}
				return sb.toString();
			}
		};
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	@Bean
	public RedisCacheManager redisCacheManager(@Autowired RedisTemplate redisTemplate) {
		// spring cache注解序列化配置
		RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
				.serializeKeysWith(
						RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getKeySerializer()))
				.serializeValuesWith(
						RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getValueSerializer()))
				.disableCachingNullValues()
				.entryTtl(Duration.ofSeconds(60));

		Set<String> cacheNames = new HashSet<>();
		cacheNames.add("user");

		Map<String, RedisCacheConfiguration> configMap = new HashMap<>();
		configMap.put("user", redisCacheConfiguration.entryTtl(Duration.ofSeconds(120)));

		RedisCacheManager redisCacheManager = RedisCacheManager.builder(redisTemplate.getConnectionFactory())
				.cacheDefaults(redisCacheConfiguration).transactionAware().initialCacheNames(cacheNames) 
				.withInitialCacheConfigurations(configMap).build();
		return redisCacheManager;
	}

	@Bean
	public RedisTemplate<Object, Object> redisTemplate(@Autowired RedisConnectionFactory redisConnectionFactory)
			throws UnknownHostException {
		RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<Object, Object>();
		redisTemplate.setConnectionFactory(redisConnectionFactory);
		
		StringRedisSerializer stringSerializer = new StringRedisSerializer();
		redisTemplate.setKeySerializer(stringSerializer); // key序列化
		redisTemplate.setHashKeySerializer(stringSerializer); // Hash key序列化
		
		FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<Object>(Object.class);
		redisTemplate.setValueSerializer(fastJsonRedisSerializer); // value序列化
		redisTemplate.setHashValueSerializer(fastJsonRedisSerializer); // Hash value序列化
		redisTemplate.afterPropertiesSet();
		return redisTemplate;
	}
	
	@Bean
	public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)
			throws UnknownHostException {
		StringRedisTemplate template = new StringRedisTemplate();
		template.setConnectionFactory(redisConnectionFactory);
		return template;
	}

}

配置Shiro的缓存和CacheSessionDao

@Bean(name = "securityManager")
public org.apache.shiro.mgt.SecurityManager defaultWebSecurityManager(@Autowired UserRealm 		userRealm, @Autowired TokenRealm tokenValidateRealm) {
    DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
    securityManager.setAuthenticator(multiRealmAuthenticator());
    securityManager.setRealms(Arrays.asList(userRealm, tokenValidateRealm));
    securityManager.setRememberMeManager(rememberMeManager());
    securityManager.setCacheManager(new SpringCacheManagerWrapper());
	//必须使用DefaultWebSessionManager,不能是ServletContainerSessionManager
    DefaultWebSessionManager webSessionManager = new DefaultWebSessionManager();
    webSessionManager.setSessionDAO(cachingSessionDAO);
    securityManager.setSessionManager(webSessionManager);
    return securityManager;
}

总结

从上面的步骤可以看出配置Shiro的Session缓存,还是比较麻烦的。本来也是打算采用这种方式,后来在网上发现有个Session集成Redis的包,如下所示,也发现使用这种方式更简单,后来就直接使用Spring的Session了,只需要配置Redis(如上所示)就可以了。

 <dependency>
     <groupId>org.springframework.session</groupId>
     <artifactId>spring-session-data-redis</artifactId>
</dependency>
posted @ 2019-09-17 09:35  fzsyw  阅读(1039)  评论(0编辑  收藏  举报