javaspring-bootspring-securityauth0spring-security-rest

Spring Security /login - 404 not found


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?


Solution

  • 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);
        }
    }