How do I add a filter to spring-security based on a @Profile?
From the article about Spring Security architecture:
Spring Security is installed as a single Filter in the chain, and its concerete type is FilterChainProxy, for reasons that will become apparent soon. In a Spring Boot app the security filter is a @Bean in the ApplicationContext, and it is installed by default so that it is applied to every request.
There can be multiple filter chains all managed by Spring Security in the same top level FilterChainProxy and all unknown to the container. The Spring Security filter contains a list of filter chains, and dispatches a request to the first chain that matches it.
Note also that:
The fact that all filters internal to Spring Security are unknown to the container is important, especially in a Spring Boot application, where all @Beans of type Filter are registered automatically with the container by default. So if you want to add a custom filter to the security chain, you need to either not make it a @Bean or wrap it in a FilterRegistrationBean that explicitly disables the container registration.
So, when you define a filter as a Spring bean, it is registered with the servlet container automatically, but not with the Spring Security filter chain. That is why you need to add it to the Spring Security chain explicitly using addFilter method. You also need to disable auto-registration in the servlet container or the filter will be called twice.
See also:
- 75.3 Add a Servlet, Filter, or Listener to an Application
- What is implication of adding @Component to custom Spring Security filter
As for profiles there are at least two ways to do what you need:
Extend AbstractHttpConfigurer and move common security configuration there. After that create a separate security configuration for each profile:
@Configuration @EnableWebSecurity(debug = true) @EnableGlobalMethodSecurity(prePostEnabled = true, jsr250Enabled = true, securedEnabled = true) public class SecurityConfiguration { /** * Development security configuration. */ @Profile("dev") @Configuration public static class DevSecurityConfiguration extends WebSecurityConfigurerAdapter { @Bean public FilterRegistrationBean userIdAuthenticationFilter() { // ... } @Override protected void configure(HttpSecurity http) throws Exception { http.apply(commonSecurityConfiguration()) .and().addFilter(userIdAuthenticationFilter().getFilter()); } } /** * Production security configuration. */ @Profile("!dev") @Order(1) @Configuration public static class ProdSecurityConfiguration extends WebSecurityConfigurerAdapter { @Bean public FilterRegistrationBean jwtAuthenticationFilter() { // ... } @Override protected void configure(HttpSecurity http) throws Exception { http.apply(commonSecurityConfiguration()) .and().addFilter(jwtAuthenticationFilter().getFilter()); } } /** * Common security configuration reused by all profiles. */ public static class CommonSecurityConfiguration extends AbstractHttpConfigurer<CommonSecurityConfiguration, HttpSecurity> { @Override public void init(HttpSecurity http) throws Exception { // Your basic configuration here: // http.cors().and() // ... } public static CommonSecurityConfiguration commonSecurityConfiguration() { return new CommonSecurityConfiguration(); } } }
See also the example in the documentation.
Inject the Environment object and check the current profile:
@Configuration @EnableWebSecurity(debug = true) @EnableGlobalMethodSecurity(prePostEnabled = true, jsr250Enabled = true, securedEnabled = true) public class SecurityConfiguration extends WebSecurityConfigurerAdapter { private final Environment environment; public SecurityConfiguration(Environment environment) { this.environment = environment; } @Profile("dev") @Bean public FilterRegistrationBean userIdAuthenticationFilter() { // ... } @Profile("!dev") @Bean public FilterRegistrationBean jwtAuthenticationFilter() { // ... } @Override protected void configure(HttpSecurity http) throws Exception { // Your basic configuration here: // http.cors().and() // ... if (environment.acceptsProfiles("dev")) { http.addFilter(userIdAuthenticationFilter().getFilter()); } else { http.addFilter(jwtAuthenticationFilter().getFilter()); } } }
Alternatively you can use application property instead of profile for this.