解决跨域 Cookie
Source: Dev.to

问题
- 前端部署在 Vercel(不同域名)
- 后端(REST API 与 WebSocket)部署在 Render
由于域名不同,浏览器在 HTTP 请求中不会携带 Cookie。应用依赖 HttpOnly Cookie 来进行 REST API 认证和 WebSocket 授权。没有 Cookie,这两种机制都会失效。
修复 Cookie 设置
将 Cookie 的 sameSite 属性改为允许跨域发送即可。
// From:
sameSite: 'strict'
// To:
sameSite: 'none'
为了保持 Cookie 的安全性,还启用了 secure 标记(仅在 HTTPS 下发送)。这解决了 REST API 的认证问题,但仍有阻止第三方 Cookie 的浏览器会阻止 Cookie 发送,导致 WebSocket 连接同样失效。
将前端设为代理(同源)
第一个变通办法是通过 Vercel 前端代理 API 调用,使请求在浏览器看来是同源的。
// Before proxy
API_URL="https://message-app.render.com/:path"
// After proxy
API_URL="https://message-app.vercel.com/:path"
有了此更改,浏览器不再把 REST 请求视为跨域,认证得以成功。
对于 WebSocket 客户端,在握手时加入 withCredentials 选项以携带 Cookie:
const socket = io(URL, { withCredentials: true });
为什么 Cookie 仍然不到达 WebSocket
即使跨域环境配置正确,许多浏览器 在 WebSocket 握手期间也不会发送 Cookie。在此设置中:
- REST API 请求通过 Vercel 的代理(同源)。
- WebSocket 升级直接连接到 Render(跨域)。
因为握手是跨域的,浏览器会阻止 HttpOnly Cookie,导致认证失败。
将令牌存于内存并使用 Socket.io Auth
解决方案是不要在 WebSocket 连接中依赖 Cookie,而是:
- 从已认证的端点获取短期令牌(该端点会验证 HttpOnly Cookie)。
- 将令牌存入内存(React context、Redux 等)。
- 在握手时通过 Socket.io 的
auth参数传递令牌。
// Fetch token from authenticated endpoint, then pass explicitly
const socket = io(URL, {
auth: { token: authToken }
});
双重认证策略
- REST API:继续使用 HttpOnly Cookie(例如 5 天有效期)。
- WebSocket:使用单独的、短期的令牌(例如 1 小时),仅保存在内存中。
如果攻击者通过 XSS 获取了 WebSocket 令牌,泄露范围仅限于令牌的短暂有效期,并且在页面刷新后即失效。
要点
- 当前端和后端位于不同域名时,跨域 Cookie 需要
SameSite: 'none'和Secure: true。 - 即使 CORS 设置正确,浏览器仍可能在 WebSocket 握手时阻止 Cookie。
- 通过前端代理 API 调用可以解决 REST 的同源问题,但对 WebSocket 无效。
- 基于令牌的 WebSocket 认证(存于内存) 提供了可靠且安全的变通方案。
如果你在多个域名之间部署实时应用,预计需要实现类似的双重认证模式。