Search code examples
springauthenticationfluxbearer-token

Webflux with different authentication schemes


I just got a project I need to maintain and I need to add support for an extra authentication scheme in a resource server. Something like besides regular Authentication: Bearer <jwt.token> to use a custom one: Authentication: Custom <other.jwt.token>. Both should work and handled differently.

Yes, I know spring can handle multiple providers, I know I can use a ReactiveAuthenticationManager but I am stuck in how to deal with the Custom prefix for the opaque token.

Just to make it clear, I need both to work - and, of course, to be handled differently:

GET /
Authorization: Bearer x.y.z

and

GET /
Authorization: Custom a.b.c

If possible, I'd like also to return the list of supported authentication protocols in WWW-Authorization header (i.e. Bearer, Custom).

Any hints? Googling only points me to regular stuff, with Bearer and whatever I try, spring automatically rejects me with 401 (of course, token is not handled).

Thanks.


Solution

  • What I did:

    • I implemented different ReactiveAuthenticationManager, one for each protocol I needed. Something like BearerReactiveAuthenticationManager and CustomReactiveAuthenticationManager and made them @Components;
    • I also implemented ServerSecurityContextRepository and injected both authentication managers from previous point. In the body I had something like:
        @Override
        public Mono<SecurityContext> load(ServerWebExchange serverWebExchange) {
            ServerHttpRequest request = serverWebExchange.getRequest();
            String authHeader = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
    
            if (authHeader.startsWith("Bearer ")) {
                String authToken = authHeader.substring(7);
                Authentication auth = new UsernamePasswordAuthenticationToken(authToken, authToken);
                return this.bearerReactiveAuthenticationManager.authenticate(auth)
                        .map(SecurityContextImpl::new);
            } else if (authHeader.startsWith("Custom ")) { {
                String authToken = authHeader.substring(7);
                Authentication auth = new UsernamePasswordAuthenticationToken(authToken, authToken);
                return this.customReactiveAuthenticationManager.authenticate(auth)
                        .map(SecurityContextImpl::new);
            } else {
                log.debug("Could not identify the authentication header");
                return Mono.empty();
            }
        }
    

    And my SecurityConfig bean looked like this:

    @Configuration
    @EnableWebFluxSecurity
    @EnableReactiveMethodSecurity
    @Slf4j
    public class SecurityConfig {
    
        private final ServerSecurityContextRepository serverSecurityContextRepository;
    
        @Autowired
        public SecurityConfig(ServerSecurityContextRepository serverSecurityContextRepository) {
            this.serverSecurityContextRepository = serverSecurityContextRepository;
        }
    
        @Bean
        public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
            http
                    .csrf().disable()
                    .formLogin().disable()
                    .httpBasic().disable()
                    .logout().disable()
                    .securityContextRepository(serverSecurityContextRepository)
                    .exceptionHandling()
                    .authenticationEntryPoint((swe, e) -> Mono.fromRunnable(() -> swe.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED)))
                    .accessDeniedHandler((swe, e) -> Mono.fromRunnable(() -> swe.getResponse().setStatusCode(HttpStatus.FORBIDDEN)))
    
                    .and().authorizeExchange();
    
           return http.build();
       }
    }