Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can't create CSRF token with Spring Security

I am using Spring Security 3.2.3 in my Spring MVC application and getting some unexpected behavior.

According to the documentation here, it should be possible to use ${_csrf.token} in the meta tags of my html:

<meta name="_csrf" content="${_csrf.token}" />
<!-- default header name is X-CSRF-TOKEN -->
<meta name="_csrf_header" content="${_csrf.headerName}" />

From where I extract the value of "content" using JQuery and place it into the Request Header using AJAX.

For some reason though, Spring Security doesn't "convert" this into an actual token, it just gets sent into the header as a literal string "${_csrf.token}".

Trying the alternate route of using ${_csrf.token} in a hidden input according to the documentation, I then tried to check what the token evaluates to by checking the input's value, but it's still just plain text "${_csrf.token}".

Since it seems that Spring Security isn't in effect, am I missing some kind of configuration? I am currently using a barebones Spring Security Java configuration (as opposed to xml) as shown here:

import org.springframework.context.annotation.*;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.*;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
          .csrf();
      }
}

I know configure is getting called since I put a debug statement in it, so I assume that CSRF protection is indeed enabled since it should be by default.

I realize that the syntax "${}" is JSP Expression Language, and I am currently successfully using it to evaluate the context into an object with Thymeleaf, for example:

th:object="${context}"

So I tried adding "th:" in front of the meta tag's "content" like so:

<meta name="_csrf" th:content="${_csrf.token}"/>

But it results in an exception that this cannot be evaluated:

Exception evaluating SpringEL expression: "_csrf.token"

I think the key here may be figuring out how to get the expression to evaluate properly in my view.

like image 231
starmandeluxe Avatar asked May 15 '14 04:05

starmandeluxe


People also ask

How do I enable CSRF token in Spring Security?

3.1 Enabling CSRF Token in Spring Securitydisable() in your Spring security config class. With default setup, if you look at the source code of the page, you will see the _csrf parameter being added automatically to the form by Spring security.

How is CSRF token generated in spring?

You can obtain the CSRF using the request attribute named _csrf as outlined in the reference. To add the CSRF to an HTML page, you will need to use JavaScript to obtain the token that needs to be included in the requests.

Why CSRF token is not working?

The “Invalid or missing CSRF token” message means that your browser couldn't create a secure cookie or couldn't access that cookie to authorize your login. This can be caused by ad- or script-blocking plugins or extensions and the browser itself if it's not allowed to set cookies.

How CSRF is implemented in Spring Security?

To protect MVC applications, Spring adds a CSRF token to each generated view. This token must be submitted to the server on every HTTP request that modifies state (PATCH, POST, PUT and DELETE — not GET). This protects our application against CSRF attacks since an attacker can't get this token from their own page.


2 Answers

I finally solved this problem, but it basically required rewriting Spring Security. Here it is in all its glory.

First, I followed the suggestions in Eyal Lupu's great blog post here, but I had to tweak it to my situation because of my AJAX requirement.

As for the Thymeleaf situation, the key tidbit is hidden away in the archives of the Thymeleaf forums - Infamous Issue 7.

https://github.com/thymeleaf/thymeleaf-spring/issues/7#issuecomment-27643488

The last comment by the creator of Thymeleaf himself says that:

th:action ... detects when this attribute is being applied on a tag --which should be the only place, anyway--, and in such case calls RequestDataValueProcessor.getExtraHiddenFields(... ) and adds the returned hidden fields just before the closing tag.

That was the key phrase I needed to get the token to work. Unfortunately it's completely not obvious why th:action would also kick off getExtraHiddenFields, but at any rate it does, and that's what matters.

So for anyone struggling with Thymeleaf + Spring Security CSRF + AJAX POST, here are my steps (this is paring it down quite a bit but these are the high-level concepts to solve it):

  1. Implement the Spring interface RequestDataValueProcessor and register it in Spring Security's XML config so you can override the method getExtraHiddenFields, which allows you to insert a hidden input field into the HTML (with the token of course). The token itself is generated with a Java.Util UUID.

  2. With JQuery, read the value from that hidden field and set the Request Header's "X-CSRF-Token" attribute so that it gets sent over HTTP. It's not possible to simply leave the token in the hidden input field because we are not doing a form Submit, instead we use AJAX POST to call methods on the server side.

  3. Extend Spring's HandlerInterceptorAdapter and register it as an interceptor so that every time a POST method is done, the "preHandle" method on the server side is called so it can compare the request token (extracted from the HTTP header in the previous step) to the session's token (should be the same!). After it does this check, it can either allow the request to go through or return an error.

like image 130
starmandeluxe Avatar answered Oct 27 '22 15:10

starmandeluxe


I started with the same source article as you, I think, and the same "you should be able to" add answers as you did. I fought it a different way. I made Thymeleaf give me the answer I wanted.

<meta name="_csrf" th:content="${_csrf.token}"/>
<!-- default header name is X-CSRF-TOKEN -->
<meta name="_csrf_header" th:content="${_csrf.headerName}"/>

Thymeleaf put the attribute "content" with the requested Spring EL contents. I then used the provided JavaScript/JQuery to extract the info from the meta tags straight into the CSRF header.

like image 24
Tony Reynolds Avatar answered Oct 27 '22 15:10

Tony Reynolds