Starting with Chrome 51, a new SameSite attribute has been added to the browser cookie to prevent CSRF attacks and user tracking.

SameSite

1. What is a CSRF attack?

Cookies are often used to store a user’s identity information, and a malicious website can manage to forge an HTTP request with the correct cookie, which is a CSRF attack.

For example, a user logs into the bank’s website your-bank.com and a cookie is sent from the bank’s server.

1
Set-Cookie:id=a3fWa;

The user then visits the malicious website malicious.com with a form on it.

1
2
3
<form action="your-bank.com/transfer" method="POST">
  ...
</form>

Once a user is tricked into sending this form, the bank’s website will receive the request with the correct cookie. To prevent this attack, the form is usually accompanied by a random token that tells the server that this is a genuine request.

1
2
3
4
<form action="your-bank.com/transfer" method="POST">
  <input type="hidden" name="token" value="dad3weg34">
  ...
</form>

This type of cookie, known as a third-party cookie, is used in addition to CSRF attacks and can be used for user tracking.

For example, Facebook inserts an invisible image into a third-party website.

1
<img src="facebook.com" style="visibility:hidden;">

When the browser loads the above code, it sends a request to Facebook with a cookie so that Facebook will know who you are and what websites you visit.

2. SameSite Property

The SameSite property of a cookie is used to restrict third-party cookies and thus reduce security risks.

It can be set to three values.

  • Strict
  • Lax
  • None

2.1 Strict

Strict is the most restrictive, forbidding third-party cookies altogether and not sending cookies under any circumstances when crossing sites; in other words, only if the URL of the current page is the same as the target of the request will it carry a cookie.

1
Set-Cookie: CookieName=CookieValue; SameSite=Strict;

This rule is too strict and can cause a very bad user experience. For example, if there is a GitHub link on the current page, the user will not submit a GitHub cookie when they click on the link to enter the Github site, resulting in a non-logged-in state when entering Github from the current page.

2.2 Lax

The Lax rule is slightly relaxed, and in most cases no third-party cookies are sent, except for Get requests that navigate to the target URL.

1
Set-Cookie: CookieName=CookieValue; SameSite=Lax;

GET requests that navigate to the target URL include only three cases: links, preload requests, and GET forms. See the table below for details.

Type Example Normal situation Lax
Link <a href="..."></a> Send Cookie Send Cookie
preload <link rel="prerender" href="..."/> Send Cookie Send Cookie
GET Form <form method="GET" action="..."> Send Cookie Send Cookie
POST Form <form method="POST" action="..."> Send Cookie Not sent
iframe <iframe src="..."></iframe> Send Cookie Not sent
AJAX $.get("...") Send Cookie Not sent
Image <img src="..."> Send Cookie Not sent

After setting Strict or Lax, CSRF attacks are basically eliminated. Of course, this assumes that the user’s browser supports the SameSite property.

2.3 None

Chrome plans to make Lax the default setting. In this case, sites can choose to explicitly turn off the SameSite property by setting it to None. However, this is only possible if the Secure property is also set (cookies can only be sent over the HTTPS protocol), otherwise it will not work.

The following setting is not valid.

1
Set-Cookie: widget_session=abc123; SameSite=None

The following settings are valid.

1
Set-Cookie: widget_session=abc123; SameSite=None; Secure

3. Spring Application

We generally set cookies through the javax.servlet.http.Cookie object provided by the Servlet, but it does not currently implement the SameSite property.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Cookie implements Cloneable, Serializable {
  ...

  //
  // The value of the cookie itself.
  //

  private String name; // NAME= ... "$Name" style is reserved
  private String value; // value of NAME

  //
  // Attributes encoded in the header's cookie fields.
  //

  private String comment; // ;Comment=VALUE ... describes cookie's use
  // ;Discard ... implied by maxAge < 0
  private String domain; // ;Domain=VALUE ... domain that sees cookie
  private int maxAge = -1; // ;Max-Age=VALUE ... cookies auto-expire
  private String path; // ;Path=VALUE ... URLs that see the cookie
  private boolean secure; // ;Secure ... e.g. use SSL
  private int version = 0; // ;Version=1 ... means RFC 2109++ style
  private boolean isHttpOnly = false;

  ...
}

ResponseCookie

As we all know, a cookie is actually an HttpHeader only.

Spring provides a tool class ResponseCookie that can be used to write a cookie to the client with the SameSite property. it is also very simple to use.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package io.springboot.demo.web.controller;

import java.time.Duration;

import javax.servlet.http.HttpServletResponse;

import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseCookie;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/demo")
public class DemoController {

  @GetMapping(produces = "text/plain")
  public String demo (HttpServletResponse response) {
    
    // Building cookies
    ResponseCookie cookie = ResponseCookie.from("myCookie", "myCookieValue") // key & value
        .httpOnly(true)
        .secure(false)
  //    .domain("localhost")  // host
  //    .path("/")      // path
        .maxAge(Duration.ofHours(1))
        .sameSite("Lax")  // sameSite
        .build()
        ;
    
    // Response to the client
    response.setHeader(HttpHeaders.SET_COOKIE, cookie.toString());
    
    return "ok";
  }
}

Request this Controller and you will get the following response.

1
2
3
4
5
6
7
8
9
HTTP/1.1 200 OK
Connection: keep-alive
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Set-Cookie: myCookie=myCookieValue; Max-Age=3600; Expires=Thu, 28 Apr 2022 08:34:16 GMT; HttpOnly; SameSite=Lax
Content-Type: text/plain;charset=UTF-8
Content-Length: 2
Date: Thu, 28 Apr 2022 07:34:16 GMT

As you can see, a cookie with a SameSite value of Lax was successfully set.

HttpSession relies on a cookie with the name JSESSIONID (default name).

For setting the JSESSIONID cookie, you can modify the following configuration to set the SameSite property.

1
server.servlet.session.cookie.same-site=Lax

Note that if your SpringBoot version is less than 2.6, then you cannot use this configuration.

SpringBoot version below 2.6

If you use Tomcat as the server, then you can set the SameSite property of the session cookie by the following configuration.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import org.apache.tomcat.util.http.Rfc6265CookieProcessor;
import org.apache.tomcat.util.http.SameSiteCookies;
import org.springframework.boot.web.embedded.tomcat.TomcatContextCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class TomcatConfiguration {
  @Bean
  public TomcatContextCustomizer sameSiteCookiesConfig() {
    return context -> {
      final Rfc6265CookieProcessor cookieProcessor = new Rfc6265CookieProcessor();
      // SameSite
      cookieProcessor.setSameSiteCookies(SameSiteCookies.LAX.getValue());
      context.setCookieProcessor(cookieProcessor);
    };
  }
}

If you are using Spring-Session then you can use the following configuration to set the SameSite property of the cookie.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.session.web.http.CookieSerializer;
import org.springframework.session.web.http.DefaultCookieSerializer;

@Configuration
public class SpringSessionConfiguration {

  @Bean
  public CookieSerializer cookieSerializer() {
    DefaultCookieSerializer serializer = new DefaultCookieSerializer();
    serializer.setCookieName("JSESSIONID");
    serializer.setDomainName("localhost");
    serializer.setCookiePath("/");
    serializer.setCookieMaxAge(3600);
    serializer.setSameSite("Lax");  // SameSite
    serializer.setUseHttpOnlyCookie(true);
    serializer.setUseSecureCookie(false);
    return serializer;
  }
}

Reference https://www.ruanyifeng.com/blog/2019/09/cookie-samesite.html