shiro-redis缓存存入验证吗Captcha总是第一次取不到

一、问题说明

项目主要技术:

        SpringBoot 2.5+

        MybatisPlus 3.4.2

        Apache Shiro 1.8.0

其他:Thymeleaf,Quartz,Druid,Redis,Fastjson,lombok,velocity,swagger等

源码修改自【若依】开源系统

问题描述:

在启动项目后再第次打开登录页面登录总是报验证错误

问题原因:

Captcha生成验证码时会把验证码放在session中,第一次打开登录lonin页面输入验证登录,生成的验证码在sessionz中找不到了。

二、问题分析

问题出现在org.crazycake.shiro.RedisSessionDAO这里:

/**
     * RedisSessionDAO shiro sessionDao层的实现 通过redis
     * 使用的是shiro-redis开源插件
     */
    @Bean
    public RedisSessionDAO redisSessionDAO() {
        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        redisSessionDAO.setRedisManager(new ShiroRedisManager());
        return redisSessionDAO;
    }

打开登录页面请求后端时候生成session,shiro-redis会把session放在本地的ThreadLocal一份和Redis中一份。当生成验证码往session放之后会被ThreadLocal中的session同步掉,没有了验证码(断点跟代码时RedisSessionDAO 同一个请求超过一秒会访问Redis中的session,所以同步掉这一步我跟源码没有跟出来

 RedisSessionDAO的doReadSession在获取session时会优先从ThreadLocal获取  

protected Session doReadSession(Serializable sessionId) {
        //sessionInMemoryEnabled从本地获取session的开关
        if (this.sessionInMemoryEnabled) {
            this.removeExpiredSessionInMemory();
        }

        if (sessionId == null) {
            logger.warn("session id is null");
            return null;
        } else {
            Session session;
            if (this.sessionInMemoryEnabled) {
                session = this.getSessionFromThreadLocal(sessionId);
                if (session != null) {
                    return session;
                }
            }

            session = null;

            try {
                String sessionRedisKey = this.getRedisSessionKey(sessionId);
                logger.debug("read session: " + sessionRedisKey + " from Redis");
                session =                 
(Session)this.valueSerializer.deserialize(this.redisManager.get(this.keySerializer.serialize(sessionRedisKey)));
                if (this.sessionInMemoryEnabled) {
                    this.setSessionToThreadLocal(sessionId, session);
                }
            } catch (SerializationException var4) {
                logger.error("read session error. sessionId: " + sessionId);
            }

            return session;
        }
    }

三、解决问题 

        到这可以很明显看到怎么解决问题了把,把sessionInMemoryEnabled改为false不从ThreadLocal取就可以解决了,修改为

    @Bean
    public RedisSessionDAO redisSessionDAO() {
        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        redisSessionDAO.setSessionInMemoryEnabled(false);
        redisSessionDAO.setRedisManager(new ShiroRedisManager());
        return redisSessionDAO;
    }

 四、衍生问题

        频繁请求redis获取session会造成一定压力,而且也不太合适

        解决方法:重写DefaultWebSessionManager类的retrieveSession方法,我们可以直接把 session 对象放进 request 里去!那么在单次请求周期内我们都可以从 request 中取 session 了,而且请求结束后 request 被销毁,作用域和生命周期的问题都不需要我们考虑了。 所以我们需要 Override 这个 retrieveSession() 方法,为此我们需要使用自定义的 SessionManager,如下:
 

public class ShiroSessionManager extends DefaultWebSessionManager {
    private static final Logger logger = LoggerFactory.getLogger(ShiroSessionManager.class);
    public ShiroSessionManager(){
        super();
    }
    //重写这个方法为了减少多次从redis中读取session(自定义redisSessionDao中的doReadSession方法)
    @Override
    protected Session retrieveSession(SessionKey sessionKey){
        Serializable sessionId = getSessionId(sessionKey);
        ServletRequest request = null;
        if(sessionKey instanceof WebSessionKey){
            request = ((WebSessionKey)sessionKey).getServletRequest();
        }
        if(request != null && sessionId != null){
            Session session =  (Session) request.getAttribute(sessionId.toString());
            if(session != null){
                return session;
            }
        }
        Session session = super.retrieveSession(sessionKey);
        if(request != null && sessionId != null){
            request.setAttribute(sessionId.toString(),session);
        }
        return session;
    }
}

修改 ShiroConfig配置

  /**
     * 会话管理器
     */
    @Bean
    public ShiroSessionManager sessionManager() {
        ShiroSessionManager manager = new ShiroSessionManager();
        // 加入缓存管理器
        manager.setCacheManager(cacheManager());
        // 删除过期的session
        manager.setDeleteInvalidSessions(true);
        // 设置全局session超时时间,单位:毫秒
        manager.setGlobalSessionTimeout(expireTime * 60 * 1000);
        // 去掉 JSESSIONID
        manager.setSessionIdUrlRewritingEnabled(false);
        //使用Redis的SessionDao
        manager.setSessionDAO(redisSessionDAO());
        return manager;
    }

 解决思路参考了:springboot + shiro 整合 redis 解决频繁访问 redis 和更新 session_快乐的小三菊的博客-优快云博客_redis频繁更新

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值