28

I'm implementing a custom AngularJS login page for Spring Security and having issues authenticating.

Followed this tutorial/example, and their example works fine locally.

However, when I try to implement this myself, authentication fails. I'm not sure where my mistake is.

A POST is made to /login with credentials, (the curl is identical to the example), and I receive a 302 Found with a redirect to GET /login/, which returns a 404 Not Found.

When I try to POST to /login, Spring does not generate any debug logs. So I'm not sure how it is serving the 302.

My code can be found here:

Notable changes (And most likely the source of my issues):

  • File structure changes

  • Using strictly Angular (No jQuery) - Which results in a different function needed to make the POST request

  • Using bower instead of wro4j

  • Angular code styling/scoping

Many related Spring Security questions suggest the POST request is formatted incorrectly, but mine appears to be the same as the example (at least when I copy to curl in chrome dev console). Others suggest implementing custom authorization providers, but it is not needed in the example, so I'm perplexed to what the difference is between mine and the example. Help me Stack Exchange, you're my only hope.

Dev Tools: imgurDOTcom/a/B2KmV

Relevant code:

login.js

'use strict';
angular
    .module('webApp')
    .controller('LoginCtrl', ['$root`enter code here`Scope', '$scope', '$http', '$location', '$route', function($rootScope, $scope, $http, $location, $route) {
        console.log("LoginCtrl created.");

        var vm = this;
        vm.credentials = {
            username: "",
            password: ""
        };
        //vm.login = login;

        $scope.tab = function(route) {
            return $route.current && route === $route.current.controller;
        };

        var authenticate = function(callback) {

            $http.get('user').success(function(data) {
                console.log("/user success: " + JSON.stringify(data));
                if (data.name) {
                    console.log("And Authenticated!");
                    $rootScope.authenticated = true;
                } else {
                    console.log("But received invalid data.");
                    $rootScope.authenticated = false;
                }
                callback && callback();
            }).error(function(response) {
                console.log("/user failure." + JSON.stringify(response));
                $rootScope.authenticated = false;
                callback && callback();
            });

        };

        authenticate();

        $scope.login = function() {

            var data2 = 'username=' + encodeURIComponent(vm.credentials.username) +
                '&password=' + encodeURIComponent(vm.credentials.password);

            $http.post('login', data2, {
                headers : {
                    'Content-Type': 'application/x-www-form-urlencoded'
                }
            }).success(function() {
                authenticate(function() {
                    if ($rootScope.authenticated) {
                        console.log("Login succeeded");
                        $location.path("/");
                        $scope.error = false;
                        $rootScope.authenticated = true;
                    } else {
                        console.log("Login failed with redirect");
                        $location.path("/login");
                        $scope.error = true;
                        $rootScope.authenticated = false;
                    }
                });
            }).error(function() {
                console.log("Login failed");
                $location.path("/login");
                $scope.error = true;
                $rootScope.authenticated = false;
            })
        };

        $scope.logout = function() {
            $http.post('logout', {}).success(function() {
                $rootScope.authenticated = false;
                $location.path("/");
            }).error(function() {
                console.log("Logout failed");
                $rootScope.authenticated = false;
            });
        }

    }]);

application.java

package com.recursivechaos.springangularstarter;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.csrf.CsrfFilter;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.csrf.CsrfTokenRepository;
import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.WebUtils;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.security.Principal;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

@SpringBootApplication
@RestController
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @RequestMapping("/user")
    public Principal user(Principal user) {
        return user;
    }

    @RequestMapping("/resource")
    public Map<String, Object> home() {
        Map<String, Object> model = new HashMap<>();
        model.put("id", UUID.randomUUID().toString());
        model.put("content", "Hello World");
        return model;
    }

    @Configuration
    @Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
    protected static class SecurityConfiguration extends WebSecurityConfigurerAdapter {
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.
                formLogin().
                //loginPage("/#/login").
            and().
                logout().
            and().
                authorizeRequests().
                antMatchers("/index.html", "/home/**", "/login/**", "/bower_components/**", "/", "/main.js", "/login/", "/navigation/**","/login","login/","/login.html").
                permitAll().
                anyRequest().
                authenticated().
            and().
                csrf().
                csrfTokenRepository(csrfTokenRepository()).
            and().
                addFilterAfter(csrfHeaderFilter(), CsrfFilter.class);
        }

        private Filter csrfHeaderFilter() {
            return new OncePerRequestFilter() {
                @Override
                protected void doFilterInternal(HttpServletRequest request,
                                                HttpServletResponse response, FilterChain filterChain)
                    throws ServletException, IOException {
                    CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class
                        .getName());
                    if (csrf != null) {
                        Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN");
                        String token = csrf.getToken();
                        if (cookie == null || token != null
                            && !token.equals(cookie.getValue())) {
                            cookie = new Cookie("XSRF-TOKEN", token);
                            cookie.setPath("/");
                            response.addCookie(cookie);
                        }
                    }
                    filterChain.doFilter(request, response);
                }
            };
        }

        private CsrfTokenRepository csrfTokenRepository() {
            HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
            repository.setHeaderName("X-XSRF-TOKEN");
            return repository;
        }
    }

}
7
  • 1
    If you get redirected to /login that means you are not authenticated (but it shouldn't be a 404 so that's odd). You can switch on debug logging for org.springframework.security to get more detailed information about the access decision (I expect the credentials were bad in some way), e.g. set logging.level.org.springframework.security=DEBUG in application.[properties,yml]. Commented Feb 11, 2015 at 18:06
  • @DaveSyer Looking through the logs, I can't seem to even see a POST to /login How can I verify that Spring is handling POST /login? pastebin.com/GeUkCUvg Commented Feb 11, 2015 at 20:12
  • 1
    It appears to be a path issue (or possibly scope), as I was able to pull your "single" project from the github examples, remove the wro4j, and replace with the following bower dependencies: "angular": "^1.3.0", "angular-resource": "^1.3.0", "angular-bootstrap": "~0.12.0", "bootstrap-css-only": "~3.3.2", "angular-route": "~1.3.11" And using... var req = 'username=' + ($scope.credentials.username) + =' + ($scope.credentials.password); $http.post('login', req.... Seems to work fine Commented Feb 11, 2015 at 21:00
  • 1
    I also can't see a POST to /login but the log seemed to end in the middle of loading the homepage. Are you sure your client actually sent a POST anyway (can you see that in the client, and what are the request/response headers)? Commented Feb 11, 2015 at 22:14
  • 1
    OK, the 404 is obvious: your client is sending a GET to /login/ which you have declared to be permitAll() but haven't provided a view for. Spring Security provides a whitelabel view at /login (no trailing slash), and it only sends /login in 302 responses, so your client is behaving rather strangely. Commented Feb 12, 2015 at 10:15

5 Answers 5

1

Try adding WebSecuritConfigAdapter

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
            .authorizeRequests()
            .antMatchers("/**").permitAll()
            .anyRequest().authenticated();
    }
}
Sign up to request clarification or add additional context in comments.

Comments

0

There is one thing worng with login.js that it invokes authenticate() which calls /user and you get a redirect to GET /login/. Spring looks for login.jsp which is not there and end up with 404 Not Found.

You can make it work by taking following steps:

1) Remove invocation of authenticate() from line 38 in login.js

2) Add login processing URL like:

http.
     formLogin().
     loginProcessingUrl("/perform_login").
     and().
     logout()
 ....

3) Change your login URL to 'perform_login' like:

$http.post('perform_login', data2, {
            headers : {
                'Content-Type': 'application/x-www-form-urlencoded'
            }
        })....

and it works, you get the user.

Refer to http://www.baeldung.com/spring-security-login for spring security config.

Comments

0

This kind of error is most likely a Spring Security configuration problem.

when i read your spring security, 'loginPage' is commented.
Also your :

antMatchers("/index.html", "/home/**", "/login/**", "/bower_components/**", "/", "/main.js", "/login/", "/navigation/**","/login","login/","/login.html")

Seems weird to me.

antMatchers("/index.html", "/home**", "/login**", "/bower_components**", "/main.js", "/navigation**")

Should be fine.

And i'm not very fond of Angular, but your authenticate() method is called (just after it's definition) and it does a GET on 'user' which is not in your 'permitAll' matcher.

So consider doing this differently. Wether you add the matcher, which is not a good practice to permit user data free access. Or get the user info after you authenticate.

Cheers

Comments

0

Can you try adding an AuthenticationSuccessHandler to override the default Spring success handler which redirects requests

private AuthenticationSuccessHandler successHandler() {
    return new AuthenticationSuccessHandler() {
      @Override
      public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        httpServletResponse.getWriter().append("OK");
        httpServletResponse.setStatus(200);
      }
    };
  }

In your configuration add the authentication success handler

http.
                formLogin().successHandler(successHandler())
            and().
                logout().
            and().
                authorizeRequests().
                antMatchers("/index.html", "/home/**", "/login/**", "/bower_components/**", "/", "/main.js", "/login/", "/navigation/**","/login","login/","/login.html").
                permitAll().
                anyRequest().
                authenticated().
            and().
                csrf().
                csrfTokenRepository(csrfTokenRepository()).
            and().
                addFilterAfter(csrfHeaderFilter(), CsrfFilter.class);

Comments

0
  1. Enable more Spring logging: create application.properties and put:

logging.level.ROOT=DEBUG You'll see full authentication process details and actual error.

  1. You have CSRF protection enabled:

    and().csrf(). csrfTokenRepository(csrfTokenRepository()). and(). addFilterAfter(csrfHeaderFilter(), CsrfFilter.class);

    and CSRF token is extracted from cookie instead of url parameter:

    CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class .getName()); if (csrf != null) { Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN"); String token = csrf.getToken(); if (cookie == null || token != null && !token.equals(cookie.getValue())) {

So in that case you need to verify that cookie value is also provided with request.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.