I would like to configure the actual SecurityFilterChain Bean used based on a confiurable property.
We have one same app, which supports different type of Security.
@Configuration
@EnableWebSecurity
class SecurityConfiguration {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
httpSecurity.authorizeHttpRequests(authorize -> authorize.anyRequest().permitAll()).csrf(AbstractHttpConfigurer::disable);
return httpSecurity.build();
}
@Bean
public SecurityFilterChain securityFilterChainX509(HttpSecurity httpSecurity) throws Exception {
httpSecurity.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.x509()
.subjectPrincipalRegex("CN=(.*?)(?:,|$)");
return httpSecurity.build();
}
@Bean
public SecurityFilterChain securityFilterChainJWT(HttpSecurity httpSecurity) throws Exception {
}
[...]
}
I am pasting just a sample here, but please imagine this file is full of SecurityFilterChain beans with many authentication type, let's imagine 50.
The use case is that each of the instances of the same app has its own security configuration.
For example, the app deployed in the U.S. needs to check the client certificate, hence would need to use the securityFilterChainX509 bean, while the same app deployed in France would need to check username password, while the app deployed in Japan would need another type of auth, etc etc etc.
As of now, we are using a rather "strange" approach, which we use based on profiles.
By that, I mean :
@Bean
@Profile({"canada", "brazil"})
SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
httpSecurity.authorizeHttpRequests(authorize -> authorize.anyRequest().permitAll()).csrf(AbstractHttpConfigurer::disable);
return httpSecurity.build();
}
@Bean
@Profile({"usa", "egypt"})
public SecurityFilterChain securityFilterChainX509(HttpSecurity httpSecurity) throws Exception {
httpSecurity.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.x509()
.subjectPrincipalRegex("CN=(.*?)(?:,|$)");
return httpSecurity.build();
}
@Bean
@Profile({"germany"})
public SecurityFilterChain securityFilterChainJWT(HttpSecurity httpSecurity) throws Exception {
}
While this approach is working, it has drawbacks, it requires us to:
(Really want to avoid profiles if possible)
Question:
Is there a way, without profile, but with a configurable property, something like
@Value("${the.security.bean.to.use}")
private String configuredSecurityBeanToUse;
To configure the actual SecurityFilterChain bean to use?
Yes. You can initialize bean based on property you define. You need to add @ConditionalOnProperty
annotation to the bean method.
@Bean
@ConditionalOnProperty(value = "the.security.bean.to.use", havingValue = "default", matchIfMissing = true)
SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
httpSecurity.authorizeHttpRequests(authorize -> authorize.anyRequest().permitAll()).csrf(AbstractHttpConfigurer::disable);
return httpSecurity.build();
}
@Bean
@ConditionalOnProperty(value = "the.security.bean.to.use", havingValue = "certificate")
public SecurityFilterChain securityFilterChainX509(HttpSecurity httpSecurity) throws Exception {
httpSecurity.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.x509()
.subjectPrincipalRegex("CN=(.*?)(?:,|$)");
return httpSecurity.build();
}
@Bean
@ConditionalOnProperty(value = "the.security.bean.to.use", havingValue = "jwt")
public SecurityFilterChain securityFilterChainJWT(HttpSecurity httpSecurity) throws Exception {
}
@ConditionalOnProperty
will check the property value having value as you defined only that bean will be initialized. matchIfMissing attribute is used when no property defined then it will match the condition and initialize that bean. So it must be used once for same bean type initialization.
In application.properties file you need to define following configuration to use certificate security.
the.security.bean.to.use=certificate
@ConditionalOnProperty Docs Sample with details