springboot解决谷歌80及以上版本的SameSite设置cookie失效

springboot解决谷歌80及以上版本的SameSite设置cookie失效

十月 29, 2020

前言

谷歌80新加了一个SameSite属性,防止跨域。但是就是由于这个新加的属性,我无法把cookie传到前端,搜罗了全网找到了两种解决方法,在此记录一下。
如果console出现下面这个,那cookie估计就没设置上:
在这里插入图片描述

A cookie associated with a cross-site resource at http://stu.hrbkyd.com/ was set without the SameSite attribute. It has been blocked, as Chrome now only delivers cookies with cross-site requests if they are set with SameSite=None and Secure. You can review cookies in developer tools under Application>Storage>Cookies and see more details at https://www.chromestatus.com/feature/5088147346030592 and https://www.chromestatus.com/feature/5633521622188032.

先看一下能正常设置cookie的request和response

addCookie的response:
在这里插入图片描述
getCookie的request:
在这里插入图片描述
可以看出添加cookie的response会有 Set-Cookie 字段,getCookie的request会有 Cookie 字段,有这两个字段才能添加成功。
下面来看springboot下的操作。

方法一

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
@GetMapping("/addCookie3")
@ResponseBody
public String addCookie3(@RequestParam("name") String name,@RequestParam("value") String value,HttpServletRequest request,HttpServletResponse response) {
System.out.println(name+" "+value);
Cookie cookie = new Cookie(name,value);
cookie.setDomain(domain);//域名,xxx.com
cookie.setHttpOnly(false);
cookie.setPath(request.getContextPath());
cookie.setMaxAge(60*60*24);
response.addCookie(cookie);
String s = name+"="+value+";";
response.setHeader("Set-Cookie",s + "Path=*; SameSite=None; Secure");
return "success";
}
@GetMapping("/getCookie")
@ResponseBody
public String getCookie(@RequestParam("name") String name,HttpServletRequest request) {
Cookie[] cookies = request.getCookies();
if (cookies == null) {
return "no cookies";
}
for (Cookie cookie : cookies) {
System.out.println(cookie.getName()+" "+cookie.getValue());
}
for (Cookie cookie : cookies) {
if (cookie.getName().equals(name)) {
return cookie.getValue();
}
}
return "no such cookie";
}

效果图:

说明:测试用页面的两个set按钮分别是添加cookie,一个为添加demoData=demoData,另一个为demoData2=damoData2,两个get是没别获取demoData和demoData2.

添加demoData:

在这里插入图片描述
可以看出response的Set-Cookie字段存在值。这里我的SameSite设置为了None,这样就必须再设置一个属性Secure,并且用https连接,这里是迫不得已,还可以设置为SameSite=Lax,这样就不用设置Secure,但是这样我无法get到值,所以采用None搭配Secure。

得到demoData:

在这里插入图片描述
本次我们发送的request也存在之前设置的cookie,并且也取到了值。
如下:
在这里插入图片描述
现在我们添加了一个cookie是成功了,这时我就在想,上面的代码是拼接的Set-Cookie字段,那么设置下一个cookie时要不要把之前的cookie也获取到,然后再拼接上新cookie呢?
比如说在设置一个demoData2=demoData2,那么这次用不用写成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
其实是不用的。这里在测试一下就可以,我们点击第二个set按钮,看看发生什么。
#### 添加demoData2
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200902145320718.PNG?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzE1NzY0NDc3,size_16,color_FFFFFF,t_70#pic_center)
可以看出,request发送时候带有demoData=demoData,response我们并没有像上面那么拼接,只拼接了demoData2,但是response自动带上了demoData。
#### 得到demoData2
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200902145515862.PNG?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzE1NzY0NDc3,size_16,color_FFFFFF,t_70#pic_center)
全部结果如下
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200902145601864.PNG#pic_center)
成功的取到了demoData2.
下面来看方法二
### 方法二
get方法是一样的,这里只贴出来addCookie
```java
@GetMapping("/addCookie2")
@ResponseBody
public String addCookie2(@RequestParam("name") String name,@RequestParam("value") String value,HttpServletRequest request,HttpServletResponse response) {
System.out.println(name+" "+value);
//new
HttpCookie cookie = CookieUtils.generateSetCookie(request, name, value,Duration.ofHours(24 * 7));//七天过期
response.setHeader(HttpHeaders.SET_COOKIE, cookie.toString());
return "success";
}

下面是CookieUtils

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class CookieUtils {
private final static String domain = "xxx.com";
public static HttpCookie generateSetCookie(HttpServletRequest request, String name, String value,Duration duration){
ResponseCookie cookie = ResponseCookie.from(name, value) // key & value
.secure(true) // 在https下传输,配合none使用
.domain(domain)// 域名
.path("*") // path
.maxAge(duration) // 过期时间
.sameSite("None")
.build()
;
return cookie;
}
}

具体情况和细节与方法一是一样的。

deleteCookie

有了添加的方法,删除就不难了,只需要把有效时间变为0就行。下面采用方法2来删除cookie。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@GetMapping("/deleteCookie")
@ResponseBody
public String deleteCookie(String names, HttpServletRequest request,HttpServletResponse response) {
System.out.println("names:"+names);
Cookie[] cookies = request.getCookies();
if (cookies == null) {
return "no cookies";
}
for (int i = 0; i < cookies.length; i++) {
if (names.equals(cookies[i].getValue())) {
ResponseCookie cookie = (ResponseCookie)CookieUtils.generateSetCookie(request,names,null,Duration.ZERO);//有效时间为0
response.setHeader(HttpHeaders.SET_COOKIE, cookie.toString());
break;
}
}
return "success";
}

设置session

cookie整明白了,session就好设置了,因为session是通过Set-Cookie的JSESSIONID设置的,只需要获取到JSESSIONID就行。

1
2
3
4
5
6
7
8
9
10
11
12
@GetMapping("/setSession")
@ResponseBody
public Result setSession(String str,HttpSession session,HttpServletRequest request,HttpServletResponse response) {
System.out.println(str);
session.setAttribute("str",str);
String id = session.getId();
HttpCookie cookie = CookieUtils.generateSetCookie(request, "JSESSIONID", id, Duration.ofHours(3));
response.setHeader(HttpHeaders.SET_COOKIE, cookie.toString());
Result result = new Result();
result.setCode(HttpStatus.OK.value());
return result;
}

最后附上测试用前端代码

注意如果设置SameSite=None,必须设置Secure,并且用https传输。如果是SameSite=Lax,则没有前面要求。

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.bootcdn.net/ajax/libs/axios/0.20.0-0/axios.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.js"></script>
</head>
<body>
<button id="btn1">按钮1(set)</button>
<button id="btn2">按钮2(get)</button>
<button id="btn3">按钮3(set)</button>
<button id="btn6">按钮6(get)</button>
<button id="btn4">按钮4(delete1)</button>
<button id="btn5">按钮4(delete2)</button>
<script>


let config = {headers: {'Content-Type': 'application/x-www-form-urlencoded'}};
let domain = "localhost"
$(function () {
axios.defaults.withCredentials = true;
$("#btn1").click(function () {
axios.get('https://xxx.com/test/addCookie?name=demoData&value=demoData').then(res => {
console.log(res.data);
});
});

$("#btn2").click(function () {
axios.get('https://xxx.com/test/getCookie?name=demoData').then(res => {
console.log(res.data);
});
})

$("#btn3").click(function () {
axios.get('https://xxx.com/test/addCookie?name=demoData2&value=demoData2').then(res => {
console.log(res.data);
});
});

$("#btn4").click(function () {
axios.get('https://xxx.com/test/deleteCookie?names=demoData').then(res => {
console.log(res.data);
});
});

$("#btn5").click(function () {
axios.get('https://xxx.com/test/deleteCookie?names=demoData2').then(res => {
console.log(res.data);
});
});

$("#btn6").click(function () {
axios.get('https://xxx.com/test/getCookie?name=demoData2').then(res => {
console.log(res.data);
});
})
})

</script>
</body>
</html>

后记

测试时候发现,/user下面的session会保存成功,但是到了/orderForm时,不会自动加上session,会在生成一个session,查看cookie就能发现有两个JSESSION,但是路径不一样。我的处理方式是在cookieUtil里面设置路径的path设置为”/“。

1
2
3
4
5
6
7
8
9
10
11
public static HttpCookie generateSetCookie(HttpServletRequest request, String name, String value,Duration duration) {
ResponseCookie cookie = ResponseCookie.from(name, value) // key & value
.secure(true) // 在https下传输,配合none使用
.domain(domain)// 域名
.path("/") // path
.maxAge(duration) // 过期时间
.sameSite("None") // 大多数情况也是不发送第三方 Cookie,但是导航到目标网址的 Get 请求除外Lax或者none
.build()
;
return cookie;
}