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.
What I did:
ReactiveAuthenticationManager
, one for each protocol I needed. Something like BearerReactiveAuthenticationManager
and CustomReactiveAuthenticationManager
and made them @Components
;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();
}
}