Search code examples
spring-bootspring-securityjwtservlet-filters

How to make a custom UsernamePasswordAuthenticationFilter register at an endpoint other than /login?


I've been following a tutorial to implementing JWT authentication in Spring Boot but am trying to adapt it to a case where I have two WebSecurityConfigurerAdapter classes, one for my API (/api/** endpoints) and one for my web front-end (all other endpoints). In the tutorial, a JWTAuthenticationFilter is created as a subclass of UsernamePasswordAuthenticationFilter and added to the chain. According to the author, this filter will automatically register itself with the "/login" endpoint, but I want it to point somewhere different, such as "/api/login" because I'm using this authentication method for my API only.

Here's the security configuration code for both the API and front-end (with some abbrevation):

@EnableWebSecurity
public class MultipleSecurityConfigurations {

    @Configuration
    @Order(1)
    public static class APISecurityConfiguration extends WebSecurityConfigurerAdapter {

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.antMatcher("/api/**")
                    .sessionManagement()
                        .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                        .and()
                    .csrf().disable()
                    .authorizeRequests()
                        .anyRequest().authenticated()
                        .and()
                    .addFilter(new JWTAuthenticationFilter(authenticationManager()))
                    .addFilter(new JWTAuthorizationFilter(authenticationManager()));
        }
    }


    @Configuration
    public static class FrontEndSecurityConfiguration extends WebSecurityConfigurerAdapter {

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                    .formLogin()
                    .loginPage("/login").permitAll()
                    .defaultSuccessUrl("/")
                    .and()
                    .logout()
                    .logoutUrl("/logout")
                    .logoutSuccessUrl("/?logout")
                    .and()
                    .authorizeRequests()
                    .mvcMatchers("/").permitAll() 
                    .mvcMatchers("/home").authenticated() 
                    .anyRequest().denyAll() 
                    ;
        }

    }

}

The question is: how can I define an endpoint such as "/api/login" as the endpoint for my custom JWTAuthenticationFilter?

Or, do I need to change the filter to not be a subclass of UsernamePasswordAuthenticationFilter and if so, how would I configure that?

EDIT: Something I've tried:

I guessed that the /api/login endpoint needed to be .permitAll() and I tried using formLogin().loginProcessingUrl(), even though it's not really a form login - it's a JSON login. This doesn't work. When i POST to /api/login I end up getting redirected to the HTML login form as if I were not logged in. Moreover, my Spring boot app throws a weird exception:

org.springframework.security.web.firewall.RequestRejectedException: The request was rejected because the URL contained a potentially malicious String ";"

The configuration I'm trying now:

    protected void configure(HttpSecurity http) throws Exception {
        http.antMatcher("/api/**")
                .sessionManagement()
                    .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                    .and()
                .csrf().disable()
                .formLogin().loginProcessingUrl("/api/login").and()
                .authorizeRequests()
                    .antMatchers("/api/login").permitAll()
                    .anyRequest().authenticated()
                    .and()
                .addFilter(new JWTAuthenticationFilter(authenticationManager()))
                .addFilter(new JWTAuthorizationFilter(authenticationManager()));
    }

Solution

  • Since JWTAuthenticationFilter is a UsernamePasswordAuthenticationFilter, you could change the login endpoint directly on the filter instance:

    JWTAuthenticationFilter customFilter = new JWTAuthenticationFilter(authenticationManager());
    customFilter.setFilterProcessesUrl("/api/login");
    
    http.addFilter(customFilter);
    

    This configures JWTAuthenticationFilter to attempt to authenticate POST requests to /api/login.