spring-boot redis:如何使用户的所有会话无效?

Posted

技术标签:

【中文标题】spring-boot redis:如何使用户的所有会话无效?【英文标题】:spring-boot redis : How to invalidate all sessions of a user? 【发布时间】:2017-08-22 18:34:31 【问题描述】:

我是 redis 的新手。我已按照本教程将 HttpSession 与 redis 一起使用。

https://docs.spring.io/spring-session/docs/current/reference/html5/guides/boot.html

现在我的应用程序具有“退出所有设备”选项。单击该按钮后,如何删除或使该用户的所有会话无效?

此外,当用户更改密码时,我如何使他的所有会话无效,除了当前会话?

编辑:

我尝试使用会话注册表。

@Autowired
private FindByIndexNameSessionRepository sessionRepository;

@Autowired
FindByIndexNameSessionRepository<? extends ExpiringSession> sessions;

@RequestMapping(value = "/logoutalldevices", method = RequestMethod.GET)
public Response test(HttpServletRequest request, HttpServletResponse response) throws Exception 

    SpringSessionBackedSessionRegistry sessionRegistry = new SpringSessionBackedSessionRegistry(sessionRepository);

    Collection<? extends ExpiringSession> usersSessions = sessions
            .findByIndexNameAndIndexValue(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, "myUserId")
            .values();

    usersSessions.forEach((temp) -> 
        String sessionId = temp.getId();
        // sessionRegistry.removeSessionInformation(sessionId);
        SessionInformation info = sessionRegistry.getSessionInformation(sessionId);
        info.expireNow();
    );

    return Response.ok().build();

但它不会从 redis db 中删除会话或使其无效。尽管它向名为“sessionAttr:org.springframework.session.security.SpringSessionBackedSessionInformation.EXPIRED”的会话添加了一个新属性,值为true。当我这样做时,我可以使用 redis 客户端在 redis db 中看到这个新的键值对

HGETALL 'sessionid'

编辑

我尝试使用 redistemplate 从 redis db 手动删除会话。

@Autowired
RedisTemplate<String, String> redisTemplate;

---------

redisTemplate.delete("spring:session:sessions:" + sessionId);
redisTemplate.delete("spring:session:sessions:expires:" + sessionId);

这几乎可行。它从 redis db 中删除值,但不删除键。

127.0.0.1:6379> keys *
1) "spring:session:sessions:25635a14-a4f1-4aa1-bf5a-bc20f972eec7"
2) "spring:session:sessions:expires:25635a14-a4f1-4aa1-bf5a-bc20f972eec7"
3) "spring:session:index:org.springframework.session.FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME:1"
127.0.0.1:6379> hgetall spring:session:sessions:25635a14-a4f1-4aa1-bf5a-bc20f972eec7
1) "lastAccessedTime"
2) "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;\x8b\xe4\x90\xcc\x8f#\xdf\x02\x00\x01J\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x01[R'\x15\xc1"
127.0.0.1:6379> 

它删除了会话中除 lastAccessedTime 时间之外的所有其他键值对。

还有一个奇怪的是,这是我在执行redisTemplate.delete("key") 时在redis 监视器中看到的日志:

1491731944.899711 [0 127.0.0.1:62816] "DEL" "spring:session:sessions:25635a14-a4f1-4aa1-bf5a-bc20f972eec7"
1491731944.899853 [0 127.0.0.1:62816] "DEL" "spring:session:sessions:expires:25635a14-a4f1-4aa1-bf5a-bc20f972eec7"

如果我把上面两个命令复制粘贴到redis-client执行,key就被删除了。当我不再执行keys * 时,我看不到密钥。我想知道为什么使用RedisTemplate删除密钥时没有删除密钥

127.0.0.1:6379> "DEL" "spring:session:sessions:25635a14-a4f1-4aa1-bf5a-bc20f972eec7"
(integer) 1
127.0.0.1:6379> "DEL" "spring:session:sessions:expires:25635a14-a4f1-4aa1-bf5a-bc20f972eec7"
(integer) 1
127.0.0.1:6379> keys *
1) "spring:session:index:org.springframework.session.FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME:1"
127.0.0.1:6379>

【问题讨论】:

你能分享你的spring安全配置吗? 我没有设置任何配置。刚刚添加了 spring-boot-starter-security 依赖项。而已。我需要添加一些东西吗? 您评论了// sessionRegistry.removeSessionInformation(sessionId);,应该可以正常工作。你的情况发生了什么? 我试过了,还是不行。 sessionStatus.setComplete() 对您的情况有用吗? 【参考方案1】:

试试这个删除键“redisTemplate.opsForValue().getOperations().delete(KEY);”

【讨论】:

效果一样。不删除密钥。【参考方案2】:

我想知道您 you are following the correct path 使用户会话无效

    usersSessions.forEach((session) ->         
        sessionRegistry.getSessionInformation(session.getId()).expireNow();
    );

注意事项

SessionInformation.expireNow()

并不是要从redis 数据库中删除条目,它只是将过期属性附加到会话中,正如您正确提到的那样。

但这如何使用户的会话无效?

ConcurrentSessionFilter 在这里发挥作用 .doFilter() 方法完成了automatically logging out 的伎俩

这是 ConcurrentSessionFilter

的 sn-p
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
        throws IOException, ServletException 
    HttpServletRequest request = (HttpServletRequest) req;
    HttpServletResponse response = (HttpServletResponse) res;

    HttpSession session = request.getSession(false);

    if (session != null) 
        SessionInformation info = sessionRegistry.getSessionInformation(session
                .getId());

        if (info != null) 
            if (info.isExpired()) 
                // Expired - abort processing
                doLogout(request, response);

                String targetUrl = determineExpiredUrl(request, info);

                if (targetUrl != null) 
                    redirectStrategy.sendRedirect(request, response, targetUrl);

                    return;
                
                else 
                    response.getWriter().print(
                            "This session has been expired (possibly due to multiple concurrent "
                                    + "logins being attempted as the same user).");
                    response.flushBuffer();
                

                return;
            
            else 
                // Non-expired - update last request date/time
                sessionRegistry.refreshLastRequest(info.getSessionId());
            
        
    

    chain.doFilter(request, response);

干杯!

【讨论】:

我发现这种方法的一个问题是过期的会话永远不会从 redis db 中删除。 @HeisenBerg 你去吧:spring.session.cleanup.cron.expression(系统属性)【参考方案3】:

试试这个

usersSessions.forEach((session) ->         
        sessionRegistry.delete(session.getId());
  );

【讨论】:

【参考方案4】:

如果你只想在调试过程中做一次事情,你可以登录redis_cli并刷新所有Redis键。

$ redis-cli
127.0.0.1:6379> KEYS *
1) "spring:session:index:org.springframework.session.FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME:bbb"
2) "spring:session:expirations:1558782600000"
3) "spring:session:expirations:1558783140000"
4) "spring:session:sessions:expires:953146bf-7300-4394-bbf0-bf606ff6b326"
5) "spring:session:expirations:1558782540000"
6) "spring:session:sessions:953146bf-7300-4394-bbf0-bf606ff6b326"
127.0.0.1:6379> FLUSHALL
OK
127.0.0.1:6379> KEYS *
(empty list or set)
127.0.0.1:6379>

【讨论】:

【参考方案5】:
@Autowired
private RedisIndexedSessionRepository redisIndexedSessionRepository;

redisIndexedSessionRepository.findByPrincipalName('your@login').keySet().forEach(redisIndexedSessionRepository::deleteById);

【讨论】:

也许您可以扩展您的答案,解释该代码的作用,和/或自提出问题以来的 4 年内情况如何变化。 是的,您确实应该包含一个存在此答案的上下文以及为什么它比已经接受的答案更好。 这个确实解决了我的问题。谢谢!【参考方案6】:

我偶尔会遇到现有会话对象与当前代码不兼容的问题,通常是在升级到包含较新 Spring 版本的版本之后。即serialVersionUID 已更改。

在这种情况下,您不能使用任何会话接口(以及上面的答案),因为它们都会尝试反序列化无法完成的对象。

相反,您只需回到基础

Set<String> sessionKeys = redisTemplate.keys("spring:session:*");
if (sessionKeys != null) 
    redisTemplate.delete(sessionKeys);

如果您在@EnableRedisHttpSession 上设置了不同的redisNameSpace,则需要更改关键字搜索"spring:session:*"

【讨论】:

以上是关于spring-boot redis:如何使用户的所有会话无效?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Spring-Boot 项目中为电话号码身份验证创建 REST API?

如何使 azure redis 服务的公共链接和私有端点链接同时工作

Redis进阶实践之十一 Redis的Cluster集群搭建

spring-boot Cache redis 类型转换错误

Spring-Boot 使用 Jedis 操作 Redis

spring-boot导入redis依赖后报错 Error creating bean with name ‘redisConnectionFactory‘