Jan Zyka
Jan Zyka

Reputation: 17898

Using multiple WebSecurityConfigurerAdapter in spring boot

I'm having 2 classes which extends WebSecurityConfigurerAdapter. And can't make them work together.

The idea is as follows:

  1. Have one WebSecurityConfigurerAdapter which only adds custom filter to security chain. The filter does some custom authentication and saves Authentication into SecurityContext. This generally works fine. Configured as follows (imports omitted):
 @Order(1)
 @Configuration
 @EnableWebMvcSecurity
 public class BestSecurityConfig extends WebSecurityConfigurerAdapter {

     @Autowired
     private BestPreAuthenticationFilter ssoAuthenticationFilter;

     @Bean
     protected FilterRegistrationBean getSSOAuthenticationFilter() {
         FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(ssoAuthenticationFilter);

         // Avoid include to the default chain
         filterRegistrationBean.setEnabled(false);

         return filterRegistrationBean;
     }

     @Override
     protected void configure(HttpSecurity http) throws Exception {
         http
            .addFilterAfter(ssoAuthenticationFilter, SecurityContextPersistenceFilter.class);

     }

     @Configuration
     protected static class AuthenticationConfiguration extends
             GlobalAuthenticationConfigurerAdapter {

         @Autowired
         private BestAuthenticationProvider authenticationProvider;

         @Override
         public void configure(AuthenticationManagerBuilder auth) throws Exception {
             auth.authenticationProvider(authenticationProvider);
         }
     }
 }
  1. I want the above to be kind of library class which anyone can include via @ComponentScan and get the custom authentication sorted. Obviously they want to provide custom HttpSecurity to secure edpoints. Trying something like:
 @Configuration
 @EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
 @Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
 public class SecurityConfig extends WebSecurityConfigurerAdapter {

     @Override
     protected void configure(HttpSecurity http) throws Exception {
         http
             .csrf().disable()
             .authorizeRequests()
             .antMatchers("/testUrl").hasRole("NON_EXISTING")
             .anyRequest().authenticated();
     }
 }

Obviously the test URL should not be accessible as my user is not member of role NON_EXISTING. Unfortunatelly she is.

If I move the security authorizeRequests() part to the configuration class form 1. next to adding the security filter then it blocks the access as expected. But in my case it looks like the second configuration is ignored.

I also debugged the configure() methods and noticed that HttpSecurity is not the same object which smells a bit.

Any tips how can I make this work much appreciated.

Sum up of the goal:

Spring boot 1.1.6-RELEASE

Upvotes: 34

Views: 33594

Answers (3)

eis
eis

Reputation: 53462

Define a special interface

public interface ServiceWebSecurityConfigurer {
    void configure(HttpSecurity http) throws Exception;
}

Then have just one ConfigurerAdapter:

public class MyConfigurerAdapter extends WebSecurityConfigurerAdapter {

    @Autowired(required = false)
    ServiceWebSecurityConfigurer serviceSecConfig;

    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests(). // whatever

        if (serviceSecConfig != null) serviceSecConfig.configure(http);

        http.authorizeRequests(). // whatever
    }
}

and then just implement ServiceWebSecurityConfigurer elsewhere when needed. There can be multiple implementations as well, just autowire them as list and iterate and use them all in your main configuration.

Upvotes: 20

Julio Villane
Julio Villane

Reputation: 1034

I founded (in my opinion) a cleaner way of structuring some default configurations and make it simple to integrate in new projects by using Custom DSLs.

I'm using it to config JWT authentication filters, but i think a CORS filter is more simple and didactic:

public class CustomCorsFilterDsl extends AbstractHttpConfigurer<CustomCorsFilterDsl, HttpSecurity> {

    @Override
    public void init(HttpSecurity http) throws Exception {
        //your init code here, no needed in this case
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        CorsFilter corsFilter = corsFilter(corsProperties);
        http.addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class);
    }

    private CorsFilter corsFilter(CorsProperties corsProperties) {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.addAllowedOrigin("http://localhost:9000");
        config.addAllowedHeader("*");
        config.addAllowedMethod("GET, POST, PUT, PATCH, DELETE");
        source.registerCorsConfiguration("/**", config);
        return new CorsFilter(source);
    }

    public static CustomCorsFilterDsl dsl() {
        return new CustomCorsFilterDsl();
    }
}

And in your WebSecurityConfig you can use it like this:

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .csrf().disable()
                .exceptionHandling()
                .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                .antMatchers("/foo/**").permitAll()
                //... your configurations
                .antMatchers("/**").authenticated()
                .and()
                .apply(CustomCorsFilterDsl.dsl());
    }
}

And you accomplished your objective of having libraries with default configurations independent of your projects code, in a more clear way, because you can visualize in the project's WebSecurityConfig a custom CORS entry.

Upvotes: 9

Jan Zyka
Jan Zyka

Reputation: 17898

So one option I just found is:

  1. Remove the @Configuration annotation from the first bean

And change the 2. to:

 @Configuration
 @EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
 @Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
 public class SecurityConfig extends BestSecurityConfig { //Note the changed extend !

     @Override
     protected void configure(HttpSecurity http) throws Exception {

         super.configure(http); // Merge of the 2 HTTP configurations

         http
             .csrf().disable()
             .authorizeRequests()
             .antMatchers("/testUrl").hasRole("NON_EXISTING")
             .anyRequest().authenticated();
     }
 }

Any comments on whether this is right or wrong approach much appreciated

Edit: After few years I still didn't find other way but I like this way more and more. Even in the default case you extend the abstract WebSecurityConfigurerAdapter there is no reason why some other layer of abstraction can't provide another abstract extension which provides meaningful defaults.

Upvotes: 16

Related Questions