后端使用cookie记录登录信息时,登录完成后响应头会携带Set-Cookie属性,浏览器会识别Set-Cookie并在满足一些条件后将Cookie注入浏览器中。
这里的请求它如果是跨域(前端域名和后端域名不一致)的,那么Cookie将无法注入。
1 前端设置
跨域请求的情况下,浏览器默认禁止携带cookie,如果想携带cookie,需要在请求时设置withCredentials
参数为true
,fetch请求的设置方法为method.config.credentials = 'include'
当请求的credentials
模式为include
时,响应头Access-Control-Origin
的值不可以是*
,也就是说服务端的cors插件的origin参数,必须指定具体域名,而不能是*
这种通配符
在这个设置后的Cookie注入情况
2 后端设置
前端设置完毕后,将开始设置后端,后端主要设置SameSite
SameSite
允许我们声明 cookie 可以用来 first-party 或者 same-site。它会明确的给出"网站"是指哪些(哪些可以带 cookie)。网站由域名后缀和域名前部分组成,比如www.web.dev
是web.dev
的一部分。
SameSite
属性提供了三种不同的方式来管理 cookie,你可以不指定这个属性(None
),或者使用Strict
和Lax
来限制 cookie 的 same-site 请求。
Strict
Strict
最为严格,完全禁止第三方 Cookie,跨站点时,任何情况下都不会发送 Cookie。换言之,只有当前网页的 URL 与请求目标一致,才会带上 Cookie。
这个规则过于严格,可能造成非常不好的用户体验。比如,当前网页有一个 GitHub 链接,用户点击跳转就不会带有 GitHub 的 Cookie,跳转过去总是未登陆状态。
Lax
Lax
规则稍稍放宽,大多数情况也是不发送第三方 Cookie,但是导航到目标网址的 Get 请求除外。
导航到目标网址的 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 属性。
None
这种情况下允许在 third-party 发送请求时使用 cookie
但是设置SameSite
为None
时需要同时设置Secure
,Cookie 只能通过 HTTPS 协议发送),否则无效。
bash
Set-Cookie: widget_session=abc123; SameSite=None; Secure
在我们的后端并没有设置HTTPS协议,故通过设置SameSite的方式完全无效了,我们选择通过代理的方式设置。
这块设置完后的Cookie注入情况
3 代理设置
在生产环境中,我们可以通过设置前端的Nginx代理服务器,设置后端服务反向代理到前端的域名上(或者前端反向代理到后端服务器上也是一样的)这样就从根源上解决了跨域问题。
Nginx配置如下
nginx
# 代替vite 中的代理服务 功能 反向代理后端域名到 前端
location /api {
proxy_pass http://xx.xx.xx.xx:xx;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
这个时候,请求前端域名http://localhost:5173/api
就相当于请求后端域名的http://xx.xx.xx.xx:xx/api
,注意Nginx是没有重写功能的,故必须配置后端的请求路径
yaml
servlet:
context-path: /api
这个时候就可以正常的注入Cookie了
开发环境中的前端配置,需要通过 Vite 的代理服务器实现,具体配置如下,基本都会的
js
server: {
// 指定dev sever的端口号,默认为5173
port: env.VITE_APP_PORT,
// 自动打开浏览器运行以下页面
// open: '/',
// 设置反向代理
proxy: {
// 以下示例表示:请求URL中含有"/api",则反向代理到http://localhost
// 例如: http://localhost:3000/api/login -> http://localhost/api/login
// 如果反向代理到localhost报错Error: connect ECONNREFUSED ::1:80,
// 则将localhost改127.0.0.1
'/api': {
target: env.VITE_API_URL,
changeOrigin: true,
// rewrite: (path) => path.replace(/^\/api/, ''),
},
},
},