侧边栏壁纸
博主头像
いちごみるく

行动起来,活在当下

  • 累计撰写 69 篇文章
  • 累计创建 48 个标签
  • 累计收到 55 条评论

目 录CONTENT

文章目录

CORS跨域访问错误(附websocket解决方案)

维护于 2026-10-01:修正通配 Origin 与凭据冲突、只给 OPTIONS 加响应头,以及清空 WebSocket Origin 的不安全做法。下面仅示范明确允许 https://app.example.com 访问自己 API 的情况。

一、先确认错误来自哪一层

CORS 是跨源资源共享(Cross-Origin Resource Sharing)。它规定浏览器何时允许网页脚本读取另一来源的响应。CORS 失败不一定对应 HTTP 403;服务器即使返回 200,浏览器也可能因响应头不符合策略而阻止脚本读取。真实 403 还可能来自认证、WAF 或应用权限。

在浏览器网络面板中分别检查 OPTIONS 预检与实际请求的状态、响应头和服务端日志。是否预检取决于方法、请求头和 Content-Type 等条件;不能简化为“GET 不预检,POST 都预检”。CORS 也不能替代身份认证或 CSRF 防护。Fetch 标准定义了这些规则。

二、Nginx 的一个明确白名单示例

map 放在 http 上下文,location 放在已有 API server 内。此处允许带凭据的请求,所以 Origin 必须返回允许的具体来源,不能返回 *。API 上游示例为本机 127.0.0.1:8080。

# http 上下文
map_hash_bucket_size 64;
map $http_origin $cors_origin {
    default "";
    "https://app.example.com" "https://app.example.com";
}

# 已有 API server 中
location /api/ {
    add_header Access-Control-Allow-Origin $cors_origin always;
    add_header Access-Control-Allow-Credentials "true" always;
    add_header Access-Control-Allow-Methods "GET, POST, OPTIONS" always;
    add_header Access-Control-Allow-Headers "Content-Type, Authorization" always;
    add_header Vary "Origin" always;

    if ($request_method = OPTIONS) {
        return 204;
    }

    proxy_pass http://127.0.0.1:8080;
}

这里没有在 if 内重新定义 add_header,预检与实际响应使用同一组头。未知 Origin 对应空值,Nginx 不发送允许来源头,浏览器不能读取该响应。实际请求仍可能到达应用,因此应在应用层验证认证、方法和敏感操作权限。允许的方法与头按实际 API 缩小或扩展;这个示例不会自动支持 PUT、DELETE 或任意自定义头。若来源名称更长、配置检查报 could not build map_hash,按报错适当增大 map_hash_bucket_size。

不要同时让应用和 Nginx 各添加一份 Access-Control-Allow-Origin。优先由一个位置完整管理。若使用缓存,还要避免缓存把一个来源的允许响应复用给其他来源;Vary: Origin 是其中一项必要设置。

检查配置、reload 后可用 curl 查看预检:

curl -i -X OPTIONS https://api.example.com/api/example \
  -H 'Origin: https://app.example.com' \
  -H 'Access-Control-Request-Method: POST' \
  -H 'Access-Control-Request-Headers: content-type,authorization'

再在浏览器里验证实际请求、带 Cookie 的情况及不在白名单中的来源。curl 不执行浏览器 CORS 限制,拿到响应不代表浏览器测试通过。

三、WebSocket 使用自己的 Origin 校验

浏览器 WebSocket 握手不会按普通 fetch 的 CORS 预检流程处理。服务端应按明确白名单校验握手 Origin,同时验证用户身份。不要用 proxy_set_header Origin ''; 消除来源信息来绕过拒绝;这可能移除应用的跨站连接防护。RFC 6455 的安全说明解释了 Origin 的用途。

Nginx 的 HTTP/1.1 WebSocket 反向代理示例:

# http 上下文
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

# 已有 server 中
location /ws/ {
    proxy_pass http://127.0.0.1:8080;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
    proxy_set_header Host $host;
    proxy_set_header Origin $http_origin;
}

应用允许正常网页的 Origin,而不是允许所有来源。验证 101 握手、收发消息、空闲超时及错误来源拒绝;只有握手成功还不够。Nginx WebSocket 文档说明了 Upgrade 与 Connection 的显式转发。

0

评论区