今天在配置Umami(站点访问统计面板)时遇到了跨域访问控制权限的问题,今天借这篇文章来解释一下何为CORS
Error 1
Access to fetch at 'https://umami.timberfirear.cc/api/send' from origin 'http://localhost:4321' has been blocked by CORS policy:
Response to preflight request doesn't pass access control check:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
这个报错本质是 umami.timberfirear.cc 没有给 localhost:4321 返回 CORS 许可头,而且预检 OPTIONS 也没通过
需要进行以下的操作:
- 允许预检请求
OPTIONS - 在
OPTIONS和POST响应里返回这些头:
Access-Control-Allow-OriginAccess-Control-Allow-MethodsAccess-Control-Allow-HeadersAccess-Control-Max-AgeVary: Origin
当前还有一层来源限制(只允许 timberfirear.cc),所以本地 http://localhost:4321 会被拦。
有两种处理方式:
- 生产推荐:本地不注入 Umami(只在
import.meta.env.PROD注入) - 本地联调:把
localhost:4321加入 Nginx 允许来源,并给/api/send增加完整 CORS 头和OPTIONS 204响应
可直接用在 location = /api/send 的关键逻辑是:
if ($request_method = OPTIONS) { ... return 204; }
add_header Access-Control-Allow-Origin $http_origin always
add_header Access-Control-Allow-Methods "POST, OPTIONS" always
add_header Access-Control-Allow-Headers "content-type" always
add_header Vary "Origin" always
Error 2
Access to fetch at 'https://umami.timberfirear.cc/api/send' from origin 'https://timberfirear.cc' has been blocked by CORS policy:
The 'Access-Control-Allow-Origin' header contains multiple values '*, https://timberfirear.cc', but only one is allowed.
Have the server send the header with a valid value.
响应头重复导致的冲突。
现在同一个响应里有两个 Access-Control-Allow-Origin:
- Umami 上游返回了
* - Nginx 又加了
https://timberfirear.cc
浏览器只允许一个值,所以直接拦截。
修复方式:在 location = /api/send 里先隐藏上游 CORS 头,再由 Nginx 只返回一份。
加这几行(放在 proxy_pass 前):
proxy_hide_header Access-Control-Allow-Origin;
proxy_hide_header Access-Control-Allow-Methods;
proxy_hide_header Access-Control-Allow-Headers;
proxy_hide_header Access-Control-Max-Age;
proxy_hide_header Vary;
然后保留现在的 add_header ... 规则即可。
验证目标:
/api/send 响应里只剩一个 Access-Control-Allow-Origin: https://timberfirear.cc。
Error 3
Access to fetch at 'https://umami.timberfirear.cc/api/send' from origin 'https://timberfirear.cc' has been blocked by CORS policy:
Request header field x-umami-cache is not allowed by Access-Control-Allow-Headers in preflight response.
这是预检请求被自己的 CORS 白名单拦了:
浏览器现在会带 x-umami-cache 请求头,但你在 Nginx 里 Access-Control-Allow-Headers 只允许了 content-type,所以被拒。
把 /api/send 的允许头扩展成包含 x-umami-cache(建议同时加常见头):
Access-Control-Allow-Headers: content-type, x-umami-cache, x-requested-with
验证:
- 预检
OPTIONS /api/send响应头里应包含x-umami-cache - 控制台不再报这个 header 不允许错误。
什么是CORS?
CORS 的全称是 Cross-Origin Resource Sharing(跨源资源共享)。
简单来说,浏览器出于安全考虑,存在一个同源策略(Same-Origin Policy)。如果两个 URL 的协议(如 http/https)、域名(如 example.com)或端口(如 :80/:8080)有任何一个不同,就被认为是跨源(跨域)。在同源策略下,浏览器默认会拦截前端向其他域名发起的 AJAX/Fetch 数据请求。
CORS 就是 W3C 制定的一种官方标准机制,用来“打破”同源策略的限制。 它基于 HTTP 头部(HTTP Headers),允许服务器声明哪些外部域名有权限访问该服务器的资源。
CORS能做什么?
- CORS 的核心作用是在保证安全的前提下,实现前后端跨域通信。具体体现在:实现前后端分离架构:前端部署在www.frontend,后端API 部署在 api.backend.com。
- 通过 CORS,前端可以合法地调用后端接口获取数据。精细的访问控制:服务器可以通过响应头精准控制谁能访问资源。例如:
Access-Control-Allow-Origin: 指定允许访问的域名(可以是具体的域名,也可以是 * 代表全部允许)。Access-Control-Allow-Methods: 指定允许的 HTTP 请求方法(如只允许 GET 和 POST,拒绝 DELETE)。 - 安全预检机制(Preflight):对于可能对服务器数据产生副作用的“复杂请求”(如 PUT/DELETE 或带有自定义请求头的请求),CORS 机制会先自动发送一个
OPTIONS请求去“问路”。服务器同意后,浏览器才会发送真正的请求,保护了服务器数据不被意外修改。 - 跨域携带用户凭证:默认跨域请求不能携带 Cookie。通过配置
Access-Control-Allow-Credentials: true,CORS 允许跨域请求携带用户的登录状态(Cookie/Session)。
现代浏览器的跨源安全隔离策(“CO”家族)
CORS 是用来允许跨域的,而由于 CPU 幽灵漏洞(Spectre)等安全问题的出现,现代浏览器引入了一系列新的 HTTP 响应头,用来隔离和防御跨域读取。它们通常与 CORS 配合使用:
- CORP (Cross-Origin Resource Policy)
- 作用:保护你的资源不被其他域名加载。
- 场景:即使别人没有用 AJAX,而是用
<img>或<script>标签强行引用你的图片或脚本,你可以通过设置Cross-Origin-Resource-Policy: same-origin,让浏览器拦截这种跨域加载,防止数据泄露。
- COOP (Cross-Origin Opener Policy)
- 作用:隔离不同的浏览上下文(Window)。
- 场景:当你通过
window.open()打开一个第三方恶意网页时,第三方网页原本可以通过window.opener访问你的页面对象。设置 COOP 可以切断这种联系,把你的页面放进一个安全的“隔离环境”。
- COEP (Cross-Origin Embedder Policy)
- 作用:防止你的页面加载未经明确授权的跨域资源。
- 场景:要求页面上所有跨域的图片、脚本、iframe,要么由 CORS 允许,要么由 CORP 允许,否则直接阻止加载。
- 应用:前端如果要使用
SharedArrayBuffer等高级多线程 API(如 WebAssembly 或 FFmpeg.wasm),浏览器强制要求必须同时开启 COOP 和 COEP(即“跨域隔离”状态)。
- CSP (Content Security Policy, 内容安全策略)
- 它也是通过 HTTP Header 下发的(如
Content-Security-Policy: default-src 'self' api.example.com)。 - 它的作用是告诉浏览器:“这个页面只允许从
api.example.com和我自己的域名加载脚本、图片或发起 AJAX 请求”。 - 它相当于一种白名单机制,是防御 XSS(跨站脚本攻击)的终极武器。
- 它也是通过 HTTP Header 下发的(如
