Shiro中适用Redis管理session

发布时间 2024-01-02 16:47:56作者: 东方来客

实现RedisSessionDao思路

Shiro提供了SessionDAO接口,可以实现此类来操作session,其中提供了

  1. create 新建一个session,并保存到数据库、文件系统或者持久化缓存中。
  2. readSession 根据sessionId检索session
  3. update 更新session信息
  4. delete 从EIS中删除session
  5. getActiveSessions 返回EIS中active状态的session

EIS(Enterprise Information System)企业信息系统,例:文件系统、数据库、企业云等。

最终的继承关系如图所示。
image

其中CachingSessionDAO实现了CacheManagerAware接口,用于标记需要与缓存管理器进行交互的组件或服务。
需要调用setCacheManager设置一个CacheManager

RedisTemplate操作Redis

引入Redis

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

添加RedisTemplate Bean

@Bean("sessionRedisTemplate")
public RedisTemplate<String, Object> sessionRedisTemplate(RedisConnectionFactory factory) {
    RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
    template.setConnectionFactory(factory);

    GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
    // 在redis-cli中使用keys *列举出来的key不会携带\x00\x05sr\x00这种序列化的信息,是可读的
    template.setKeySerializer(new StringRedisSerializer());
    template.setValueSerializer(genericJackson2JsonRedisSerializer);
    template.setHashKeySerializer(genericJackson2JsonRedisSerializer);
    template.setHashValueSerializer(genericJackson2JsonRedisSerializer);
    template.setDefaultSerializer(genericJackson2JsonRedisSerializer);
    template.afterPropertiesSet();
    return template;
}

RedisTemplate中有一个boundValueOps方法,主要用于操作 Redis 的字符串。
传入一个key值会返回一个BoundValueOperations,其中主要提供了setget方法来设置和获取对应的信息。
还提供了boundZSetOpsboundStreamOpsboundListOps等方法。
也可以使用opsForSetopsForStream这种来进行数据操作。

使用RedisTemplate实现RedisSessionDao

public class RedisSessionDao extends CachingSessionDAO {
    private static final long TIMEOUT = 30;
    private static final String SESSION_KEY_FORMAT = "xxx_session_%s";
    private final RedisTemplate<String, Object> sessionRedisTemplate;

    public RedisSessionDao(@Qualifier("sessionRedisTemplate") RedisTemplate<String, Object> sessionRedisTemplate) {
        this.sessionRedisTemplate = sessionRedisTemplate;
        setCacheManager(new AbstractCacheManager() {
            @Override
            protected Cache<Serializable, Session> createCache(String name) throws CacheException {
                return new MapCache<Serializable, Session>(name, new ConcurrentHashMap<Serializable, Session>());
            }
        });
    }

    @Override
    protected void doUpdate(Session session) {
        BoundValueOperations<String, Object> sessionValueOperations = sessionRedisTemplate
                .boundValueOps(sessionKeyGenerator(session.getId().toString()));
        sessionValueOperations.set(session);
        sessionValueOperations.expire(TIMEOUT, TimeUnit.MINUTES);
    }

    @Override
    protected void doDelete(Session session) {
        sessionRedisTemplate.delete(sessionKeyGenerator(session.getId().toString()));
    }

    @Override
    protected Serializable doCreate(Session session) {
        Serializable sessionId = generateSessionId(session);
        assignSessionId(session, sessionId);
        BoundValueOperations<String, Object> stringObjectBoundValueOperations = sessionRedisTemplate.
                boundValueOps(sessionKeyGenerator(session.getId().toString()));
        stringObjectBoundValueOperations.set(session, TIMEOUT, TimeUnit.MINUTES);
        return sessionId;
    }

    @Override
    protected Session doReadSession(Serializable sessionId) {
        String sessionKey = sessionKeyGenerator(sessionId.toString());
        BoundValueOperations<String, Object> sessionValue = sessionRedisTemplate.boundValueOps(sessionKey);
        Session session = (Session) sessionValue.get();
        return session;
    }

    private String sessionKeyGenerator(String sessionId) {
        return String.format(SESSION_KEY_FORMAT, sessionId);
    }

}

添加ShiroConfig

@Configuration
public class ShiroConfig {
    private static final long GLOBAL_SESSION_TIMEOUT = 1000 * 60 * 60 * 24;

    /**
     * The bean for Security manager.
     *
     * @param realm the specific realm.
     * @return Security manager.
     */
    @Bean
    public SecurityManager securityManager(Realm realm, @Qualifier("CustomSessionManager") SessionManager sessionManager) {
        val securityManager = new DefaultWebSecurityManager(realm);
        securityManager.setSessionManager(sessionManager);
        SecurityUtils.setSecurityManager(securityManager);
        return securityManager;
    }

    @Bean("CustomSessionManager")
    public SessionManager sessionManager(RedisSessionDao sessionDao) {
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        sessionManager.setSessionDAO(sessionDao);
        sessionManager.setGlobalSessionTimeout(GLOBAL_SESSION_TIMEOUT);
        return sessionManager;
    }

    /**
     * The bean for security manager.
     *
     * @param securityManager security manager.
     * @return Shiro filter factory bean.
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(
        SecurityManager securityManager) {

        val shiroFilterFactoryBean = new CustomShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        val filters = shiroFilterFactoryBean.getFilters();
        filters.put("authc", new CustomFormAuthenticationFilter());
        val shiroFilterDefinitionMap = new LinkedHashMap<String, String>();
        shiroFilterDefinitionMap.put("/v1.0/session", "anon");
        shiroFilterDefinitionMap.put("/**", "authc");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(shiroFilterDefinitionMap);
        return shiroFilterFactoryBean;
    }
}

Docker启动Redis

docker run -p 6379:6379  --name redis  -d redis --appendonly yes  --requirepass "your_password"

使用到的Redis的一些命令

  1. docker exec -it your-container redis-cli
  2. 如果设置了密码需要使用auth your_password进行验证
  3. keys * 获取所有key
  4. get key_name 获取key_name对应的信息
  5. ttl key_name 查看key_name的过期时间
  6. del key_name 删除key_name
  7. flushall 清除所有保存的信息
  8. set key_name xxx 设置key_name的值为xxx