How to validate multiple Bearer tokens using Spring Security

2 weeks ago 13
ARTICLE AD BOX

I'm working on a Spring Boot (3.5.x) web application using Spring Security and OAuth2 Resource Server. I need to support two types of Bearer tokens for authentication:

Standard Bearer JWT: Sent in the Authorization header (e.g., Authorization: Bearer <token>), with scopes/authorities.

Custom Session JWT: Sent in a custom header (e.g., X-Authorization-Session: Bearer <token>), which does not contain any authorities — just needs to be validated for authenticity (expiration, issuer, signature, etc.).

My requirements are:

Some endpoints should only require the Session token (just check validity, no authorities).

Some endpoints should require both: first validate the Session token, then validate the standard Bearer token and use its authorities for authorization.

Some endpoints should only require the standard Bearer token.

I've already implemented an AuthenticationManagerResolver /ReactiveAuthenticationManagerResolver that can handle multiple issuers, and if I use the standard configuration like:

oauth2ResourceServer(oauth2 -> oauth2.authenticationManagerResolver(standardBearerTokenAuthenticationManagerResolver))

...everything works fine if I only configure the SecurityFilterChain / SecurityWebFilterChain to authorize endpoints using only the standard bearer token by applying @PreAuthorize or directly calling .hasRole() / .hasAuthority() on a AuthorizeExchangeSpec:

@Bean public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http, ReactiveAuthenticationManagerResolver<ServerWebExchange> authManagerResolver) { Customizer<ServerHttpSecurity.AuthorizeExchangeSpec> authorizeExchangeCustomizer = authorizeExchangeSpec -> authorizeExchangeSpec .pathMatchers("/api/public").permitAll() .anyExchange().authenticated(); return http .csrf(ServerHttpSecurity.CsrfSpec::disable) .authorizeExchange(authorizeExchangeCustomizer) .oauth2ResourceServer(oauth2 -> oauth2.authenticationManagerResolver(authManagerResolver)) .build(); }

I can't figure out how to configure Spring Security so that I'm able to mix/match both of the token validators based on my previously mentioned requirements. So far I've tried using multiple SecurityFilterChain beans with different securityMatcher() and authorizeHttpRequests() configurations. First chain is annotated with @Order(1) and oauth2 configuration is applied using the specific ReactiveAuthenticationManagerResolver as well as ServerBearerTokenAuthenticationConverter with .setBearerTokenHeaderName("X-Authorization-Session"). Then I configure a 2nd security chain with @Order(2) which sets up the standard bearer token using the same .oauth2ResourceServer(oauth2 -> ...). 3rd chain is for setting up any public endpoints with .permitall(), etc. - problem with this approach, is that if there are endpoints, where both of these tokens should be used, the SecurityFilterChain with @Order(1) configured for session bearer token runs first, successfully authenticates the request and since session token does not contain any authorities, the request is forbidden. Example:

@Bean @Order(1) public SecurityWebFilterChain sessionTokenSecurityWebFilterChain(ServerHttpSecurity http, ReactiveAuthenticationManagerResolver<ServerWebExchange> sessionAuthResolver) { ServerWebExchangeMatcher sessionTokenPaths = ServerWebExchangeMatchers.pathMatchers("/api/session", "/api/sessionAndStandardToken"); var sessionTokenConverter = new ServerBearerTokenAuthenticationConverter(); sessionTokenConverter.setBearerTokenHeaderName("X-Authorization-Session"); return http .securityMatcher(sessionTokenPaths) .authorizeExchange(exchange -> exchange.matchers(sessiontokenPaths).authenticated()) .oauth2ResourceServer(oAuth2 -> oAuth2.authenticationManagerResolver(sessionAuthResolver) .bearerTokenConverter(sessionTokenConverter)) .build(); } @Bean @Order(2) public SecurityWebFilterChain standardTokenSecurityWebFilterChain(ServerHttpSecurity http,ReactiveAuthenticationManagerResolver<ServerWebExchange> standardAuthResolver) { ServerWebExchangeMatcher standardTokenPaths = ServerWebExchangeMatchers.pathMatchers("/api/admin", "/api/sessionAndStandardToken"); return http .securityMatcher(standardTokenPaths) .authorizeExchange(exchange -> exchange.matchers(standardTokenPaths).authenticated()) .oauth2ResourceServer(oAuth2 -> oAuth2.authenticationManagerResolver(standardAuthResolver)) .build(); } @Bean @Order(3) public SecurityWebFilterChain defaultSecurityWebFilterChain(ServerHttpSecurity http) { return http .securityMatcher(ServerWebExchangeMatchers.anyExchange()) .authorizeExchange(exchange -> exchange.pathMatchers("/api.public").permitAll()) .build(); }

How can I configure Spring Security to support these scenarios and ensure that for endpoints requiring both tokens, the Session token is validated first, and only if valid, the standard Bearer token is checked for authorities? Is there a recommended way to structure this logic in Spring Security 6+?

Read Entire Article