0

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?

2 Answers 2

1

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.

Sign up to request clarification or add additional context in comments.

4 Comments

Thank you so much for your thorough answer! I agree with the 401 — I just want to customize the response body which I have difficulty doing at this moment using the first configuration. Any idea how I can do this using the first example if possible at all?
Also, you're right as I see that internally AadResourceServerHttpSecurityConfigurer is using oauth2ResourceServer().jwt(). I suppose my main concern is the "magic" that goes behind all of this. In my second configuration, I have absolutely no idea how this jwt token is validated to be honest. I have bookmarked your starter. How did you know about the starters being randomly maintained and documented?! Frankly, your comment about using the second conf as starting point relieved me a bit!
It is the different issues I had with the various proprietary starters (all doing about the same thing in the end: configuring Spring Security for OpenID) that decided me to write my own.
1

For the security have you missed ?

@EnableWebSecurity
@EnableMethodSecurity

On your configuration class I've found this 'tuto' https://gpiskas.com/posts/refresh-oauth2-tokens-spring-boot-spring-cloud-azure/ and both yours and this one uses these annotations.

For the exception part you can use a more global exception handler

@ControllerAdvice
@ResponseBody
class CustomExceptionHandler(){

    @ExceptionHandler
    @ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY)
    CustomErrorObj customExceptionHandling(e: CustomException) {
        return new CustomErrorObj(e.class.getName()+": "+e.getMessage())
    }

}

1 Comment

Thank you for your help. The article you've referenced is something I had indeed also found, but unfortunately didn't help as it still didn't trigger my access denied handler. I have tried your ControllerAdvice example (not in this way but trying to handle the AccessDeniedException itself). This didn't work — and I believe that your example will catch all sorts of exceptions — this is something I haven't tried yet. Will get back to you on this one asap!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.