I have a springboot webflux application protected by spring security oauth2. I have both restricted and unrestricted endpoints in the application. The application is throwing 401 when pass Authorization header to unrestricted endpoint. It works fine when I don't pass Authorization header for unrestricted endpoint. I can see that AuthenticationManager
is getting executed for both restricted and unrestricted endpoints when Authorization header is passed.
SecurityWebFilterChain
bean configuration is given below.
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity serverHttpSecurity) {
return serverHttpSecurity
.requestCache()
.requestCache(NoOpServerRequestCache.getInstance())
.and()
.securityContextRepository(NoOpServerSecurityContextRepository.getInstance())
.exceptionHandling()
.authenticationEntryPoint((swe, e) -> Mono.fromRunnable(() -> swe.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED)))
.accessDeniedHandler((swe, e) -> Mono.fromRunnable(() -> swe.getResponse().setStatusCode(HttpStatus.FORBIDDEN)))
.and().csrf().disable()
.authorizeExchange()
.pathMatchers("/api/unrestricted").permitAll()
.and()
.authorizeExchange().anyExchange().authenticated()
.and()
.oauth2ResourceServer()
.jwt(jwtSpec -> jwtSpec.authenticationManager(authenticationManager()))
.authenticationEntryPoint((swe, e) -> Mono.fromRunnable(() -> swe.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED)))
.accessDeniedHandler((swe, e) -> Mono.fromRunnable(() -> swe.getResponse().setStatusCode(HttpStatus.FORBIDDEN)))
.and().build();
}
AuthenticationManager
code is like below.
private ReactiveAuthenticationManager authenticationManager() {
return authentication -> {
log.info("executing authentication manager");
return Mono.justOrEmpty(authentication)
.filter(auth -> auth instanceof BearerTokenAuthenticationToken)
.cast(BearerTokenAuthenticationToken.class)
.filter(token -> RSAHelper.verifySigning(token.getToken()))
.switchIfEmpty(Mono.error(new BadCredentialsException("Invalid token")))
.map(token -> (Authentication) new UsernamePasswordAuthenticationToken(
token.getToken(),
token.getToken(),
Collections.emptyList()
));
};
}
We found this issue when one of our API consumers sent dummy Authorization header for unrestricted endpoint.
I can find Spring MVC solution for the similar issue in SpringMVC Oauth2.
I have a working example in the github project demo-security. I have written couple of Integration Tests to explain this issue.
@AutoConfigureWebTestClient
@SpringBootTest
public class DemoIT {
@Autowired
private WebTestClient webTestClient;
@Test
void testUnrestrictedEndpointWithAuthorizationHeader() {
webTestClient.get()
.uri("/api/unrestricted")
.header(HttpHeaders.AUTHORIZATION, "Bearer token") // fails when passing token
.exchange()
.expectStatus().isOk();
}
@Test
void testUnrestrictedEndpoint() {
webTestClient.get()
.uri("/api/unrestricted")
.exchange()
.expectStatus().isOk();
}
@SneakyThrows
@Test
void testRestrictedEndpoint() {
webTestClient.get()
.uri("/api/restricted")
.header(HttpHeaders.AUTHORIZATION, "Bearer " + RSAHelper.getJWSToken())
.exchange()
.expectStatus().isOk();
}
}
I'm not sure what might be the problem. Is my Security Config misconfigured ? Any help would be really appreciated.