Spring Session/Security:Redis会话未被正确删除问题排查
问题排查与修复方案
问题概述
搭建Spring OAuth客户端(BFF架构)对接Angular与Auth0时,登录会话可正常持久化到Redis,但注销操作仅清空会话内容,会话Key仍留存,且有序集合未被清理。
核心问题分析
自定义清理逻辑与Spring Session生命周期冲突
代码中先调用sessionInformation.invalidate()清空会话属性/标记过期,再调用removeSession删除会话。但ReactiveRedisIndexedSessionRepository的deleteAndReturn方法依赖获取有效会话才能执行后续删除逻辑:- 若会话已被
invalidate()清空属性或标记过期,getSession(id)会返回Mono.empty(),跳过所有删除步骤(删除sessionKey、索引、过期键等),仅触发空的成功信号,导致日志显示删除成功但实际Redis未清理。
- 若会话已被
未利用WebSession的默认清理机制
Spring Session的WebSession.invalidate()方法会触发完整的会话清理流程,包括删除Redis中的会话Key、有序集合索引及过期键,自定义逻辑覆盖了这一默认流程,导致清理不彻底。
修复方案
方案1:改用WebSession默认invalidate方法
删除自定义SessionControl依赖,直接调用WebSession.invalidate()触发完整清理:
@Component internal class SessionServerLogoutHandler( private val sessionProperties: SessionProperties, private val csrfProperties: CsrfProperties, ) : ServerLogoutHandler { private val logger = LoggerFactory.getLogger(SessionServerLogoutHandler::class.java) override fun logout(exchange: WebFilterExchange, authentication: Authentication?): Mono<Void> { return exchange.exchange.session .flatMap { session -> logger.info("Logging out: Invalidating User Session: ${session.id}") // 调用Spring Session默认清理逻辑 session.invalidate() .then(Mono.fromRunnable { logger.info("Deleting Session Cookie: ${sessionProperties.SESSION_COOKIE_NAME}") // 删除会话Cookie val sessionCookie = ResponseCookie.from(sessionProperties.SESSION_COOKIE_NAME) .maxAge(0) .httpOnly(sessionProperties.SESSION_COOKIE_HTTP_ONLY) .secure(sessionProperties.SESSION_COOKIE_SECURE) .sameSite(sessionProperties.SESSION_COOKIE_SAME_SITE) .path(sessionProperties.SESSION_COOKIE_PATH) .domain(sessionProperties.SESSION_COOKIE_DOMAIN) .build() exchange.exchange.response.headers.add( HttpHeaders.SET_COOKIE, sessionCookie.toString() ) logger.info("Deleting CSRF Cookie: ${csrfProperties.CSRF_COOKIE_NAME}") // 删除CSRF Cookie val csrfCookie = ResponseCookie.from(csrfProperties.CSRF_COOKIE_NAME) .maxAge(0) .httpOnly(csrfProperties.CSRF_COOKIE_HTTP_ONLY) .secure(csrfProperties.CSRF_COOKIE_SECURE) .sameSite(csrfProperties.CSRF_COOKIE_SAME_SITE) .path(csrfProperties.CSRF_COOKIE_PATH) .domain(csrfProperties.CSRF_COOKIE_DOMAIN) .build() exchange.exchange.response.headers.add( HttpHeaders.SET_COOKIE, csrfCookie.toString() ) }) } } }
方案2:修复SessionControl删除逻辑
若需保留自定义SessionControl,直接跳过invalidate()步骤,调用删除方法:
fun invalidateSession(session: WebSession): Mono<Void> { val sessionId = session.id logger.info("Invalidating sessionId: $sessionId") // 直接执行删除操作,无需提前invalidate return webSessionStore.removeSession(sessionId) .doOnSuccess { logger.info("Session removed: $sessionId") } .doOnError { error -> logger.error("Error removing session: $sessionId", error) } }
额外检查点
- 确认
ReactiveRedisIndexedSessionRepository的命名空间配置正确,删除操作针对Redis中实际存储的键前缀(默认spring:session:)。 - 检查Redis操作日志,排除权限不足、连接超时等导致删除失败的外部因素。
- 验证
getSessionInformation方法是否正确获取RedisSession实例,避免因会话对象错误导致操作无效。
内容的提问来源于stack exchange,提问作者Sachin
相关产品推荐
相关产品推荐

