Issue
Here is my working security conf before migration :
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers("/auth/**")
.antMatchers("/swagger-ui/**")
.antMatchers("/swagger-ui.html")
.antMatchers("/swagger-resources/**")
.antMatchers("/v2/api-docs/**")
.antMatchers("/v3/api-docs/**");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedPortalRoleConverter);
http
.csrf().disable()
.cors()
.and()
.exceptionHandling()
.authenticationEntryPoint(new AuthenticationFallbackEntryPoint())
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests(authorize -> authorize.anyRequest().authenticated())
.oauth2ResourceServer()
.jwt().jwtAuthenticationConverter(jwtAuthenticationConverter);
}
And here is my Security chain config after migration :
@Bean
@Order(1)
public SecurityFilterChain ignorePathsSecurityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.antMatchers(
"/auth/**",
"/swagger-ui/**",
"/swagger-ui.html",
"/swagger-resources/**",
"/v3/api-docs/**")
.permitAll());
return http.build();
}
@Bean
@Order(2)
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http, GrantedPortalRoleConverter grantedPortalRoleConverter) throws Exception {
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedPortalRoleConverter);
http
.csrf().disable()
.cors(Customizer.withDefaults())
.exceptionHandling(configurer -> configurer.authenticationEntryPoint(new AuthenticationFallbackEntryPoint()))
.sessionManagement(configurer -> configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(authorize -> authorize.anyRequest().authenticated())
.oauth2ResourceServer(configurer -> configurer.jwt().jwtAuthenticationConverter(jwtAuthenticationConverter));
return http.build();
}
With the original conf, when I call a random non existing path :
@Test
void should_not_authenticate_or_return_not_found() throws Exception {
logger.info("should_not_authenticate_or_return_not_found");
mvc.perform(get("/toto/tata"))
.andExpect(status().isUnauthorized());
}
I get :
15:44:00.230 [main] DEBUG o.s.s.w.a.i.FilterSecurityInterceptor - Failed to authorize filter invocation [GET /toto/tata] with attributes [authenticated]
With the new conf, I'm just getting HTTP 404, what am I missing here please ? I can't see any difference and debug logs don't show much.
Here is the first line of log missing using the non working conf :
16:24:58.651 [main] DEBUG o.s.s.w.a.e.ExpressionBasedFilterInvocationSecurityMetadataSource - Adding web access control expression [authenticated] for any request
But in both logs, I can see (2 lines of this for the new conf since there are 2 security chains) :
o.s.s.web.DefaultSecurityFilterChain - Will secure any request with (...)
Solution
Explanation
When you have multiple SecurityFilterChain
s, you have to specify a request matcher, otherwise all requests will be processed by the first SecurityFilterChain
, annotated with @Order(1)
, and never reach the second SecurityFilterChain
, annotated with @Order(2)
.
In the code you shared above, this means configuring .requestMatchers()
in ignorePathsSecurityFilterChain
:
@Bean
@Order(1)
public SecurityFilterChain ignorePathsSecurityFilterChain(HttpSecurity http) throws Exception {
http
.requestMatchers(requests -> requests // add this block
.antMatchers(
"/auth/**",
"/swagger-ui/**",
"/swagger-ui.html",
"/swagger-resources/**",
"/v3/api-docs/**")
)
.authorizeHttpRequests(authorize -> authorize
.antMatchers(
"/auth/**",
"/swagger-ui/**",
"/swagger-ui.html",
"/swagger-resources/**",
"/v3/api-docs/**")
.permitAll());
return http.build();
}
This means that only the requests matching /auth/**
, /swagger-ui/**
etc will be processed by ignorePathsSecurityFilterChain
, while the rest of the requests will move on to defaultSecurityFilterChain
.
To understand the difference between requestMatchers
and authorizeHttpRequests
you can check out this StackOverflow question.
Solution
An even better option is to combine the SecurityFilterChain
s into a single one. I don't see any reason why you would separate them in this case.
The resulting configuration would be:
@Bean
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http, GrantedPortalRoleConverter grantedPortalRoleConverter) throws Exception {
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedPortalRoleConverter);
http
.authorizeHttpRequests(authorize -> authorize
.antMatchers(
"/auth/**",
"/swagger-ui/**",
"/swagger-ui.html",
"/swagger-resources/**",
"/v3/api-docs/**")
.permitAll()
.anyRequest().authenticated()
)
.csrf().disable()
.cors(Customizer.withDefaults())
.exceptionHandling(configurer -> configurer.authenticationEntryPoint(new AuthenticationFallbackEntryPoint()))
.sessionManagement(configurer -> configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.oauth2ResourceServer(configurer -> configurer.jwt().jwtAuthenticationConverter(jwtAuthenticationConverter));
return http.build();
}
Alternative
Alternatively you can use a WebSecurityCustomizer
to ignore certain endpoints:
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> web.ignoring().antMatchers(
"/auth/**",
"/swagger-ui/**",
"/swagger-ui.html",
"/swagger-resources/**",
"/v3/api-docs/**");
}
Then you would use defaultSecurityFilterChain
as your only SecurityFilterChain
.
Answered By - Eleftheria Stein-Kousathana
Answer Checked By - Katrina (JavaFixing Volunteer)