Manuel Jordan
Manuel Jordan

Reputation: 16271

Spring Security: How configure correctly CSRF for Ajax security control filter

I am working with Spring Security

The app has enabled the following:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
        .antMatchers("/resources/css/**", "/resources/images/**", "/resources/jquery/**", "/resources/js/**").permitAll()
        ... more URLs to intercept  
        .antMatchers("/notification**").hasAuthority("ROLE_ADMIN")
        .antMatchers("/ajax/notification**").hasAuthority("ROLE_ADMIN")
        .anyRequest().authenticated()

        .and()
        .csrf()
        ...

Observe CSRF is applied. For a form the following is used:

<c:url var="logoutUrl" value="/logout"/>
<form action="${logoutUrl}"
    method="post">
<input type="submit"
    value="Log out" />
<input type="hidden"
    name="${_csrf.parameterName}"
    value="${_csrf.token}"/>
</form>

It according Form Submissions

The app works fine how is expected, it intercepts any URL prior to load/render a jsp page and ask for the login control how is expected, if the user has logged the control about authorization is applied how is expected too. Therefore all is working with CSRF how is suggested.

Until here all is Ok.

I added ajax through jQuery and because it works with an URL, therefore I need apply the CSRF control for that URL too.

That's why above appears:

Now, I want get the expected HTTP error code and message when CSRF is applied to the ajax URL "/ajax/notification" and the CSRF headers were not send.

The js ajax code is:

    $.getJSON(
            "/projectname-01/ajax/notification",  
            function(data, textStatus, req) {

                console.log("data: " + data);
                console.log("textStatus: " + textStatus);
                console.log("req: " + req)

                console.log("data: " + data);
                console.log(data.content + " " + data.date);

                $("#single").empty();
                $("#single").append(data.content + " " + data.date);

                $("#multiple").append(data.content + " " + data.date)
                              .append("<br/>");

            }
    )

Note: The URL needs to be /projectname-01/ajax/notification, the projectname-01 is mandatory, if I remove that, I get the 404. It does not work even if ./ajax/notification is used (observe the dot).

Problem: The code works fine, I mean, the Ajax call happens to the server without any problem, I am expecting some error, it because the code is not using the CRSF requeriments for ajax.

It according from:

Thus, such as either:

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

or

<sec:csrfMetaTags />

and of course the js code working with:

_csrf_parameter, _csrf_header etc.

Then the expected HTTP error code and message is need it for testing and production purposes

Thus, what is missing? or what is the problem?

Headers: how was requested below the HTTP headers:

From Opera

General
  Request URL:http://localhost:8080/security-01/ajax/notification
  Request Method:GET
  Status Code:200 
  Remote Address:[::1]:8080
  Referrer Policy:no-referrer-when-downgrade
Response Headers
  HTTP/1.1 200
  X-Content-Type-Options: nosniff
  X-XSS-Protection: 1; mode=block
  Cache-Control: no-cache, no-store, max-age=0, must-revalidate
  Pragma: no-cache
  Expires: 0
  X-Frame-Options: DENY
  Content-Type: application/json;charset=UTF-8
  Transfer-Encoding: chunked
  Date: Sat, 23 Sep 2017 00:14:20 GMT
Request Headers
  GET /security-01/ajax/notification HTTP/1.1
  Host: localhost:8080
  Connection: keep-alive
  Accept: application/json, text/javascript, */*; q=0.01
  X-Requested-With: XMLHttpRequest
  User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36 OPR/47.0.2631.80
  Referer: http://localhost:8080/security-01/tiles/notification/
  Accept-Encoding: gzip, deflate, br
  Accept-Language: en-US,en;q=0.8
  Cookie: JSESSIONID=9F2007DA3C8C683D26B8D17D85563140; jenkins-timestamper-offset=18000000

From Firefox

Request Headers
  Host: localhost:8080
  User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:55.0) Gecko/20100101 Firefox/55.0
  Accept: application/json, text/javascript, */*; q=0.01
  Accept-Language: en-US,en;q=0.5
  Accept-Encoding: gzip, deflate
  Referer: http://localhost:8080/security-01/tiles/notification/
  X-Requested-With: XMLHttpRequest
  Cookie: JSESSIONID=CF344E56DD03C4019DCA334CD38B73EC
  Connection: keep-alive

Response Header
  X-Content-Type-Options: nosniff
  X-XSS-Protection: 1; mode=block
  Cache-Control: no-cache, no-store, max-age=0, must-revalidate
  Pragma: no-cache
  Expires: 0
  X-Frame-Options: DENY
  Content-Type: application/json;charset=UTF-8
  Transfer-Encoding: chunked
  Date: Sat, 23 Sep 2017 00:17:06 GMT

Clarification

According with Spring Security Reference Documentation we apply CSRF for a JSP page (submit form) and for Ajax, both work around with a URL. For the former I have already configured and works fine. The latter is the problem.

When I use jQuery's $.getJSON call without send the special CSRF data/headers values (<sec:csrfMetaTags />, meta[name='_csrf_parameter'] , etc), I mean from: 30.6 The csrfMetaTags Tag appears the following:

        // using JQuery to send an x-www-form-urlencoded request
        var data = {};
        data[csrfParameter] = csrfToken;
        data["name"] = "John";
        ...
        $.ajax({
            url: "http://www.example.org/do/something",
            type: "POST",
            data: data,
            ...
        });

        // using JQuery to send a non-x-www-form-urlencoded request
        var headers = {};
        headers[csrfHeader] = csrfToken;
        $.ajax({
            url: "http://www.example.org/do/something",
            type: "POST",
            headers: headers,
            ...
        });

From above it sends the CRSF information either from data or headers. But in my code I am not sending that. Therefore I expect some special control and error reported through Spring Security

Thus with my current ajax code, through development in runtime (Tomcat running with the app), the call to the server happens and data is returned. Thus Spring Security did not intercept and throw an error due the absence of these CRSF data/headers.

Therefore thinking now for development and testing, if I create @Test methods where should fail because the CSRF data/headers values were not send. The @Test methods are going to fail because the call to the server happens without any security control.

Upvotes: 0

Views: 863

Answers (1)

dur
dur

Reputation: 16969

Your HTTP GET request produces no error, because the CSRF token is only required for requests that update state, see Spring Security Reference:

18.2 Synchronizer Token Pattern

[...]
We can relax the expectations to only require the token for each HTTP request that updates state. This can be safely done since the same origin policy ensures the evil site cannot read the response. Additionally, we do not want to include the random token in HTTP GET as this can cause the tokens to be leaked.

and CsrfFilter:

Applies CSRF protection using a synchronizer token pattern. Developers are required to ensure that CsrfFilter is invoked for any request that allows state to change. Typically this just means that they should ensure their web application follows proper REST semantics (i.e. do not change state with the HTTP methods GET, HEAD, TRACE, OPTIONS).

To get a CSRF token you have to use HTTP POST, PUT or DELETE request.

Upvotes: 1

Related Questions