Search code examples
javaspring-bootspring-securityazure-java-sdkspring-cloud-azure

AccessDeniedHandler not called when using AadResourceServerHttpSecurityConfigurer


My application is a simple resource server — I am using AadResourceServerHttpSecurityConfigurer.aadResourceServer() to validate the given access token. The specific documentation I have followed can be found here.

Goal: Return a custom error message when a malformed token (e.g., '123') is given. It does not seem that my accessDeniedHandler is being called. In contrast, when I don't specify a JWT at all, it works fine and a custom error message is returned (authenticationEntryPoint is called).

This is the code I am using (I have modified Microsoft's example as it was using deprecated methods):

@Autowired
private ErrorHandler errorHandler;

@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
    .csrfCustomizer -> csrfCustomizer.disable())
    .with(AadResourceServerHttpSecurityConfigurer
        .aadResourceServer(), Customizer.withDefaults())
    .authorizeHttpRequests(requests -> requests
        .requestsMatchers(antMatcher("/users/**")).permitAll()
        .requestMatchers(antMatcher(HttpMethod.GET, "/admin")).hasRole(ADMIN_ROLE)    
        .anyRequest().authenticated()
    )
    .exceptionHandling(exceptionHandlingCustomizer -> exceptionHandlingCustomizer.authenticationEntryPoint(errorHandler))
    .exceptionHandling(exceptionHandlingCustomizer -> exceptionHandlingCustomizer.accessDeniedHandler(errorHandler))
    .sessionManagement(sessionManagement -> sessionManagementCustomizer.sessionCreationPolicy(sessionCreationPolicy.STATELESS)));

    return http.build();
}

The specific error handler (simplified):

@Configuration
public class ErrorHandler authenticationEntryPoint, AccessDeniedHandler {
    @Override
    public void commence() {
        // this is called
    }

    @Override
    public void handle() {
        // this is not called
    }
}

In contrast, this configuration works fine and a custom error message is returned when I provide a malformed token, but I'm not utilising AadResourceServerHttpSecurityConfigurer.aadResourceServer():

@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
    .csrfCustomizer -> csrfCustomizer.disable())
    .oauth2ResourceServer(httpSecurityOAuth2ResourceServerConfigurer -> httpSecurityOAuth2ResourceServerConfigurer
        .authenticationEntryPoint(errorHandler)
        .accessDeniedHandler(errorHandler)
    )
    .authorizeHttpRequests(requests -> requests
        .requestsMatchers(antMatcher("/users/**")).permitAll()
        .requestMatchers(antMatcher(HttpMethod.GET, "/admin")).hasRole(ADMIN_ROLE)    
        .anyRequest().authenticated()
    )
    .sessionManagement(sessionManagement -> sessionManagementCustomizer.sessionCreationPolicy(sessionCreationPolicy.STATELESS)));

    return http.build();
}

Is there a way to utilise AadResourceServerHttpSecurityConfigurer.aadResourceServer() with both the authenticationEntryPoint and accessDeniedHandler exception handlers?


Solution

  • but I'm not utilising AadResourceServerHttpSecurityConfigurer.aadResourceServer()

    I would not set using AadResourceServerHttpSecurityConfigurer as a requirement.

    Microsoft proprietary Boot starters are randomly maintained and documented. It sometimes took a while before they published versions compatible with new Spring Security versions, and it's not always quite clear what versions a documentation page / starter lib is compatible with. For this reasons, I prefer to use just Spring Boot official starters (spring-boot-stater-client or spring-boot-stater-resource-server), optionaly with an additional starter of mine which is compatible with any OIDC authorization server (AAD, as well as Keycloak, Auth0, Amazon Cognito,...)

    So, your second conf is a better starting point. "My" Boot starter can help replace Java security conf with just application properties (and implement rather tricky features like cookie-based CSRF protection, authorities mapping, multi-tenancy, changing authorization code flow redirection statuses, and more).

    P.S.

    401 is what a resource server should return in case of a missing or invalid token (expired, wrong issuer or audience, etc.). So ensure that you change no more than the error message.