Java – Multiple WebSecurityConfigurerAdapters: JWT authentication and form login in spring security

Multiple WebSecurityConfigurerAdapters: JWT authentication and form login in spring security… here is a solution to the problem.

Multiple WebSecurityConfigurerAdapters: JWT authentication and form login in spring security

I have a Spring Boot app with ThymeLeaf. I’m using the spring security formLogin method to ensure security, and now I just need to add JWTs for some APIs.


@EnableWebSecurity
public class SecurityConfigurations {
    @Autowired
    UserDetailsServiceImpl userDetails;

@Bean
    DaoAuthenticationProvider provider() {
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        provider.setPasswordEncoder(encoder());
        provider.setUserDetailsService(userDetails);
        return provider;
    }

@Bean
    PasswordEncoder encoder() {
        return new BCryptPasswordEncoder();
    }

@Configuration
    @Order(1)

public class JWTSecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired
        private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;

@Autowired
        private JwtRequestFilter jwtRequestFilter;

@Autowired
        DaoAuthenticationProvider provider;

@Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.authenticationProvider(provider);
        }

@Bean
        @Override
        public AuthenticationManager authenticationManagerBean() throws Exception {
            return super.authenticationManagerBean();
        }

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

httpSecurity.csrf().disable()

.authorizeRequests().antMatchers("/api/user/authenticate").permitAll()

.antMatchers("/api/user/**").hasRole("USER")
                    .and().
                    exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and().sessionManagement()
                    .sessionCreationPolicy(SessionCreationPolicy.STATELESS);

 Add a filter to validate the tokens with every request
            httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
        }
    }

@Configuration
    public static class FormLoginConfigurationAdapter extends WebSecurityConfigurerAdapter {
        @Autowired
        DaoAuthenticationProvider provider;

@Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.authenticationProvider(provider);
        }

@Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests().antMatchers("/admin/admins**").hasAnyRole("SADMIN").antMatchers("/admin/**")
                    .hasAnyRole("ADMIN", "SADMIN", "WADMIN").antMatchers("/rest/**")
                    .hasAnyRole("ADMIN", "SADMIN", "WADMIN", "USER").antMatchers("/user/**").hasAnyRole("USER")
                    .anyRequest().permitAll().and().formLogin().loginPage("/sign-in-up")
                    .loginProcessingUrl("/signInProcess").usernameParameter("phone").and().logout()
                    .logoutRequestMatcher(new AntPathRequestMatcher("/logout")).logoutSuccessUrl("/")
                    .invalidateHttpSession(false).and().csrf().disable().cors();

}
    }

}

By doing this, the JWT works fine, but formlogin has stopped and called “/signInProcess” which now gives 404:
signInProcess is not working

Note: If I change the order and make formLogin @order(1) work again, but of course it won’t work.

I also tried combining them like this and now they all work fine, but if the JWT authentication error will return a formlogin thymeleaf error page, there is a problem with exception handling:

@Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests().antMatchers("/admin/admins**").hasAnyRole("SADMIN").antMatchers("/admin/**")
                    .hasAnyRole("ADMIN", "SADMIN", "WADMIN").antMatchers("/rest/**")
                    .hasAnyRole("ADMIN", "SADMIN", "WADMIN", "USER").antMatchers("/user/**").hasAnyRole("USER")
                    .antMatchers("/api/user/authenticate").permitAll()
                    .antMatchers("/api/user/**").hasRole("USER")
                    .anyRequest().permitAll().and().formLogin().loginPage("/sign-in-up")
                    .loginProcessingUrl("/signInProcess").usernameParameter("phone").and().logout()
                    .logoutRequestMatcher(new AntPathRequestMatcher("/logout")).logoutSuccessUrl("/")
                    .invalidateHttpSession(false).and().csrf().disable().cors();
            
http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);

}

Any suggestions to make this work effective. Thank you.

Solution

Your WebSecurityConfigurerAdapters will process incoming requests sequentially.
Since JWTSecurityConfig is annotated with @Order(1), it will process the request first.

You don’t specify an antMacher for this adapter, so it will match all requests.
This means that the request never reaches the FormLoginConfigurationAdapter because JWTSecurityConfig matches them.

If you want JWTSecurityConfig to apply only to specific requests, you can specify an antMatrix in your security configuration.
Here’s an example:

@EnableWebSecurity
public class SecurityConfigurations {

@Configuration
    @Order(1)
    public class JWTSecurityConfig extends WebSecurityConfigurerAdapter {

@Override
        protected void configure(HttpSecurity http) throws Exception {
           http
              .requestMatchers(matchers -> matchers
                  .antMatchers("/api/**") // apply JWTSecurityConfig to requests matching "/api/**"
              )
              .authorizeRequests(authz -> authz
                  .anyRequest().authenticated()
              )
              .addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
        }
    }

@Configuration
    public class FormLoginConfigurationAdapter extends WebSecurityConfigurerAdapter {

@Override
        protected void configure(HttpSecurity http) throws Exception {
           http
              .authorizeRequests(authz -> authz
                  .anyRequest().authenticated()
              )
              .formLogin();
        }
    }
}

For more details about multiple WebSecurityConfigurerAdapters, you can see multiple HttpSecurity Spring Security section of the reference documentation.

For more on the differences between authorizeRequests() and requestMatchers(), see this Stack Overflow question .

Related Problems and Solutions