Looks like what you want it's something like this.
@Bean
@Order(2)
protected SecurityFilterChain filterChainPublic(HttpSecurity http) throws Exception {
return http.csrf().disable()
.authorizeHttpRequests()
.antMatchers("/art/**")
.permitAll()
.and()
.authorizeHttpRequests()
.antMatchers("/**")
.authenticated()
.and()
.cors()
.disable()
.build();
}
@Bean
@Order(1)
protected SecurityFilterChain filterChainPrivate(HttpSecurity http) throws Exception {
return http.csrf().disable()
.requestMatchers()
.antMatchers("/api/**") // only apply the security configuration to requests that start with /api
.and()
.authorizeHttpRequests()
.antMatchers("/api/contenteditor/feed/**").hasAnyRole("SITE_ADMIN", "STANDARD_ADMIN", "DOMAIN_MEMBER")
.and()
.addFilterBefore(new CustomOncePerRequestFilter(), UsernamePasswordAuthenticationFilter.class)
.cors()
.disable()
.build();
}
The provided code creates two security filter chains to filter elements based on their security level. The second bean applies to secured URLs, while the first bean is more open and does not require any special security configurations.
Another option it's you just filtering it out the paths you don't want in your filter object.
How this code works
The filter chains allow you to have multiple chains for different base paths. The requestMatchers define the base paths that the SecurityFilterChain will work with. The antMatchers define pipeline elements conforming to patterns, and can be used with requestMatchers and authorizeHttpRequests().
Let's take as an example, the code I proposed the first time. What we are doing here it's define a more restrictive element and a open one for general security configurations.
Let's see this on code:
http.csrf().disable()
.requestMatchers()
.antMatchers("/api/**") // only apply the security configuration to requests that start with /api
.and()
.authorizeHttpRequests()
.antMatchers("/api/contenteditor/**")
.hasAnyRole("SITE_ADMIN", "STANDARD_ADMIN", "DOMAIN_MEMBER")
.and()
.addFilterBefore(new CustomOnPerRequestFilter(), UsernamePasswordAuthenticationFilter.class)
This code specifies that the filter chain should only work with requests that start with /api/** and use the filter only in these base paths. It also specifies that requests conforming to /api/contenteditor/** require the SITE_ADMIN, STANDARD_ADMIN, or DOMAIN_MEMBER roles. The addFilterBefore method specifies that a custom CustomOnPerRequestFilter filter should be added before the UsernamePasswordAuthenticationFilter. Notice that your custom filter would be executed in any /api/** path not only in/api/contenteditor/**.
If we instead do like the first bean
http.csrf().disable()
.authorizeHttpRequests()
.antMatchers("/art/**")
.permitAll()
.and()
.authorizeHttpRequests()
.antMatchers("/**")
.authenticated()
The requestMatcher by default is any path, so here we are operating over all the urls if we incluide a filter in here would be executed in any path that doesn't match the previous filter. The art endpoint is public but the rest are under authentication.
It's important to note that the order of the beans is essential, as Spring will use the first bean that conforms to the URL. Therefore, the most specific should be the first in order, and the most general should be the last.
Lastly, it's important not to annotate custom security filters as @Component or any other IOD annotation in this approach because Spring scans every bean that implements the filter and adds it to the regular filter chain.
Note that the provided code works with Spring Security 5, but in Spring Security 6, things will change. In Spring Security 6, you can use the following code:
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(authorize -> {
authorize.requestMatchers("/art/**")
.permitAll();
authorize.requestMatchers("/api/**")
.hasAnyRole("SITE_ADMIN", "STANDARD_ADMIN", "DOMAIN_MEMBER")
.anyRequest()
.authenticated().and()
.addFilterBefore(new CustomOnPerRequestFilter(), UsernamePasswordAuthenticationFilter.class);
})
.cors()
.disable()
.build(
);
}