ch4mpy / spring-addons

Ease spring OAuth2 resource-servers configuration and testing
Apache License 2.0
521 stars 84 forks source link

Support for override AuthenticationEntryPoint #152

Closed nexus061 closed 9 months ago

nexus061 commented 9 months ago

I have added possibility to ovveride AuthenticationEntryPoint for excpetion handing and and oauth2ResourceServer

nexus061 commented 9 months ago

Hi, I think so:

we have 3 cases:

AccessDeniedHandler -> responds to the FORBIDDEN 403 case

Customizer<ExceptionHandlingConfigurer> -> intercepts the missing token

authenticationEntryPoint at the BearerTokenAuthenticationFilter level intercepts an invalid token

I would let you customize the configuration for all three. With the default set to what it uses from the factory Oaut2, I try to jot down this implementation

ch4mpy commented 9 months ago

As a side note, 401 is the expected status for both missing and invalid tokens.

Actually, AccessDeniedHandler can respond more than 403: if you have a look at the reactive implementation, it is slightly different from the servlet one and uses the ServerAccessDeniedHandler instead of the authenticationEntryPoint to return 401 in case of missing or invalid token and 403 in case of access rule violation:

response.setStatusCode(principal instanceof AnonymousAuthenticationToken ? HttpStatus.UNAUTHORIZED : HttpStatus.FORBIDDEN);

This means that you can process missing tokens with both authenticationEntryPoint and accessDeniedHandler, even if the first should be preferred as it is made to process that case => the reactive implementation should be changed to (I will do after this PR for servlet is merged):

I'm not sure Customizer<ExceptionHandlingConfigurer> is designed specifically to intercept the missing token. I think it is made to configure things like authenticationEntryPoint and accessDeniedHandler, as you do in the resourceServer configurer, but for the all SecurityFilterChain. As we have only Bearer tokens security in the filter-chain we are configuring, doing it as you do in the resourceServer or doing it in the exceptionHandling is the same and we don't need to do both.

I suggest that we choose between one of the two:

@ConditionalOnMissingBean @Bean Customizer<ExceptionHandlingConfigurer> exceptionHandlingCustomizer( AuthenticationEntryPoint authenticationEntryPoint, Optional accessDeniedHandler) { return exceptionHandling -> { exceptionHandling.authenticationEntryPoint(authenticationEntryPoint); accessDeniedHandler.ifPresent(exceptionHandling::accessDeniedHandler); }; }

@Conditional(IsJwtDecoderResourceServerCondition.class) @Order(Ordered.LOWEST_PRECEDENCE) @Bean SecurityFilterChain springAddonsJwtResourceServerSecurityFilterChain( HttpSecurity http, ServerProperties serverProperties, SpringAddonsOidcProperties addonsProperties, ResourceServerExpressionInterceptUrlRegistryPostProcessor authorizePostProcessor, ResourceServerHttpSecurityPostProcessor httpPostProcessor, AuthenticationManagerResolver authenticationManagerResolver, Customizer<ExceptionHandlingConfigurer> exceptionHandlingCustomizer) throws Exception { http.oauth2ResourceServer(server -> { server.authenticationManagerResolver(authenticationManagerResolver); });

http.exceptionHandling(exceptionHandlingCustomizer);

ServletConfigurationSupport
        .configureResourceServer(http, serverProperties, addonsProperties.getResourceserver(), authorizePostProcessor, httpPostProcessor);

return http.build();

}

- inject both `authenticationEntryPoint` and `accessDeniedHandler` in the resource server filter-chains, keeping the default `exceptionHandling` (remove `Customizer<ExceptionHandlingConfigurer>` from what is injected into the resource server filter-chains), and configure `authenticationEntryPoint` and `accessDeniedHandler` inside `resourceServer` configurer
```java
@ConditionalOnMissingBean
@Bean
AuthenticationEntryPoint authenticationEntryPoint() {
    return (request, response, authException) -> {
        response.addHeader(HttpHeaders.WWW_AUTHENTICATE, "Bearer realm=\"Restricted Content\"");
        response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase());
    };
}

@Conditional(IsJwtDecoderResourceServerCondition.class)
@Order(Ordered.LOWEST_PRECEDENCE)
@Bean
SecurityFilterChain springAddonsJwtResourceServerSecurityFilterChain(
        HttpSecurity http,
        ServerProperties serverProperties,
        SpringAddonsOidcProperties addonsProperties,
        ResourceServerExpressionInterceptUrlRegistryPostProcessor authorizePostProcessor,
        ResourceServerHttpSecurityPostProcessor httpPostProcessor,
        AuthenticationManagerResolver<HttpServletRequest> authenticationManagerResolver,
        AuthenticationEntryPoint authenticationEntryPoint,
        Optional<AccessDeniedHandler> accessDeniedHandler)
        throws Exception {
    http.oauth2ResourceServer(server -> {
        server.authenticationManagerResolver(authenticationManagerResolver);
        server.authenticationEntryPoint(authenticationEntryPoint);
        accessDeniedHandler.ifPresent(server::accessDeniedHandler); 
    });

    ServletConfigurationSupport
            .configureResourceServer(http, serverProperties, addonsProperties.getResourceserver(), authorizePostProcessor, httpPostProcessor);

    return http.build();
}
nexus061 commented 9 months ago

I think the second one is better, I implement it and push it ​

nexus061 commented 9 months ago

I think the second one is better, I implement it and push it​

@ch4mpy push done

ch4mpy commented 9 months ago

I have already pushed the same feature for reactive applications. Will release with a bump to Spring Boot 3.1.5 as transient dependency.

nexus061 commented 9 months ago

I hope to be able to help again in the future!

ch4mpy commented 9 months ago

Don not hesitate to open more tickets or PRs.

I don't know why the 7.1.11 is not available yet from https://repo1.maven.org/maven2/com/c4-soft/springaddons/spring-addons-starter-oidc/ (the maven release to maven central ended successfully).

If it isn't there tomorrow morning, I'll publish a new version with the same content.