Search code examples
javaspring-bootspring-securitykeycloakspring-boot-actuator

spring boot actuator endpoints with Keycloak security


we have a spring boot project (2.3.0.RELEASE) with actuator endpoints and we are introducing keycloak to the project with KeycloakWebSecurityConfigurerAdapter how can I prevent actuator endpoints being secured by the keycloak filter chain.

We would like to have the "/actuator/**" endpoints secured by basic auth.

Currently we have a custom WebSecurityConfigurerAdapter with @Order(1) where we apply the basic auth to "/actuator/**" and then we have with @Order(2) antotated the KeycloakWebSecurityConfigurerAdapter

so 2 filter chains gets registered and when I call the actuator endpoints the second filter chain fails as unauthorised 401

is it possible to prevent handling the "/actuator/**" resorce path on the second filter chain?

First actuator security configuration.

@Configuration
@Order(1)
public class ActuatorWebSecurityConfig extends WebSecurityConfigurerAdapter {

    private final String username;
    private final String password;
    private final PasswordEncoder encoder;

    public ActuatorWebSecurityConfig(
            @Value("${spring.security.user.name}") String username,
            @Value("${spring.security.user.password}") String password,
            Optional<PasswordEncoder> encoder) {
        this.username = username;
        this.password = password;
        this.encoder = encoder.orElseGet(PasswordEncoderFactories::createDelegatingPasswordEncoder);
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser(username)
                .password(encoder.encode(password))
                .roles("USER");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .antMatcher("/actuator/**")
                .authorizeRequests(authorize -> authorize.anyRequest().authenticated())
                .httpBasic(Customizer.withDefaults());
    }
}

second keycloak securoty configuration

@Order(2)
@KeycloakConfiguration
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {

    private final String swaggerUrl;
    private final CorsFilter corsFilter;
    private final CustomSecurityConfig customSecurityConfig;

    @Autowired
    public SecurityConfig(
            @Value("${springdoc.swagger-ui.url:#{null}}") String swaggerUrl,
            CorsFilter corsFilter,
            CustomSecurityConfig customSecurityConfig) {
        this.swaggerUrl = swaggerUrl;
        this.corsFilter = corsFilter;
        this.customSecurityConfig = customSecurityConfig;
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        KeycloakAuthenticationProvider keycloakProvider = keycloakAuthenticationProvider();
        keycloakProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
        auth.authenticationProvider(keycloakProvider);
    }

    @Bean
    @Override
    protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
        return new NullAuthenticatedSessionStrategy();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        super.configure(http);
        http.csrf().disable()
            .requestMatcher(new NegatedRequestMatcher(new AntPathRequestMatcher("/actuator/**")));
            .headers().frameOptions().disable()
        .and()
           .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        .and()
        .authorizeRequests()
           .antMatchers("/public/**", "/resources/**", "/resources/public/**").permitAll()
           .antMatchers(OPTIONS, "/**").permitAll();
        .authorizeRequests()
           .antMatchers("/**")
           .authenticated();
    }
}

I have tried with on keycloak config

.antMatchers("/actuator/**").permitAll();

and with

http.requestMatcher(new NegatedRequestMatcher(new AntPathRequestMatcher("/actuator/**")));

but nothing works I receive unauthorised 401 for actuator

the registered filter chains :

2022-01-18 17:38:44,688 INFO org.springframework.security.web.DefaultSecurityFilterChain [main] Creating filter chain: Ant [pattern='/actuator/**'], [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@25c6a9de, org.springframework.security.web.context.SecurityContextPersistenceFilter@56f3f9da, org.springframework.security.web.header.HeaderWriterFilter@33dcbdc2, org.springframework.security.web.csrf.CsrfFilter@522fdf0c, org.springframework.security.web.authentication.logout.LogoutFilter@365ad794, org.springframework.security.web.authentication.www.BasicAuthenticationFilter@23df16cf, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@227cba85, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@b38dc7d, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@142422a4, org.springframework.security.web.session.SessionManagementFilter@2f0b7b6d, org.springframework.security.web.access.ExceptionTranslationFilter@74bca236, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@30587737]
2022-01-18 17:38:44,691 INFO org.springframework.security.web.DefaultSecurityFilterChain [main] Creating filter chain: NegatedRequestMatcher [requestMatcher=Ant [pattern='/actuator/**']], [com.betex.auth.filters.CorsFilter@20a9f5fb, org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@10e28d97, org.springframework.security.web.context.SecurityContextPersistenceFilter@c6b08a5, org.springframework.security.web.header.HeaderWriterFilter@5f05cd7e, org.keycloak.adapters.springsecurity.filter.KeycloakPreAuthActionsFilter@2a54c92e, org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticationProcessingFilter@55b62db8, org.springframework.security.web.authentication.logout.LogoutFilter@274f51ad, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@54980154, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@25874884, org.keycloak.adapters.springsecurity.filter.KeycloakSecurityContextRequestFilter@8cb7185, org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticatedActionsFilter@4dac40b, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@37d43b9b, org.springframework.security.web.session.SessionManagementFilter@11e8e183, org.springframework.security.web.access.ExceptionTranslationFilter@56f1db5f, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@78543f0d]

Solution

  • When you extend KeycloakWebSecurityConfigurerAdapter, the adapter register a Bean of type KeycloakAuthenticationProcessingFilter. This filter is registered in the Spring Security's SecurityFilterChain, and because it's a Bean, it is also automatically registered by Spring Boot in the original chain, therefore even if Spring Security doesn't apply it, it will be applied later on in original the filter chain.

    Try disabling this filter from being registered by Spring Boot, like so:

    @Bean
    public FilterRegistrationBean registration(KeycloakAuthenticationProcessingFilter filter) {
        FilterRegistrationBean registration = new FilterRegistrationBean(filter);
        registration.setEnabled(false);
        return registration;
    }
    

    In addition, if you are using OAuth 2, you may consider using spring-security-oauth2-resource-server and simplifying your Resource Server's configuration. Take a look at the documentation. This way you don't need to extend the custom adapter, just rely on the out-of-the-box configuration from Spring Security.