简体   繁体   中英

Spring Boot and Security with custom AngularJS Login page

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

}

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

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.

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

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);
  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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM