Reputation: 507
Any suggestions on how to work around the below bug, or am I doing something fundamentally wrong? Whenever I do something like val contextAttr = session.getAttribute<Map<String, Any>>(springAttribute), the contextAttr definitely comes back as a LinkedHashedMap.. These objects are stored in Redis as attributes to the main session, which I believe is all in JSON / Map <String, Any?> format.
Describe the bug
When my Spring logout handler calls this
fun invalidateSession(sessionId: String): Mono<Void> {
logger.info("Invalidating sessionId: ${sessionId}")
// handle the session invalidation process
return reactiveSessionRegistry.getSessionInformation(sessionId)
.flatMap { session ->
// invalidate session
session.invalidate()
.then(
// delete session
webSessionStore.removeSession(sessionId)
)
.doOnSuccess {
logger.info("Session invalidated and removed: ${sessionId}")
}
.doOnError { error ->
logger.error("Error invalidating session: ${sessionId}", error)
}
}
}
The following function inside SpringSessionBackedReactiveSessionRegistry gets called:
@Override
public Mono<ReactiveSessionInformation> getSessionInformation(String sessionId) {
return this.sessionRepository.findById(sessionId).map(SpringSessionBackedReactiveSessionInformation::new);
}
The inner class is implemented as follows:
class SpringSessionBackedReactiveSessionInformation extends ReactiveSessionInformation {
SpringSessionBackedReactiveSessionInformation(S session) {
super(resolvePrincipalName(session), session.getId(), session.getLastAccessedTime());
}
private static String resolvePrincipalName(Session session) {
String principalName = session
.getAttribute(ReactiveFindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME);
if (principalName != null) {
return principalName;
}
SecurityContext securityContext = session.getAttribute(SPRING_SECURITY_CONTEXT);
if (securityContext != null && securityContext.getAuthentication() != null) {
return securityContext.getAuthentication().getName();
}
return "";
}
@Override
public Mono<Void> invalidate() {
return super.invalidate()
.then(Mono.defer(() -> SpringSessionBackedReactiveSessionRegistry.this.sessionRepository
.deleteById(getSessionId())));
}
}
But, here on this line:
SecurityContext securityContext = session.getAttribute(SPRING_SECURITY_CONTEXT);
SPRING_SECURITY_CONTEXT is received from Redis as a HashMap or LinkedHashMap, so cannot be cast to SecurityContext (w/o proper de-serialisation)
This is EXACTLY the error I see:
Two calls to get session?
Also, I'm not sure if this is calling Redis again to get the security context, but is it necessary?, given just before calling /logout endpoint, the session / security context is retrieved anyway, (see below.)
SessionId would come from the session, here, when this is called in the line just before fun invalidateSession(sessionId: String): Mono ) (to get the session ID), so calling getSessionInformation(String sessionId) and with it, this.sessionRepository.findById(sessionId), again, seems a bit wasteful...?
To Reproduce See above, just try the above, with sessions stored to redis, then try to invalidate a session calling the above functions
Expected behavior The casting should be properly deserialised. A linkedHashmap cannot be cast to a SecurityContext object directly
Sample
See above. Github code can be found here:
My implementations https://github.com/dreamstar-enterprises/docs/blob/master/Spring%20BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/sessions/SessionRegistryConfig.kt
Spring implementation (where error is I believe) https://github.com/spring-projects/spring-session/blob/main/spring-session-core/src/main/java/org/springframework/session/security/SpringSessionBackedReactiveSessionRegistry.java
Appreciate any help or suggestions given!
Upvotes: 1
Views: 63