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>


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM