2

I am developing a web application which has a front-end with angularjs and a back-end with spring-security and jersey.

I am trying to implement spring-security. I can authenticate the user. But I stucked at the logout point. I am sending the X-CSRF-TOKEN within a value, but it seems that spring-security is refusing it.

web.xml

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>


    <display-name>M2Carros</display-name>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            classpath:applicationContext.xml
            classpath:spring-security.xml
        </param-value>
    </context-param>

    <listener>
        <listener-class>
            org.springframework.web.context.ContextLoaderListener
        </listener-class>
    </listener>

    <servlet>
        <servlet-name>jersey-serlvet</servlet-name>
        <servlet-class>
            com.sun.jersey.spi.spring.container.servlet.SpringServlet
        </servlet-class>
        <init-param>
            <param-name>com.sun.jersey.config.property.packages</param-name>
            <param-value>br.com.m2carros</param-value>
        </init-param>
        <init-param>
            <param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
            <param-value>true</param-value>
        </init-param>
        <init-param>
            <param-name>com.sun.jersey.spi.container.ContainerRequestFilters</param-name>
            <param-value>com.sun.jersey.api.container.filter.LoggingFilter</param-value>
        </init-param>
        <init-param>
            <param-name>com.sun.jersey.spi.container.ContainerResponseFilters</param-name>
            <param-value>com.sun.jersey.api.container.filter.LoggingFilter</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>jersey-serlvet</servlet-name>
        <url-pattern>/api/*</url-pattern>
    </servlet-mapping>

<!--    Spring Security -->
    <filter>
        <filter-name>

    springSecurityFilterChain</filter-name>
            <filter-class>org.springframework.web.filter.DelegatingFilterProxy

</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>springSecurityFilterChain</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <filter>
    <filter-name>CorsFilter</filter-name>
    <filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
        <init-param>
            <param-name>cors.allowed.headers</param-name>
            <param-value>Content-Type,X-Requested-With,accept,Origin,Access-Control-Request-Method,Access-Control-Request-Headers,Authorization</param-value>
        </init-param> 
        <init-param>
            <param-name>cors.exposed.headers</param-name>
            <param-value>Content-Type,X-Requested-With,accept,Origin,Access-Control-Request-Method,Access-Control-Request-Headers,Authorization,X-CSRF-TOKEN</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CorsFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
</web-app>

spring-security.xml

<beans:beans xmlns="http://www.springframework.org/schema/security"
    xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/security
    http://www.springframework.org/schema/security/spring-security-3.2.xsd">

    <!-- enable use-expressions -->
    <http auto-config="true" use-expressions="true">
        <intercept-url pattern="/" access="permitAll" />
        <intercept-url pattern="/index.html" access="permitAll" />
        <intercept-url pattern="/api/user" access="isAuthenticated()" />
        <!-- enable csrf protection -->
        <csrf/>
    </http>

    <!-- Select users and user_roles from database -->
    <authentication-manager>
        <authentication-provider>
<!--            <password-encoder hash="md5" />  -->
            <jdbc-user-service data-source-ref="dataSource"
                users-by-username-query=
                    "select username,password, enabled from usuario where username=?"
                authorities-by-username-query=
                    "select username, role from user_roles where username =?  " />
        </authentication-provider>
    </authentication-manager>

</beans:beans>

app.js (ommited routes)

$httpProvider.defaults.headers.common["X-Requested-With"] = 'XMLHttpRequest';
    var csrfHeaderName = 'X-CSRF-TOKEN';

    $httpProvider.interceptors.push(function() {
        return {
            response: function(response) {
                console.log(response.headers());
                console.log(response.headers(csrfHeaderName));
                if(response.headers(csrfHeaderName) != null){
                    $httpProvider.defaults.headers.common[csrfHeaderName] = response.headers(csrfHeaderName);
                }
                return response;
            }
        }    
    });

appCtrl.js

angular.module('m2App').controller('appCtrl', function($rootScope, $scope, $http, $location){

    var serverUrl = 'http://localhost:8080/m2carros/api';

    var authenticate = function(credentials, callback) {

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

        $http.get(serverUrl+'/user', {headers : headers}).then(function(response) {
          if (response.data.principal != undefined && response.data.principal.username) {
            $rootScope.authenticated = true;
            console.log("is authenticated ? "+$rootScope.authenticated);
          } else {
            $rootScope.authenticated = false;
            console.log("is authenticated ? "+$rootScope.authenticated);
          }
          callback && callback();
        }, function() {
          $rootScope.authenticated = false;
          console.log("is authenticated ? "+$rootScope.authenticated);
          callback && callback();
        });

      }


      authenticate();
      $scope.credentials = {};
      $scope.login = function() {
          authenticate($scope.credentials, function() {
            if ($rootScope.authenticated) {
              $location.path("/");
              console.log("Redirecionando usuario autenticado para /")
              self.error = false;
            } else {
              $location.path("/login");
              self.error = true;
            }
          });
      };

      $rootScope.logout = function() {
          $http.post('logout', {}).then(function() {
            $rootScope.authenticated = false;
            $location.path("/");
          });
        }

});

Authenticating User

Trying to Logout

brazuka
  • 1,011
  • 4
  • 13
  • 22
  • Does this answer your question? [Invalid CSRF Token 'null' was found on the request parameter '\_csrf' or header 'X-CSRF-TOKEN'](https://stackoverflow.com/questions/21128058/invalid-csrf-token-null-was-found-on-the-request-parameter-csrf-or-header) – singhpradeep Mar 01 '22 at 09:17

2 Answers2

3

XSRF is a technique by which an unauthorized site can gain your user's private data. Angular provides a mechanism to counter XSRF. When performing XHR requests, the $http service reads a token from a cookie (by default, XSRF-TOKEN) and sets it as an HTTP header (X-XSRF-TOKEN).

If you set appropriate cookie, then it ensures that angular will take care of the header internally

So on that case, you need to check that server config won't need a new token each request

You need to send the csrf token when you submit your form. You need to add the following line in your HTML form:

<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>

Resource Link:

  1. Spring Rest Service - Invalid CSRF token when I attempt to login

As CodeMed suggested to add

.antMatchers("/send-pin").permitAll()

in SecurityConfiguration class. He got some issue as stated below:

To examine the Network tab of the Firefox debug tools, which showed that the following two cookies were sent with the request: JSESSIONID:"99192501E7CEA0EDEF853BD666AF3C35" and XSRF-TOKEN:"b50afb87-e15c-4bef-93ca-7c2fdf145fd8", even though the server log for the same request still boiled down to Invalid CSRF token found for http://localhost:9000/send-pin . This caused me to examine why the sent token was being rejected, and a few minutes later I noticed the missing antmatchers(...) for the url pattern, leading to this answer.

This change caused SecurityConfiguration.configure(...) method to now look like:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.httpBasic().and().authorizeRequests()
        .antMatchers("/send-pin").permitAll() 
        .antMatchers("/check-pin").permitAll()
        .antMatchers("/index.html", "/", "/login", "/someotherrurl") 
        .permitAll().anyRequest().authenticated().and().csrf()
        .csrfTokenRepository(csrfTokenRepository()).and()
        .addFilterAfter(csrfHeaderFilter(), CsrfFilter.class);
}  

Resource Link:

  1. How do I send CSRF tokens from AngularJS front end to Spring REST service backend?
  2. Spring Security - Token based API auth & user/password authentication
Community
  • 1
  • 1
SkyWalker
  • 28,384
  • 14
  • 74
  • 132
  • Now my backend return a X-CSRF-TOKEN (cookie) and a X-CSRF-TOKEN (header) with the generated values. And in my interceptor I get the header value from the response and set it in the next request. So, now I am sending a $http.post with a X-CSRF-TOKEN (cookie) and a X-CSRF-TOKEN (header) but I am still getting the same error. I've just added a but still get the same error. – brazuka May 17 '16 at 02:44
0

For those who are facing the same problem. Here is my solution.

  1. First I wasn't able to get the cookie using $cookies.get(). It's because that the cookie that I was returning from the backend didn't had a path configured.
  2. After the authentication where I get a token I make many other http requests and I was discarding the tokens returned by these requests. So, when Spring Security compared the tokens It used the authentication token which was invalid and not the token from the last request.

Hope this help someone.

brazuka
  • 1,011
  • 4
  • 13
  • 22