-
Notifications
You must be signed in to change notification settings - Fork 6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Refreshed Keycloak tokens are not saved in the session when using Spring-Session-Redis #9462
Comments
Thanks for the report. Unfortunately we cannot always save. For now, I'd encourage Keycloak to create a new SecurityContext as operating on the same SecurityContext can cause race conditions. While the ThreadLocal provides isolation to a single thread, in memory session environments refer to the same SecurityContext on every Thread that is accessing it. See https://docs.spring.io/spring-security/site/docs/5.4.x/reference/html5/#servlet-authentication-securitycontextholder
In the long term this will be fixed by #9634 |
Closing as invalid since users should not change the SecurityContext directly but instead should create a new SecurityContext. |
Posting my hacky workaround in case it helps others. I tried to find a way to override some small piece in Keycloak to do it "the right way", but I couldn't figure it out. It seemed to involve overriding entire classes. So what I ended up doing was putting a custom filter right before KeycloakSecurityContextRequestFilter:
This basically compares the access token before and after. If it changed, then it creates a new context and uses the existing authentication instance (that Keycloak modified in-place). I left in a commented line with RequestContextHolder, which is what I was doing prior to reading this issue. I'm not sure that is any better/worse that what I'm doing now. Definitely not ideal, but it seems to work for now. |
Describe the bug
When using Spring-Session-Redis and Keycloak, the token data is correctly saved to the session after logging in, but subsequently refreshed tokens are not stored.
When the SSO Token time out has been reached, the next refresh will cause a new token to be generated using the refresh token. However the new token is not saved in the Session - so the next request will again cause a new refresh and so on. Even though we continuously issue requests, the Keycloak auth will be terminated when the SSO Idle Timeout is reached - because the refresh token has not been updated either and therefore is now invalid too.
This currently causes us severe issues in our production environment as users always lose their authentication when the SSO Idle Timeout is reached - even though they are actively using the software.
To Reproduce
When using Spring-Session-Redis and Keycloak, configure Keycloak tokens with a short lifespan like
Then Login and issue some requests. The first Minute everything is just normal. After one minute the Access token has reached its ttl and needs to be refreshed. That happens in the background for every following request. After 5 Minutes the refresh token reached its ttl and the refresh fails.
Expected behavior
The refreshed Keycloak token data should automatically be saved using the
HttpSessionSecurityContextRepository
.Dependency Versions
Further Information
I spend a lot of time with this issue because I assumed the error might be with our configuration. But I think I found the root cause of this issue in the
HttpSessionSecurityContextRepository
.The
RefreshableKeycloakSecurityContext
that manages the refresh logic updates the token information in place, mutating its own field values. See: https://github.com/keycloak/keycloak/blob/80bf0b6bad8bb8546db742f67736fdca2c743217/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/RefreshableKeycloakSecurityContext.java#L173 and the following lines.The
RefreshableKeycloakSecurityContext
instance is saved in theprincipal
Field ofSecurityContextHolder.getContext().getAuthentication()
-Now, when the response is processed by the
SecurityContextPersistenceFilter
it callsSecurityContextRepository#saveContext
. Seespring-security/web/src/main/java/org/springframework/security/web/context/SecurityContextPersistenceFilter.java
Line 116 in 95da121
The
repository
here is an instance ofHttpSessionSecurityContextRepository
which internally defers some logic into itsSaveToSessionRequestWrapper
. This wrapper performs a check if the context has been changed to determine if it should be saved or not. Seespring-security/web/src/main/java/org/springframework/security/web/context/HttpSessionSecurityContextRepository.java
Line 357 in 95da121
And here is the actual cause:
spring-security/web/src/main/java/org/springframework/security/web/context/HttpSessionSecurityContextRepository.java
Lines 368 to 369 in 95da121
The
contextChanged
Method now checkscontext != this.contextBeforeExecution || context.getAuthentication() != this.authBeforeExecution
butthis.authBeforeExecution
is the same object ascontext.getAuthentication
but it has been mutated. This causes this check to evaluate tofalse
even though the context acutally changed. Therefore, the changed context is not saved (so in our case updated in redis) and the next request uses the old tokens again.This issue does not appear in the tomcat session, because an explicit save action is not required.
I have overridden the
contextChanged
method to alwaysreturn true
- this completly fixed the issue for me.Im sorry if this report seems a bit convoluted/unorganized - debugging this was pretty exhausting. Feel free to edit / reformat this.
The text was updated successfully, but these errors were encountered: