It looks like when using Keycloak and Spring Security with the OIDC Client protocol there
is a way to hose the application session unintentionally when the Keycloak SSO session
timeout setting is lower than the application (ie. Client) session timeout value.
If the user accesses any parts of the application which are protected by the Keycloak
adapter after the access token has expired (configured for 5 minutes) without first ending
the application session, Spring Security still has the authentication object. But as part
of the authentication flow in the application, the Keycloak adapter checks to see if the
Access token is active which it won't be at this point. So the Keycloak adapter
(RefreshableKeycloakSecurityContext.java) attempts to get a new Access token using the
refresh token it has. Since the refresh token has been invalidated in Keycloak the adapter
receives a "Stale refresh token" error response from Keycloak. The "no
access token" is propagated to the Keycloak adapter's
OAuthRequestAuthenticator.java which proceeds to trigger a login redirect to Keycloak.
Once the user is successfully authenticated in Keycloak and control is returned to
the KeycloakAuthenticationProcessingFilter.java as a final step it attempts to store the
KeycloakAuthenticationToken in the Spring SecurityContextHolder (see
SpringSecurityTokenStore.saveAccountInfo). Here the code throws an exception because there
is already an existing KeycloakAuthenticationToken in the SecurityContextHolder from the
earlier login that wasn't cleared.
At this point SSO login into the application is hosed. A potential fix is to trigger a
call to the application’s logout endpoint which will clear the Spring
SecurityContextHolder object prior to fetching a new access token.
I was wondering if anyone has run into this behavior. It seems like when using the OIDC
Client protocol by it’s very nature of using short lived Access tokens and Refresh tokens
that are tied to the Keycloak session we will have to set the Keycloak Session timeout to
be the same or higher than the Client session timeout. But we do not necessarily have
control over the clients. So we will have to set the Keycloak session timeout to the
highest session timeout across all Clients since this is realm level setting and not a
per Client setting. But this breaks another use case since we are using Identity
Brokering. We want the user to be bounced to the external Identity Provider when their
application session timeouts. If the Keycloak session timeout is higher than their
application session timeout then they wouldn’t be bounced to the external
Identity Provider for authentication. Looks like we might need to force Keycloak to
initiate the authentication when we detect an application timeout.
-sud