正在统计字数

今天在配置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 也没通过

需要进行以下的操作:

  1. 允许预检请求 OPTIONS
  2. OPTIONSPOST 响应里返回这些头:
  • Access-Control-Allow-Origin
  • Access-Control-Allow-Methods
  • Access-Control-Allow-Headers
  • Access-Control-Max-Age
  • Vary: Origin

当前还有一层来源限制(只允许 timberfirear.cc),所以本地 http://localhost:4321 会被拦。
有两种处理方式:

  1. 生产推荐:本地不注入 Umami(只在 import.meta.env.PROD 注入)
  2. 本地联调:把 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能做什么?

  1. CORS 的核心作用是在保证安全的前提下,实现前后端跨域通信。具体体现在:实现前后端分离架构:前端部署在www.frontend,后端API 部署在 api.backend.com。
  2. 通过 CORS,前端可以合法地调用后端接口获取数据。精细的访问控制:服务器可以通过响应头精准控制谁能访问资源。例如:Access-Control-Allow-Origin: 指定允许访问的域名(可以是具体的域名,也可以是 * 代表全部允许)。Access-Control-Allow-Methods: 指定允许的 HTTP 请求方法(如只允许 GET 和 POST,拒绝 DELETE)。
  3. 安全预检机制(Preflight):对于可能对服务器数据产生副作用的“复杂请求”(如 PUT/DELETE 或带有自定义请求头的请求),CORS 机制会先自动发送一个 OPTIONS 请求去“问路”。服务器同意后,浏览器才会发送真正的请求,保护了服务器数据不被意外修改。
  4. 跨域携带用户凭证:默认跨域请求不能携带 Cookie。通过配置 Access-Control-Allow-Credentials: true,CORS 允许跨域请求携带用户的登录状态(Cookie/Session)。

现代浏览器的跨源安全隔离策(“CO”家族)

CORS 是用来允许跨域的,而由于 CPU 幽灵漏洞(Spectre)等安全问题的出现,现代浏览器引入了一系列新的 HTTP 响应头,用来隔离和防御跨域读取。它们通常与 CORS 配合使用:

  1. CORP (Cross-Origin Resource Policy)
    • 作用:保护你的资源不被其他域名加载。
    • 场景:即使别人没有用 AJAX,而是用 <img> 或 <script> 标签强行引用你的图片或脚本,你可以通过设置 Cross-Origin-Resource-Policy: same-origin,让浏览器拦截这种跨域加载,防止数据泄露。
  2. COOP (Cross-Origin Opener Policy)
    • 作用:隔离不同的浏览上下文(Window)。
    • 场景:当你通过 window.open() 打开一个第三方恶意网页时,第三方网页原本可以通过 window.opener 访问你的页面对象。设置 COOP 可以切断这种联系,把你的页面放进一个安全的“隔离环境”。
  3. COEP (Cross-Origin Embedder Policy)
    • 作用:防止你的页面加载未经明确授权的跨域资源。
    • 场景:要求页面上所有跨域的图片、脚本、iframe,要么由 CORS 允许,要么由 CORP 允许,否则直接阻止加载。
    • 应用:前端如果要使用 SharedArrayBuffer 等高级多线程 API(如 WebAssembly 或 FFmpeg.wasm),浏览器强制要求必须同时开启 COOP 和 COEP(即“跨域隔离”状态)。
  4. CSP (Content Security Policy, 内容安全策略)
    • 它也是通过 HTTP Header 下发的(如 Content-Security-Policy: default-src 'self' api.example.com)。
    • 它的作用是告诉浏览器:“这个页面只允许从 api.example.com 和我自己的域名加载脚本、图片或发起 AJAX 请求”。
    • 它相当于一种白名单机制,是防御 XSS(跨站脚本攻击)的终极武器。