cookie之SameSite属性
问题描述:
前端调取后端返回base64编码格式字符串验证码的接口,response返回set-Cookie
Set-Cookie: laravel_session=eyJpdiI6IlNKbDdBeU5PdjBVOFJsYnpYSmRLRHc9PSIsInZh
bHVlIjoiekdRc2ZMQWpLNlJHTE9MM1RyRnhPUFVnN0RyNmRcLytnK1NFa1p3MElNMWNRODFTampG
ZjhGanVxenhOUldjc3oiLCJtYWMiOiI3NjE3MTg0MGU4NWM5ZDcyYzkzNjIwNDkxMGI0NGI4ZWE3
NmZjOWQ3OWQxMGNlOWRmYmI1ZGQwNzljZGVkMmMxIn0%3D; expires=Thu, 17-Sep-2020
08:37:53 GMT; Max-Age=7200; path=/; httponly
但无法在前端域名下创建cookie保存sessionId,导致登录接口取不到session存储的验证码
原因:
搜索chrome未携带cookie原因,发现cookie新属性:SameSite(chrome77版本后支持) 。对该属性的介绍可以参考这篇文章:SameSite。同时,查看chrome的cookie,由于没有指明SameSite的取值,chrome默认将其设置为了Lax:
解决方案:
1、设置response的header(推荐、必要条件HTTPS)
通过在后端中设置
("Set-Cookie", "HttpOnly;Secure;SameSite=None")
响应头可以看到设置的Set-Cookie,但是chrome提示:
由于设置SameSite为NONE时,需要同时设置secure,不设置secure无效,而设置secure之后,必须使用https来传输cookie。相对来说较为安全。
2、究极方案(修改Chrome配置)
在chrome中打开链接: chrome://flags/#site-isolation-trial-opt-out,搜索samesite
禁用相关配置即可
3、nginx反向代理解决
重要代码:
# 只支持 proxy 模式下设置,SameSite 不需要可删除,如果想更安全可以把 SameSite 设置为 Strict
proxy_cookie_path / "/; httponly; secure; SameSite=Lax";
示例:
server {
listen 443 ssl http2;
server_name www.cat73.org;
ssl_certificate /etc/letsencrypt/live/cat73.org/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/cat73.org/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/cat73.org/chain.pem;
add_header X-XSS-Protection "1; mode=block";
add_header X-Frame-Options SAMEORIGIN;
add_header Strict-Transport-Security "max-age=15768000";
location / {
root /var/www/html;
}
location /api {
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# 在这里设置
proxy_cookie_path / "/; httponly; secure; SameSite=Lax";
}
}
4、前端代理解决方案
前端框架:React App
调试环境在package.json 文件加入代理配置项
"proxy": "代理地址"
之后运行调试环境 npm run start
框架相关代码
// Load proxy config
const proxySetting = require(paths.appPackageJson).proxy;
const proxyConfig = prepareProxy(proxySetting, paths.appPublic);
总结:
存在即合理,SameSite的设计初衷是为了防止CSRF攻击,禁用SameSite实际上并没有解决问题,属于下下策。这里提供一下我的理解,SameSite为了防止CSRF攻击,加强了对cookie的管理,防止用户带着cookie去访问第三方网站,而这又涉及到了跨域问题。然而,我们不可能要求用户像我们一样去禁用新版chrome的SameSite,目前的建议就是在header中设置samesite,即上述的
("Set-Cookie", "HttpOnly;Secure;SameSite=None")
后,使用https传输cookie。
SameSite等级
1、Strict
Strict最为严格,完全禁止第三方 Cookie,跨站点时,任何情况下都不会发送 Cookie。换言之,只有当前网页的 URL 与请求目标一致,才会带上 Cookie。
Set-Cookie: CookieName=CookieValue; SameSite=Strict;
这个规则过于严格,可能造成非常不好的用户体验。比如,当前网页有一个 GitHub 链接,用户点击跳转就不会带有 GitHub 的 Cookie,跳转过去总是未登陆状态。
2、Lax
Lax规则稍稍放宽,大多数情况也是不发送第三方 Cookie,但是导航到目标网址的 Get 请求除外。
Set-Cookie: CookieName=CookieValue; SameSite=Lax;
导航到目标网址的 GET 请求,只包括三种情况:链接,预加载请求,GET 表单。详见下表。
请求类型 | 示例 | 正常情况 | 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 | 不发送 |
设置了Strict或Lax以后,基本就杜绝了 CSRF 攻击。当然,前提是用户浏览器支持 SameSite 属性。
3、None
Chrome 计划将Lax变为默认设置。这时,网站可以选择显式关闭SameSite属性,将其设为None。不过,前提是必须同时设置Secure属性(Cookie 只能通过 HTTPS 协议发送),否则无效。
下面的设置无效。
Set-Cookie: widget_session=abc123; SameSite=None
下面的设置有效
Set-Cookie: widget_session=abc123; SameSite=None; Secure
参考链接
1、链接1
2、链接2
3、链接3
4、链接4