angularjsspring-securityangular-cookies

Spring Security cookie arrives Angular only after first forbidden request


I'm using Spring Security for securing my Spring Data REST webapplication with AngularJS.

My SecurityConfig is declared like this:

@Override
protected void configure(HttpSecurity http) throws Exception {

  http
    .httpBasic().and()
    .authorizeRequests()
    
    // public ressources
    .antMatchers("/index.html", "/templates/public/**", "/js/**", "/").permitAll()
    .antMatchers(HttpMethod.GET, "/api/security/user").permitAll()
    .antMatchers("/api/**").hasAnyRole("ADMIN", "NORMALUSER")
    
    .anyRequest().authenticated()
    .and().exceptionHandling().authenticationEntryPoint(basicAuthenticationEntryPointHandler)
    
    .and().logout().logoutUrl("/logout").logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler()).deleteCookies("JSESSIONID").permitAll().and()
    .csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
}

I generally followed this link for reaching my goal.

Now, if I would login myself for the first time, a angular-request for $cookies.get("XSRF-TOKEN") returns undefined and every request to my database isn't blocked.

But after logging out and logging in again, it returns me the cookie and every database-request is allowed.

And this happens only every second login, so:

  1. undefined

  2. cookie

  3. undefined

  4. cookie

  5. undefined

    ...

So now I hope that there's anyone who can help me without going deeper into my structure, but if it's required, I will do so.

Thanks in advance.

Edit 1: Here are the requests: enter image description here

In relation to the process: I'm logging the tokens in my CokieCsrfTokenRepository().loadToken(), and there all tokens are shown..

Edit 2: My Angular Service, which I'm calling by every login:

function authenticationService($rootScope, $http, $location, $filter, $cookies){    
    function authenticate(credentials, callback) {

        var headers = credentials ? {authorization : "Basic "
            + btoa(credentials.username + ":" + credentials.password)
        } : {};

        $http.get('api/security/user', {headers : headers}).success(function(data, status, get, header) {
            if (data.name) {
                $rootScope.authenticated = true;
                $rootScope.session = data;
                console.log($cookies.get("XSRF-TOKEN")) // -> returns every second login cookie
                
            } else {
                $rootScope.authenticated = false;
                $rootScope.session = null;
            }
            
            console.log($rootScope.session)
            
            callback && callback();
        }).error(function() {
            $rootScope.authenticated = false;
            
            callback && callback();
        });
        
    }
    
    return {
        authenticate: function(credentials){
            authenticate(credentials, function() {
                if ($rootScope.authenticated == true) {
                    $rootScope.authenticationError = false;
                    
                    if ($rootScope.session.principal.admin == true){
                        $location.path("/admin/manage");
                    } else{
                        $location.path("/user");
                    }
                } else {
                    $rootScope.authenticationError = true;
                    
                    $location.path("/login");
                }
            });
        },
        // called by refreshing browser
        checkLoggedIn: function(){
            authenticate();
        },
        
        logout: function(){
            $http.post('/logout', {})["finally"](function() {
                $rootScope.authenticated = false;
                $rootScope.session = null;
                $location.path("/login");
            });
        }
    };
}

Edit 3: I mentioned now, that if the cookie is undefined, the method loadToken() from this link is called only after logging out and refreshing the browser (first login). Then the token is shown and I'm logged in, again. But after every second try it still works great..

Edit 4: So no I recognized, that after the first forbidden POST-request (in Edit3 it's the /logout) the token arrives my template. After refreshing the browser, all my requests are allowed, because now the cookie will be send for every request. But how to fix this?


Solution

  • My solution:

    //WebSecurityConfigurerAdapter:
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
        ....
        .addFilterAfter(new CsrfCustomFilter(), CsrfFilter.class).and()
        .csrf().csrfTokenRepository(csrfTokenRepository());
    }
    
    private CsrfTokenRepository csrfTokenRepository() {
        HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
        repository.setHeaderName("X-XSRF-TOKEN");
        return repository;
    }
    
    public class CsrfCustomFilter extends 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);
        }
    }