spring-projects / spring-projects/spring-session
Spring Session / Security - Redis Sessions not being properly deleted
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 1.9k
- Forks
- 1.2k
- Avg merge
- 4h 27m
- Merged PRs (30d)
- 55
Description
Hi,
I have a Spring OAuth Client (BFF), between a Public Angular Client, and an Auth0 Authorization server. When I login, the BFF correctly persists the session to Redis (and with it, the Authorized Client, Security Context, and Authorized Request as attributes in the session)
When I logout though, only the contents of the Session get deleted, the key itself does not. Also nothing in the Sorted Set, ever gets deleted. I am positing here, as it might be a genuine bug.
Logout Handler
Here is my Logout Handler
@Component
internal class SessionServerLogoutHandler(
private val sessionControl: SessionControl,
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}")
val response = exchange.exchange.response
sessionControl.invalidateSession(session)
.then(Mono.fromRunnable {
logger.info("Deleting Session Cookie: ${sessionProperties.SESSION_COOKIE_NAME}")
// delete the session cookie
val sessionCookie = ResponseCookie.from(sessionProperties.SESSION_COOKIE_NAME)
sessionCookie.maxAge(0)
sessionCookie.httpOnly(sessionProperties.SESSION_COOKIE_HTTP_ONLY)
sessionCookie.secure(sessionProperties.SESSION_COOKIE_SECURE)
sessionCookie.sameSite(sessionProperties.SESSION_COOKIE_SAME_SITE)
sessionCookie.path(sessionProperties.SESSION_COOKIE_PATH)
sessionCookie.domain(sessionProperties.SESSION_COOKIE_DOMAIN)
.build()
response.headers.add(
HttpHeaders.SET_COOKIE,
sessionCookie.toString()
)
logger.info("Deleting Session Cookie: ${csrfProperties.CSRF_COOKIE_NAME}")
// delete the CSRF cookie
val csrfCookie = ResponseCookie.from(csrfProperties.CSRF_COOKIE_NAME)
csrfCookie.maxAge(0)
csrfCookie.httpOnly(csrfProperties.CSRF_COOKIE_HTTP_ONLY)
csrfCookie.secure(csrfProperties.CSRF_COOKIE_SECURE)
csrfCookie.sameSite(csrfProperties.CSRF_COOKIE_SAME_SITE)
csrfCookie.path(csrfProperties.CSRF_COOKIE_PATH)
csrfCookie.domain(csrfProperties.CSRF_COOKIE_DOMAIN)
.build()
response.headers.add(
HttpHeaders.SET_COOKIE,
csrfCookie.toString()
)
})
}
}
}
SessionControl
It calls another call called SessionControl, and the invalidate session method. Here is that function
fun invalidateSession(session: WebSession): Mono<Void> {
val sessionInformation = getSessionInformation(session)
logger.info("Invalidating sessionId: ${sessionInformation.sessionId}")
// handle the session invalidation process
return sessionInformation.invalidate()
.then(Mono.defer {
webSessionStore.removeSession(sessionInformation.sessionId)
})
.doOnSuccess {
logger.info("Session invalidated and removed: ${sessionInformation.sessionId}")
}
.doOnError { error ->
logger.error("Error invalidating session: ${sessionInformation.sessionId}", error)
}
}
WebSessionStore
That inturn calls Websession Store, and it's removeSession method.
Here is the bean and the function:
@Bean(name = ["webSessionStore"])
fun webSessionStore(
reactiveRedisIndexedSessionRepository: ReactiveRedisIndexedSessionRepository
): SpringSessionWebSessionStore<RedisSession> {
return SpringSessionWebSessionStore(reactiveRedisIndexedSessionRepository)
}
@Override
public Mono<Void> removeSession(String sessionId) {
return this.sessions.deleteById(sessionId);
}
reactiveRedisIndexedSessionRepository
The this.sessions above refers to the ReactiveRedisIndexedSessionRepository that was passed in the constructor of the WebsessionStore. Looking at the internals of the Spring ReactiveRedisIndexedSessionRepository I see this:
public Mono<Void> deleteById(String id) {
return deleteAndReturn(id).then();
}
private Mono<RedisSession> deleteAndReturn(String id) {
// @formatter:off
return getSession(id, true)
.flatMap((session) -> this.sessionRedisOperations.delete(getExpiredKey(session.getId()))
.thenReturn(session))
.flatMap((session) -> this.sessionRedisOperations.delete(getSessionKey(session.getId())).thenReturn(session))
.flatMap((session) -> this.indexer.delete(session.getId()).thenReturn(session))
.flatMap((session) -> this.expirationStore.remove(session.getId()).thenReturn(session));
// @formatter:on
}
Session in Redis before
As you can see before I logout, the session is there in Redis.
Session in Redis after
After I call the logout handler, something has definitely happened, but the session is still there with its key, just no map of values apart from a single lastaccessed map key / value.
Further more nothing ever gets deleted from the SortedSet, which according to the 4th step in the deleteAndReturn method above, it should...
So, can someone help me understand where I may have gone wrong in my code?
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the custom SessionServerLogoutHandler and SessionControl.invalidateSession flow, then trace WebSessionStore.removeSession into ReactiveRedisIndexedSessionRepository.deleteById and deleteAndReturn. Reproduce the logout with the Redis session key and sorted-set entries present, and verify whether both are removed after completion.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java, kotlin, redis, spring
- Domain
- backend, databases, security
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 42/100


