Keycloak + Spring Security: Unpacking the Filter Chain
This article examines how Keycloak integrates with Spring Security in a Spring Boot application, detailing the custom security filters added to the standard chain, the handling of authentication and authorization for the /admin/foo endpoint, and practical steps to enable detailed logging for deeper insight.
In the previous article we familiarized with common Keycloak configuration; now we analyze the execution flow of Keycloak adapting Spring Security, focusing on the custom Spring Security filters.
/admin/foo execution flow
In a Spring Boot application that adapts Keycloak and Spring Security, we defined an endpoint /admin/foo and configured its access:
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http
.authorizeRequests()
.antMatchers("/customers*").hasRole("USER")
.antMatchers("/admin/**").hasRole("base_user")
.anyRequest().permitAll();
}This is a typical Spring Security configuration granting the role base_user access to /admin/**. The user and role management are handled by the Keycloak platform, while the application controls resource access policies.
When accessing /admin/foo with logging enabled, the following filter chain is observed:
Security filter chain: [
WebAsyncManagerIntegrationFilter
SecurityContextPersistenceFilter
HeaderWriterFilter
CsrfFilter
KeycloakPreAuthActionsFilter
KeycloakAuthenticationProcessingFilter
LogoutFilter
RequestCacheAwareFilter
SecurityContextHolderAwareRequestFilter
KeycloakSecurityContextRequestFilter
KeycloakAuthenticatedActionsFilter
AnonymousAuthenticationFilter
SessionManagementFilter
ExceptionTranslationFilter
FilterSecurityInterceptor
]Besides the standard Spring Security filters, several Keycloak adapter filters are added:
KeycloakPreAuthActionsFilter
This filter exposes a PreAuthActionsHandler to Spring Security, handling Keycloak-specific pre‑authentication actions such as logout, token refresh, and JWKS retrieval.
Generally you can ignore this filter if you are not diving into low‑level details.
KeycloakAuthenticationEntryPoint
KeycloakAuthenticationEntryPoint implements AuthenticationEntryPoint and is configured in KeycloakWebSecurityConfigurerAdapter . When an anonymous user attempts to access /admin/foo , FilterSecurityInterceptor throws an AccessDeniedException , which is caught by ExceptionTranslationFilter and delegated to this entry point, resulting in a 401 response.
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
HttpFacade facade = new SimpleHttpFacade(request, response);
if (apiRequestMatcher.matches(request) || adapterDeploymentContext.resolveDeployment(facade).isBearerOnly()) {
commenceUnauthorizedResponse(request, response);
} else {
commenceLoginRedirect(request, response);
}
}The entry point either returns a 401 with a WWW-Authenticate header for login requests or redirects to the OIDC login page.
KeycloakAuthenticationProcessingFilter
This filter processes the authorization code flow. After the user logs in and grants consent, the authorization server redirects to /sso/login with code and state. The filter intercepts this request, handling the authorization header, access token, or Keycloak cookie.
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
// implementation omitted for brevity
}KeycloakSecurityContextRequestFilter
It checks whether the request carries a RefreshableKeycloakSecurityContext. If present, it stores this context in the ServletRequest for downstream filters.
KeycloakAuthenticatedActionsFilter
This filter acts on the security context placed by the previous filter, deciding whether the request is already authorized based on Keycloak policies.
public boolean handledRequest() {
if (corsRequest()) return true;
String requestUri = facade.getRequest().getURI();
if (requestUri.endsWith(AdapterConstants.K_QUERY_BEARER_TOKEN)) {
queryBearerToken();
return true;
}
if (!isAuthorized()) {
return true;
}
return false;
}Returning true stops further processing when the request is deemed handled.
Supplement
To explore any framework’s runtime flow, enable detailed logging. For the Keycloak Spring Security adapter, add the following configuration to see the filter chain execution:
@Configuration
@ComponentScan(basePackageClasses = {KeycloakSecurityComponents.class})
@EnableWebSecurity(debug = true)
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
// ignore configuration
}Also set Spring Boot logging to debug for the org packages:
logging:
level:
org: debugWith these settings, the console will clearly show the Keycloak processing steps, making it easier to understand and customize authentication and authorization behavior.
Programmer DD
A tinkering programmer and author of "Spring Cloud Microservices in Action"
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
