I have trouble to get my two SecurityFilterhains 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/**");
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With