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.

Programmer DD
Programmer DD
Programmer DD
Keycloak + Spring Security: Unpacking the Filter Chain

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: debug

With these settings, the console will clearly show the Keycloak processing steps, making it easier to understand and customize authentication and authorization behavior.

JavaAuthenticationSpring SecurityKeycloak
Programmer DD
Written by

Programmer DD

A tinkering programmer and author of "Spring Cloud Microservices in Action"

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.