I have trouble to get my two SecurityFilterhain
s work in conjunction with each other using Spring Security 6.
For one of my endpoint paths (/v1/transactions/**
) I want the user to authorize with Oauth2 and for the other endpoint path (/v1/info
) Basic Auth is required. Only one of the configurations works as expected depending on which @Order()
they have.
With the below two SecurityFilterChain
configurations I am able to make requests to /v1/info
using Basic Auth but not making requests to /v1/transaction/**
using Oauth2 which just gives me 401 Access Denied.
If I change the order so basicAuthSecurityFilterChain
gets @Order(2)
and oauth2SecurityFilterChain
gets @Order(1)
then I can make calls to /v1/transaction/**
using OAauth2 but not make calls to /v1/info
using Basic Auth which then gives me 401 Access Denied.
I am not sure why I encounter this behaviour since the docs tells me that the invocation of a SecurityFilterChain is decided based on the path and the paths for the basic auth and oauth2 resources are different (/v1/transaction/**
vs /v1/info
).
@EnableMethodSecurity(securedEnabled = true, jsr250Enabled = true)
@EnableWebSecurity
@Configuration
public class BasicAuthSecurity {
public AuthenticationManager authProviderManager() { //omitted code) }
@Bean
@Order(1)
public SecurityFilterChain basicAuthSecurityFilterChain(HttpSecurity http) throws Exception {
return http
.authenticationManager(authProviderManager())
.authorizeHttpRequests(authorize -> authorize
.requestMatchers(POST, "/v1/info", "/v1/info/{user}").hasRole("user")
.anyRequest().authenticated()
)
.httpBasic(withDefaults())
.build();
}
}
And for the OAuth2 SecurityFilterChain I am using this:
@EnableMethodSecurity(securedEnabled = true, jsr250Enabled = true)
@EnableWebSecurity
@Configuration
public class Oauth2Security {
@Bean
@Order(2)
public SecurityFilterChain oauth2SecurityFilterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(authorize -> authorize
.requestMatchers(POST, "/v1/transaction/**")
.hasAnyRole("poweruser", "admin")
.anyRequest().authenticated()
)
.oauth2ResourceServer()
.jwt()
.and().and().build();
}
}
you are missing securityMatcher
in the first filter-chain in @Order
. That simple :/
// Applies only to the specified security-matchers:
// requests with a Basic Authorization header
@Bean
@Order(1)
public SecurityFilterChain basicAuthSecurityFilterChain(HttpSecurity http) throws Exception {
http.securityMatcher((HttpServletRequest request) -> {
return Optional.ofNullable(request.getHeader(HttpHeaders.AUTHORIZATION)).map(h -> {
return h.toLowerCase().startsWith("basic ");
}).orElse(false);
});
return http
.authenticationManager(authProviderManager())
.authorizeHttpRequests(authorize -> authorize
.requestMatchers(POST, "/v1/info", "/v1/info/{user}").hasRole("user")
.anyRequest().authenticated()
)
.httpBasic(withDefaults())
.build();
}
// this one has lowest precedence (higher order) and no security matcher
// => behaves as default when higher precedence (lower order) ones security matchers did not match
@Bean
@Order(2)
public SecurityFilterChain oauth2SecurityFilterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(authorize -> authorize
.requestMatchers(POST, "/v1/transaction/**")
.hasAnyRole("poweruser", "admin")
.anyRequest().authenticated()
)
.oauth2ResourceServer()
.jwt()
.and().and().build();
}
Note that you can write security-matchers using about anything from the requests. It is more frequent to see some matching path patterns:
http.securityMatcher("/v1/info/**");