Spring Security /login - 404 not found

Viewed 2970

I'm facing problem with enabling user log-in page - 404 not found.

This is tutorial that I'm using as base of my application security.

That's how configure function looks like:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.cors().and().csrf().disable().authorizeRequests()
            .antMatchers(HttpMethod.POST, SIGN_UP_URL).permitAll()
            .anyRequest().authenticated()
            .and()
            .addFilter(new JWTAuthenticationFilter(authenticationManager()))
            .addFilter(new JWTAuthorizationFilter(authenticationManager()))
            // this disables session creation on Spring Security
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}

I have tried to simply add here:

.and()
.formLogin()
.loginPage("/login")
.permitAll();

and changing .addFilter to .addFilterAfter() i still get 404.

As you can see in the tutorial, that's how login function is accessed:

curl -i -H "Content-Type: application/json" -X POST -d '{
    "username": "admin",
    "password": "password"
}' http://localhost:8080/login

Is it even possible to enable built-in login form for this purpose?

And if not, what's the sollution there? Do i have to create /login endpoint in controller, and then POST data to http://localhost:8080/login?

And what about added authenticationFilter. Does changes have to be made there?

1 Answers

You have two problems here:

  1. JWTAuthenticationFilter extends from UsernamePasswordAuthenticationFilter which by default responds to the URL /login. formLogin() also generates a login form in this URL. So, you have two places accepting input for /login. If you choose to do a custom login page (by .loginPage("/login") ) you have to do this in a different URL, and provide the HTML view to this page. But you said that you wanted to use the built-in login form. So, here comes another problem:
  2. To use built-in login form it has to be done by the default /login URL, so you have to change de URL of JWTAuthenticationFilter. It can be achieved by setting a custom URL in AbstractAuthenticationProcessingFilter as saw here. This works like a charm, but the implementation from JWTAuthenticationFilter is expecting as input an JSON, which is not provided by /login form (it send parameters in POST). So you have to change the code for JWTAuthenticationFilter.attemptAuthentication to decide if the input comes from a JSON body or parameters.

I implemented this in my environment and worked great. Below is the code (just the snippets):

WebSecurity:

public JWTAuthenticationFilter getJWTAuthenticationFilter() throws Exception {
    final JWTAuthenticationFilter filter = new JWTAuthenticationFilter(authenticationManager());
    filter.setFilterProcessesUrl("/api/auth/login");
    return filter;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
    http.cors().and().csrf().disable().authorizeRequests()
            .antMatchers(HttpMethod.POST, SIGN_UP_URL).permitAll()
            .anyRequest().authenticated()
            .and()
            .addFilter(getJWTAuthenticationFilter())
            .addFilter(new JWTAuthorizationFilter(authenticationManager()))                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .formLogin()
            .loginProcessingUrl("/api/auth/login")
            .permitAll();
}

JWTAuthenticationFilter:

@Override
public Authentication attemptAuthentication(HttpServletRequest req,
                                            HttpServletResponse res) throws AuthenticationException {
    try {
        ApplicationUser creds = null;

        if (req.getParameter("username") != null  && req.getParameter("password") != null) {
            creds = new ApplicationUser();              
            creds.setUsername(req.getParameter("username"));
            creds.setPassword(req.getParameter("password"));                
        } else {
            creds = new ObjectMapper()
                    .readValue(req.getInputStream(), ApplicationUser.class);
        }

        return authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(
                        creds.getUsername(),
                        creds.getPassword(),
                        new ArrayList<>())
        );
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}
Related