KeyCloak — Choosing the right Authentication Strategy
Keycloak is a widely used open-source identity and access management solution that provides secure authentication capabilities with various authentication mechanisms which includes
- Public: Permits authentication, without a client secret.
- Confidential: Permits authentication, with a client secret.
- Bearer only: You need an access token to access its resources.
So, based on these clients' authentication methods we can divide the application into two parts from an authentication point of view
- Web Browser-Based Login Authentication: For such an application, the user login to the browser and is authenticated using the public and confidential client of the keycloak
- Bearer Token Based Authentication: This is required for service and service communication and Service A sends the bearer token to Service B , Service B then validates the token for its authenticity.
Spring Security based on above authentication methods provides different Session Registration Strategies
- RegisterSessionAuthenticationStrategy: As per Keycloack official documentation it is required to provide a session authentication strategy bean which should be of type
RegisterSessionAuthenticationStrategy
for public or confidential applications stated in https://github.com/keycloak/keycloak-documentation/blob/master/securing_apps/topics/oidc/java/spring-security-adapter.adoc, if we look closely into the implementation of this session strategy, it stores a new session for every request received to it.
public class RegisterSessionAuthenticationStrategy implements SessionAuthenticationStrategy {
private final SessionRegistry sessionRegistry;
public RegisterSessionAuthenticationStrategy(SessionRegistry sessionRegistry) {
Assert.notNull(sessionRegistry, "The sessionRegistry cannot be null");
this.sessionRegistry = sessionRegistry;
}
public void onAuthentication(Authentication authentication, HttpServletRequest request, HttpServletResponse response) {
this.sessionRegistry.registerNewSession(request.getSession().getId(), authentication.getPrincipal());
}
}
- NullAuthenticatedSessionStrategy: This Strategy should be used for bearer applications as per keycloak official documentation as this does not register a session for an individual request.
Choosing the right authentication strategy depending upon the client authentication mechanism is very important as it may lead to sleepless nights if wrongly configured
How it went wrong for us
In our case for one of the services PushDPS, we are using the Bearer token-based authentication flow where the client has to pass the access token as a bearer token while calling the authenticated APIs. As mentioned above for bearer token-based authentication we should be using NullSessionAuthenticationStrategy
while declaring the session strategy bean rather we were using RegisterSessionAuthenticationStrategy which saves session token In-Memory of the respective application.
Problem Statement — Memory leaks in PushDPS leading to OOM under high traffic.
We identified that during high traffic we have memory leaks in Push DPS, Push DPS is a bearer. token-based authentication application where clients send a token generated by keylock, which gets validated at Push DPS using Keycloack Spring boot security adaptor and after successful validation request is routed to the respective Push DPS end point.
How we reached the cause with the memory dump
By taking memory dumps during the high traffic hours, we figured out that 67.5% of memory is being accumulated within the Concurrent HashMap consumed by Keycloak SessionRegistory.
of "org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy"loaded by "org.springframework.boot.loader.LaunchedURLClassLoader @ 0x7048d7488" occupies 50,91,65,872 (66.29%) bytes. The memory is accumulated in one instance of "java.util.concurrent.ConcurrentHashMap$Node[]" loaded by "<system class loader>".
Keywords
org.springframework.boot.loader.LaunchedURLClassLoader @ 0x7048d7488
java.util.concurrent.ConcurrentHashMap$Node[]
org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy
More Insights
To further dig down we performed a load test with 300 users, a ramp-up time of 50 sec and running in the loop for 20 minutes
Memory & CPU Utilisation Graphs with Different Authentication Strategy
- Memory usage trend with RegisterSessionAuthenticationStrategy
2. CPU usage trend with RegisterSessionAuthenticationStrategy
3. Memory usage trend with NullAuthenticatedSessionStrategy
4. CPU usage trend with NullAuthenticatedSessionStrategy
Conclusion
- Memory usage with RegisterSessionAuthenticationStrategy is pretty high i.e up to 3.5GB and is occupied further after jmeter script is finished whereas for NullAuthenticatedSessionStrategy max memory usage during the script run was up to 1.5 GB and post script run memory came to its normal state i.e 200 MB
- CPU Utilisation with RegisterSessionAuthenticationStrategy has reached up to 85% whereas with NullAuthenticatedSessionStrategy it
reached up to 25–30%
Doubts Raised & Clarifications
Question:
Since after migration to NullAuthenticatedSessionStrategy from RegisterSessionAuthenticationStrategy, we won’t be storing session tokens and then for every incoming request the application has to introspect token and this will increase traffic on Keycloak
Answer:
A bearer token-based authentication application does not call Keycloak for every incoming request it validates the token with the public key stored as part of the Keycloak spring adaptor injected as a maven dependency in the application
- For bearer flow: token validation is done using the public key
- Keycloack spring adaptor which is part of the application stores a public key and a kid in an in-memory cache for a time of 1 day (86400 sec) which is configurable by public-key-cache-ttl
as per keycloak documentation https://www.keycloak.org/docs/4.8/securing_apps/
public-key-cache-ttl
Amount of time, in seconds, specifying maximum interval between two requests to Keycloak to retrieve new public keys. It is 86400 seconds (1 day) by default. Adapter will always try to download new public key when it recognize token with unknown kid . If it recognize token with known kid, it will just use the public key downloaded previously. However at least once per this configured interval (1 day by default) will be new public key always downloaded even if the kid of token is already known.
So to conclude this further, there are two validation flows involved for token authenticity
- Microservice to Keycloack (Online): Where the token is validated using keycloak introspect endpoint and a request is sent to Keycloak to validate the token, this is costly as it involves a round-trip
- Microservice validating token from Cache (Offline): Token is validated using the PublicKeyValidator which validates the token using the public key stored in the cache for a default time of 1 day
Reference Links: