Files
docs/docs/networking/cors.md
T
wonder 32cdd435b3
Deploy Docs / deploy (push) Successful in 11s
docs: 添加计算机网络章节(9篇文章)
2026-09-02 11:54:48 +08:00

6.3 KiB
Raw Blame History

跨域(CORS)

!!! note "浏览器的同源策略看的是'协议+域名+端口'三元组,不是'同一台机器'——只要有一个不一样就跨域"


核心概念

  1. 同源策略 — 浏览器的安全基石,不同源的请求一律先拦住
  2. Origin 三元组 — 协议 + 域名 + 端口,三者必须完全一致才算同源
  3. CORS — 跨源资源共享,服务器通过响应头告诉浏览器"允许谁来访问"
  4. 预检请求 — 非简单请求浏览器先发 OPTIONS 探路,确认安全后才发真正请求

详解

同源的定义

浏览器判断跨域的标准不是"是不是同一台机器",而是:

源(Origin)= 协议 + 域名 + 端口

https://example.com:443
  ↑        ↑         ↑
协议      域名      端口

三个都一样 → 同源 → 没问题
任何一个不一样 → 跨域 → 浏览器拦截

常见的"同一台主机但跨域"情况

1. 协议不同

页面:http://localhost:3000    (HTTP)
请求:https://localhost:3000   (HTTPS)

http ≠ https → 跨域!

2. 端口不同

页面:http://localhost:3000    (前端)
请求:http://localhost:8080    (后端 API)

3000 ≠ 8080 → 跨域!

前后端分离开发时极其常见。

3. 域名写法不同

页面:http://example.com
请求:http://www.example.com

example.com ≠ www.example.com → 跨域!

4. 子域名不同

页面:https://app.example.com      (前端)
请求:https://api.example.com      (后端 API)

app.example.com ≠ api.example.com → 跨域!

图示

graph TD
    subgraph 同源 ✅
        A1["https://example.com:443"]
        A2["https://example.com:443"]
        A1 --- A2
    end
    subgraph 跨域 ❌
        B1["https://app.example.com:443"]
        B2["https://api.example.com:443"]
        B1 --- B2
    end
    subgraph 跨域 ❌
        C1["http://localhost:3000"]
        C2["http://localhost:8080"]
        C1 --- C2
    end
    subgraph 跨域 ❌
        D1["http://example.com:80"]
        D2["https://example.com:443"]
        D1 --- D2
    end

为什么要拦截?

sequenceDiagram
    participant You as 你
    participant Bank as bank.com
    participant Evil as evil.com
    Note over You,Bank: 你登录了银行网站,浏览器存了 cookie
    You->>Evil: 访问恶意网站
    Evil->>Bank: 请求 bank.com/api/transfer(冒充你转账)
    Note over Bank: 浏览器同源策略拦截!
    Note over Bank: evil.com 的请求被阻止 😅

同源策略 = 浏览器替你守门,不同源的请求一律先拦住。

解决方案

方案 1:后端设置 CORS 响应头(最常用)

# Python Flask
from flask import Flask
from flask_cors import CORS

app = Flask(__name__)
CORS(app)  # 允许所有来源

# 或精确控制
CORS(app, origins=["https://app.example.com"])
// Go 标准库
func corsMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Access-Control-Allow-Origin", "https://app.example.com")
        w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, OPTIONS")
        w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
        if r.Method == "OPTIONS" {
            w.WriteHeader(http.StatusNoContent)
            return
        }
        next.ServeHTTP(w, r)
    })
}
# Nginx
add_header Access-Control-Allow-Origin "https://app.example.com";
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS";
add_header Access-Control-Allow-Headers "Content-Type, Authorization";

方案 2:前端开发代理

// Vite 开发服务器配置
export default {
  server: {
    proxy: {
      '/api': {
        target: 'http://localhost:8080',
        changeOrigin: true,
      }
    }
  }
}

浏览器看到的请求始终在 localhost:3000,同源,不触发跨域。

方案 3:Nginx 反向代理(生产环境)

server {
    listen 80;
    server_name app.example.com;

    location / {
        root /usr/share/nginx/html;
        try_files $uri $uri/ /index.html;
    }

    location /api/ {
        proxy_pass http://localhost:8080;
        # 对浏览器:请求的是 app.example.com/api → 同源 ✅
    }
}

简单请求 vs 预检请求

类型 条件 行为
简单请求 GET/POST/HEAD + 简单 Header 浏览器直接发,看响应头判断
预检请求 PUT/DELETE/自定义 Header 等 浏览器先发 OPTIONS 探路,确认后再发

预检请求流程:

sequenceDiagram
    participant C as 浏览器
    participant S as 服务器
    C->>S: OPTIONS /api/data(预检)
    S->>C: 204 No Content
    Note over S: Access-Control-Allow-Origin: https://app.example.com
    Note over S: Access-Control-Allow-Methods: PUT, POST, GET
    C->>C: 确认允许
    C->>S: PUT /api/data(真正的请求)
    S->>C: 200 OK

网络面板里一个请求出现两次——第一次是 OPTIONS 预检,不是 bug。


常见陷阱

!!! warning "Access-Control-Allow-Origin 不能设为 * 同时带 Cookie" 如果请求携带 Cookie(withCredentials: true),Access-Control-Allow-Origin 不能是 *,必须指定具体来源。否则浏览器会拒绝。

!!! warning "忘记处理 OPTIONS 预检请求" 很多后端框架默认不处理 OPTIONS 方法,导致预检请求返回 405 Method Not Allowed。需要显式添加 OPTIONS 处理逻辑。


练习题

??? question "为什么 WebSocket 不受同源策略限制?" ??? success "答案" WebSocket 连接一旦建立,就是全双工通信,不受 CORS 限制。但建立连接的握手阶段(HTTP Upgrade 请求)仍然受同源策略影响。所以 WebSocket 的安全性依赖服务器端的身份验证,而不是浏览器的同源策略。

??? question "如果后端设置了 Access-Control-Allow-Origin: *,前端 withCredentials: true 会怎样?" ??? success "答案" 浏览器会拒绝。规范明确要求:当请求携带凭据(Cookie、HTTP Auth)时,Access-Control-Allow-Origin 不能是通配符 *,必须是具体的源。这是为了防止任意网站都能通过你浏览器里的 Cookie 访问受保护资源。