Configure Spring Security in multiple places
Obviously, I can take full advantage of the power of Spring Security by implementing a single WebSecurityConfigurerAdapter
and accessing HttpSecurity
in its configure
method. However, this results in a monolithic implementation, and without implementing a custom measure for this, it cannot be propagated across application modules.
Therefore, one might want to implement several WebSecurityConfigurerAdapter
subclasses. But this results in duplicate HttpSecurity
objects, attempts to reconfigure some basic aspects (such as csrf) and fails to properly modify what is already configured in the first adapter. Even disabling the default settings won’t help.
So my question is: is there a Spring or Spring-Boot way to specify http security in a separate configuration/component class? (So Java is not an xml configuration)
An example might be adding a security filter in the middle of the chain. Another changes the csrf (e.g. session to cookie), while the other separate class will just leave the default value.
Solution
I don’t think there’s a direct way to do this. But we can still force it to do so in our project architecture.
There are 3 main methods, and we usually rewrite our configuration from WebSecurityConfigurerAdapter.
1. Configuration (Authentication ManagerBuilder authentication)
2. Configuration (WebSecurity web)
3. Configuration (HttpSecurity http).
According to the Spring Security Architecture, only one instance of WebSecurityConfigurer can be used.
We can design it like this:
1. With this rule, we can have our parent project hold this WebsecurityConfigurer instance.
2. We can have IBaseSecurityConfig with the above 3 method signatures.
3. We will ignore any other WebsecurityConfigurer instances and only allow the parent WebSecurityConfigurer instance.
4. We can abstract IBaseSecurityConfig as BaseSecurityConfig.
Just as Spring enforces WebsecurityConfig to us, you can force BaseSecurityConfig on your project module to override any security-related configuration.
I’ll try to explain it with an example.
public interface IBaseSecurityConfig {
void configure(AuthenticationManagerBuilder auth) throws Exception;
void configure(WebSecurity web) throws Exception;
void configure(HttpSecurity http) throws Exception;
}
@Configuration
public abstract class BaseSecurityConfig implements IBaseSecurityConfig {
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
TODO Any defaults
}
@Override
public void configure(WebSecurity web) throws Exception {
TODO Any defaults
}
@Override
public void configure(HttpSecurity http) throws Exception {
TODO Any defaults
}
}
Now we will declare our security configuration anywhere by extending BaseSecurityConfig. Suppose we declare WebSecurityConfiguration1 as follows.
@Configuration
public class WebSecurityConfiguration1 extends BaseSecurityConfig {
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/css/**", "/js/**", "/admin/**").permitAll().anyRequest().authenticated()
.and()
.addFilterBefore(ssoFilter(), BasicAuthenticationFilter.class)
.formLogin().loginPage("/login").permitAll().and().logout().logoutSuccessUrl("/");
}
}
Now we will declare a separate security configuration anywhere else. Let’s call it WebSecurtiyConfiguration2.
@Configuration
public class WebSecurtiyConfiguration2 extends BaseSecurityConfig {
@Override
public void configure(HttpSecurity http) throws Exception {
IsSecureFilter i1 = new IsSecureFilter();
http.addFilterBefore(i1, ChannelProcessingFilter.class);
}
}
Now we must automatically configure the security configuration declared above. We’ll do it in our parent project, or you can say we’ll configure them in a real instance of SecurityConfig as shown below.
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private List<IBaseSecurityConfig> securityConfigs;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
for(IBaseSecurityConfig secConfig : securityConfigs) {
secConfig.configure(auth);
}
}
@Override
public void configure(WebSecurity web) throws Exception {
for(IBaseSecurityConfig secConfig : securityConfigs) {
secConfig.configure(web);
}
}
@Override
protected void configure(HttpSecurity http) throws Exception {
System.out.println("CONFIGURING FROM BASE");
for(IBaseSecurityConfig secConfig : securityConfigs) {
secConfig.configure(http);
}
}
}
Now this is our application loading class.
We will have to ensure that no other WebSecurityConfigurerAdapter is loaded and only our parent instance is loaded. We do this by @Component-> exclusion filters. With the help of @Import, it will be ensured that only our instances are loaded.
@SpringBootApplication
@EnableCaching
@ComponentScan(excludeFilters = @ComponentScan.Filter(type=FilterType.ASSIGNABLE_TYPE,classes=WebSecurityConfigurerAdapter.class))
@Import(SecurityConfig.class)
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
Now that you’ve forced your schema to declare any security configuration by extending only BaseSecurityConfig, you can do this in different places.
Note, however, that this may overwrite each other’s configurations if a conflict occurs.