在 Spring Boot 应用中设置 Cookie 的 SameSite 属性-元一软件

未分类3个月前发布 元一软件
21 0

SameSite 是一个用于增强 Web 应用程序安全性的 Cookie 机制。它定义了浏览器在发送跨站请求时是否应该附加 Cookie。旨在防止跨站请求伪造(CSRF)攻击和某些类型的跨站信息泄露攻击。

开发工具

SameSite 属性是可选的,它有三个可选值:

  • Strict(严格模式):在严格模式下,浏览器只会在用户访问与 Cookie 关联的站点时发送 Cookie。如果请求来自其他站点(包括跨站请求),浏览器将不会发送 Cookie。这样可以有效地防止跨站请求伪造攻击。

  • Lax(宽松模式):在宽松模式下,大多数情况下,浏览器只会在用户访问与 Cookie 关联的站点时发送 Cookie。但是,如果用户从外部站点通过 GET 方法访问当前站点的 URL,浏览器会发送 Cookie。这样可以在某些常见的使用情况下保持用户体验,同时仍然提供一定程度的安全性。总结如下:

    请求类型 示例 正常情况 Lax
    链接 <a href=...></a> 发送 Cookie 发送 Cookie
    预加载 <link rel=prerender href=.../> 发送 Cookie 发送 Cookie
    GET 表单 <form method=GET action=...> 发送 Cookie 发送 Cookie
    POST 表单 <form method=POST action=...> 发送 Cookie 不发送
    iframe <iframe src=...></iframe> 发送 Cookie 不发送
    AJAX $.get(...) 发送 Cookie 不发送
    Image <img src=...> 发送 Cookie 不发送
  • None(无限制模式):在无限制模式下,浏览器会在跨站请求时始终发送 Cookie。这意味着即使请求来自其他站点,浏览器也会发送 Cookie。这种模式需要慎重使用,因为它可能导致安全风险,可能会被滥用。

    必须同时设置 Cookie 的 Secure 属性(表示 Cookie 只会在 HTTPS 协议中传输),如:SameSite=None; Secure,否则无效。

    软件

本文将会带你了解如何在 Spring Boot 应用中设置 Cookie 的 SameSite 属性。

参考资料:

通常,我们在 Spring Boot 应用中通过 Servlet 的 Cookie 对象来设置 Cookie 到浏览器。如下:

@GetMapping

public void setCookie (HttpServletResponse response) {



    // 创建 Cookie

    Cookie cookie = new Cookie("Hello", "Spring 中文网");

    cookie.setMaxAge(-1); // 浏览器关闭,则删除 Cookie

    cookie.setSecure(true); // 仅在 HTTPS 协议中传输

    cookie.setHttpOnly(true); // Javascript 不能读写

// cookie.setDomain(null); // 提交 cookie 的域

// cookie.setPath(null);  // 提交 cookie 的 path

    

    //添加 cookie 到客户端

    response.addCookie(cookie);

}

但是,直到撰稿时最新版的 JakartaEE 6 Servlet Api 中的 Cookie 类,仍然未实现这个规范。所以目前,通过这种方式,我们无法设置 Cookie 的 SameSite 属性。

Cookie 本质上也是通过 Set-Cookie 响应头进行设置的,因此我们可以自定义 Set-Cookie 响应头来设置 Cookie,从而可以实现设置 SameSite 属性。

字典与百科全书

对于这么普遍的需求,Spring 早已想到。它提供了一个 ResponseCookie 工具类,可以让我们通过设置 Header 的方式来设置 Cookie。

使用方式很简单,我们创建一个演示 Controller 来进行测试。如下:

package cn.springdoc.demo.controller;



import org.springframework.boot.web.server.Cookie.SameSite;

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;



import jakarta.servlet.http.HttpServletResponse;



@RestController

@RequestMapping("/test/cookie")

public class DemoController {

	

    @GetMapping

    public void setCookie (HttpServletResponse response) {

        

        // key & value

        ResponseCookie cookie = ResponseCookie.from("Hello", "World")

                .maxAge(-1)			// 浏览器关闭,则删除 Cookie

                .secure(false)		// 可以在 HTTP 协议中传输

                .httpOnly(true)		// Javascript 不能读写

    //				.domain(null)		// 提交 cookie 的域

    //				.path(null)			// 提交 cookie 的path

                .sameSite(SameSite.LAX.attributeValue())	// 设置 SameSite 为 LAX

                .build()

                ;

        

        // 设置Cookie

        response.setHeader(HttpHeaders.SET_COOKIE, cookie.toString());

    }

}

SameSite 枚举是 org.springframework.boot.web.server.Cookie 的内部类,它定义了 SameSite 的枚举值,如下:

编程

public enum SameSite {

    NONE("None"),

    LAX("Lax"),

    STRICT("Strict");



    private final String attributeValue;

    SameSite(String attributeValue) {

        this.attributeValue = attributeValue;

    }

    public String attributeValue() {

        return this.attributeValue;

    }

}

测试

启动应用,打开浏览器,打开控制台,访问 http://localhost:8080/test/cookie 端点。

观察网络面板中该请求的响应原文,如下:

HTTP/1.1 200 

Set-Cookie: Hello=World; HttpOnly; SameSite=Lax

Content-Length: 0

Date: Wed, 13 Sep 2023 09:57:54 GMT

Keep-Alive: timeout=60

Connection: keep-alive

如你所见,其中 Set-Cookie: Hello=World; HttpOnly; SameSite=Lax 表示已经成功设置了 Cookie,且 SameSite 属性为 Lax。

© 版权声明