Spring Session / Security - Redis Sessions not being properly deleted
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?


