CSRF/XSRF protection for Spring Security and AngularJS

7.9k views Asked by At

I tried to add CSRF/XSRF protection to my application, but ran into strange behavior. All get requests work fine, but on all post/put/delete I'm getting 403 Unauthorized. And the strangest thing is that when I tried to debug my CSRF filter, requests do not reach it, they are rejected somewhere earlier. They do not even reach my authentication filter, so I can not figure out what the problem may be.

My security config:

        @Override
        public void configure(HttpSecurity http) throws Exception {
            http
                    ...
                    .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                    .and()
                    .addFilterBefore(new StatelessAuthenticationFilter(tokenAuthenticationService()), UsernamePasswordAuthenticationFilter.class)
                    .addFilterAfter(new CsrfHeaderFilter(), CsrfFilter.class)
                    .csrf().csrfTokenRepository(csrfTokenRepository());
        }

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

I do not add the filters since as I said, the requests do not reach them. But if needed I will complete my question. I hope for your help, thank you in advance!

3

There are 3 answers

0
Anar Sultanov On BEST ANSWER

Many thanks for the answers, they really helped me to find a solution. And I want to share my solution if in the future someone will face the same issue.

As noted in the answers I used SessionCreationPolicy.STATELESS and did not have sessions so instead of HttpSessionCsrfTokenRepository I had to use CookieCsrfTokenRepository with withHttpOnlyFalse() to allow AngularJS to read cookies.

As a result, I have a configuration like this:

@Override
public void configure(HttpSecurity http) throws Exception {
    http
            ...
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .addFilterBefore(new StatelessAuthenticationFilter(tokenAuthenticationService()), UsernamePasswordAuthenticationFilter.class)
            .addFilterAfter(new CsrfHeaderFilter(), CsrfFilter.class)
            .csrf().csrfTokenRepository(csrfTokenRepository());
}

If someone is interested in how the CsrfHeaderFilter looks:

public class CsrfHeaderFilter 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);
    }
}

My second problem was CORS. AngularJS documentation says:

"The header will not be set for cross-domain requests."

To solve this problem, I had to use an HTTP Interceptor:

.factory('XsrfInterceptor', function ($cookies) {
  return {
    request: function (config) {
      var headerName = 'X-XSRF-TOKEN';
      var cookieName = 'XSRF-TOKEN';
      config.headers[headerName] = $cookies.get(cookieName);
      return config;
    }
  };
});

.config(['$httpProvider', function($httpProvider) {  
    $httpProvider.interceptors.push('XsrfInterceptor');
}]);

I hope my answer will be useful.

0
zakaria amine On

In principle, the CSRF mechanism in Spring stores the CSRF token in a HTTP only cookie. Because JavaScript cannot access a HTTP only cookie, you need to tell spring to disable HTTP only:

.and().csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());

Then you can read the cookie from Angular and add it to the XSRF-TOKEN header with each request.

This is the general case. I am not sure if this fits your special case.

3
Leffchik On

Assuming that the rest of your config/filters working properly, you're facing this issue because of this: SessionCreationPolicy.STATELESS.

You can have a look under the hood of Spring CsrfFilter. You'll see that it needs to remember the value of each CSRF-token for each user inside a session, and since you are not using sessions it can't be done.

What to do next - is really up to you. Some people saying that if you app is stateless there is actually no need for CSRF protection. Spring docs saying that CSRF attacks are still relevant. I think it really depends on your authentication mechanism.

You might also want to look at this nice article, for example.

Hope it helps.