接上篇文章说道,跨域解决方案中的 CORS 方案,会配置一个
Access-Control-Allow-Origin的配置项,而且我们一般不直接配置为*,这样做的原因是什么以及企业中的最佳实践是怎么样的,这篇文章给你答案!
简单概括
Access-Control-Allow-Origin: *允许任意来源的页面读取你的响应内容(对浏览器端的 JS 可读性开放)。- 如果响应包含敏感数据或依赖 cookie/凭证(Authorization / session),绝不能 用
*。因为*与Access-Control-Allow-Credentials: true不能同用,浏览器也会拒绝这种组合;但即便没有 credentials,开放读取会增加数据泄露与滥用风险。 - 对于公共静态资源(可公开的图片、脚本、样式),用
*是可接受且常见的做法;但对API/用户数据/鉴权资源,企业级环境会严格限定 origin 并配合其他安全措施。
* 的具体风险
-
数据暴露风险任意第三方站点都能通过 AJAX 获取你的响应并在用户浏览器内读取到响应数据(如果该请求不需要 cookie)。
-
无法与 credentials 一起用如果你的 API 依赖 Cookie / HTTP Auth(
credentials: 'include'),你不能设置*。浏览器会阻止读取响应。这里我说的
credentials: 'include'是fetch 请求选项里的一个配置,用来控制 是否在跨域请求中携带凭证(如 Cookie、HTTP Auth(Authorization 请求头)、Client SSL 证书)。当然,我们之前说过的,跨域访问的话,服务端同时需要支持
Access-Control-Allow-Credentials: true。 -
缓存污染与中间件问题如果你对
Origin动态回显而未设置Vary: Origin,中间缓存(CDN、代理)可能将一个 origin 的响应误发给另一个 origin,造成安全/隐私漏洞。Origin是浏览器在 跨域请求 时自动携带的 请求头。比如:Origin:https://frontend.com。Vary是服务器的 缓存控制响应头。告诉缓存系统(如 CDN、浏览器缓存、负载均衡代理),当缓存这个响应时,需要 区分哪个请求头不同 时生成不同的缓存版本。比如:Vary: Origin,不同 Origin 来访问相同 URL,缓存系统要为他们分别缓存。
详述 Origin 和 Very
鉴于很多同学在工作中,并没有非常关注过这些配置,这里再详细讲讲。
先看常见的动态回显代码:res.setHeader("Access-Control-Allow-Origin", req.headers.origin);
这表示 后端根据请求方的 Origin 动态设置 CORS 允许域名。
但是 如果不设置 Vary: Origin:
缓存(如 CDN 或代理服务器)可能把第一个请求的响应缓存下来。举例:
- 用户 A(域名 a.com)访问 → 缓存:
Access-Control-Allow-Origin:https://a.com - 用户 B(域名 b.com)访问 → 本应得到:
Access-Control-Allow-Origin:https://b.com - 但由于缓存未区分,响应变成:
Access-Control-Allow-Origin:https://a.com
=> 跨域安全事故:b.com 拿到了 a.com 的权限配置缓存
正确做法:用白名单 + Vary
const allowList = ["https://a.com", "https://b.com"];const origin = req.headers.origin;
if (allowList.includes(origin)) {res.setHeader("Access-Control-Allow-Origin", origin);res.setHeader("Access-Control-Allow-Credentials", "true");res.setHeader("Vary", "Origin"); // 关键
}
企业推荐做法
- 返回具体 origin(白名单)或动态回显但要做严格白名单校验。必要时返回
Access-Control-Allow-Credentials: true并确保Access-Control-Allow-Origin为允许的具体域名而非*。如果必须动态回显Origin:务必设置 Vary: Origin。 - 对公共静态资源(图标、CDN 上的公开脚本、字体):可以使用
*。例如图片、CSS、JS(仅代表不包含敏感逻辑)通常可以*,便于 CDN 与第三方站点直接引用。 - 不要仅在客户端身份验证与鉴权,客户端防“君子”不防“小人”,必须在服务器端强制检查。不能把鉴权依赖于 CORS;CORS 只是浏览器的便利机制。对每个请求在后端都做 token/session 验证和权限检查。
- Cookie 策略与 SameSite(配合 CORS 使用),对跨站登录场景优先使用
SameSite=None; Secure,并在前端使用fetch(..., credentials: 'include')。但前提是Access-Control-Allow-Origin不能为*。 - 使用反向代理,将前端请求同域发到 Nginx(配置同上述示例中的服务端配置),然后由 proxy 转发到内部 API。这样避免 CORS 的复杂性并能统一做鉴权、限流、审计。
详述 SameSite=None; Secure
SameSite 是浏览器对 cookie 跨站发送 的限制策略,用来防止 CSRF 等跨站攻击。SameSite 有三种值:
比如:Set-Cookie: session_id=xxxx; SameSite=None; Secure; HttpOnly; Path=/
Secure:cookie 只能在 HTTPS 的环境中发送,而且现代浏览器必须要求:SameSite=None➜ 必须同时带 Secure 。HttpOnly:客户端 JS 无法读 cookie,防止 XSS 。Path=/:这个 Cookie 在整个网站(所有路径)都有效。比如,我们的网站是example.com,浏览器在example.com域下自动设置了 Cookie(Set-Cookie 已响应),之后只要访问example.com这个域下的任意路径,都会自动带上它。- 比如:
Set-Cookie: token=abc123; Path=/user,访问/user/xxx可以访问,但是访问/,/api等等的请求路径就不会携带 token 。
- 比如:
写到这里,有些同学就想问了,这么看来,cookie 挺好的呀,为什么后面逐渐又出现了 jwt 这种会话控制方案呢?他们的区别是什么呢?下一篇文章给你答案!