计算机网络:什么是跨域访问问题,怎么解决?

#计算机网络#操作系统#面试八股

计算机网络:什么是跨域访问问题,怎么解决?

结论:跨域是浏览器同源策略的产物,不是服务器拒绝连接。源(origin)= 协议 + 域名 + 端口,三者任一不同即跨域。 解决方式首选 CORS(服务端返回授权响应头),其次是同源反向代理;JSONP 属于历史方案,只支持 GET 且有注入风险。

1. 同源怎么判定

以页面 https://shop.example.com/index.html 为基准:

目标地址是否同源原因
https://shop.example.com/api/user是协议、域名、端口都相同
https://shop.example.com:8443/api否端口不同
http://shop.example.com/api否协议不同
https://api.example.com/user否域名不同,子域也算跨域
https://SHOP.example.com/user是域名大小写不敏感

容易踩的坑:localhost:3000 调 localhost:8080 也是跨域;http://a.com 与 https://a.com 在浏览器里是两个源。

2. 浏览器如何放行

CORS 把请求分成两类:

简单请求(GET / HEAD / POST + 安全首部)
浏览器 --GET /api/user (Origin: https://shop.example.com)--> 服务端
浏览器 <--200 + Access-Control-Allow-Origin: https://shop.example.com--
        ↓ 浏览器检查响应头,通过才把响应交给 JS;否则抛 TypeError

预检请求(PUT / DELETE / application/json / 自定义头)
浏览器 --OPTIONS /api/user (Origin + Access-Control-Request-Method)--> 服务端
浏览器 <--204 + Allow-Origin + Allow-Methods + Allow-Headers + Max-Age--
浏览器 --PUT /api/user--> 服务端

简单请求的条件很窄:方法只能是 GET / HEAD / POST,首部只能是 Accept、Accept-Language、Content-Language、Content-Type, 且 Content-Type 只能是 text/plain、multipart/form-data、application/x-www-form-urlencoded。 一旦用了 application/json 或加了 Authorization,就会触发预检。

3. 服务端怎么配

nginx 同时处理预检和普通请求:

location /api/ {
    # 只信任自己的前端源;回空值等价于不授权,避免误放行
    set $cors_origin "";
    if ($http_origin ~* "^https://(www|shop)\.example\.com$") {
        set $cors_origin $http_origin;
    }
    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,PUT,DELETE,OPTIONS" always;
    add_header Access-Control-Allow-Headers     "Content-Type,Authorization,X-Request-Id" always;
    add_header Access-Control-Expose-Headers    "X-Request-Id" always;
    add_header Access-Control-Max-Age           86400 always;
    add_header Vary Origin always;      # 缓存要按 Origin 区分,否则会串味

    if ($request_method = OPTIONS) {
        return 204;             # 预检只需头,不要 body
    }
    proxy_pass http://backend;
}

Go 中间件的等价写法:

func cors(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        origin := r.Header.Get("Origin")
        if trusted(origin) { // 白名单校验,绝不直接回显
            w.Header().Set("Access-Control-Allow-Origin", origin)
            w.Header().Set("Access-Control-Allow-Credentials", "true")
            w.Header().Add("Vary", "Origin")
        }
        if r.Method == http.MethodOptions {
            w.Header().Set("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE")
            w.Header().Set("Access-Control-Allow-Headers", "Content-Type,Authorization")
            w.Header().Set("Access-Control-Max-Age", "86400")
            w.WriteHeader(http.StatusNoContent)
            return
        }
        next.ServeHTTP(w, r)
    })
}

4. 常见追问

Q:为什么表单能提交到别的域,Ajax 却不行? 表单提交是页面导航,不是脚本读取响应。同源策略管的是"脚本能不能读",不是"请求能不能发"。 所以老式 CSRF 能成立,而跨域读响应必须由服务端显式授权。

Q:带 Cookie 的跨域要注意什么? Access-Control-Allow-Origin 不能是 *,必须是具体源,同时 Access-Control-Allow-Credentials: true; 前端 fetch 要写 credentials: "include";Cookie 自身还要 SameSite=None; Secure,否则跨站请求根本带不上。

Q:预检结果能缓存多久? 靠 Access-Control-Max-Age,Chrome 上限 7200 秒,Firefox 上限 86400 秒,写更大也没用。 预检缓存按"源 + URL + 方法 + 首部集合"命中,改了请求头就要重新预检。

Q:为什么本地开发配了 proxy 就没跨域? 开发服务器把请求转发给后端,浏览器只看到同源地址,跨域变成了服务端之间的调用,不受同源策略约束。 生产环境同理:用 nginx 把前后端收进同一个域名。

Q:CORS 是安全机制吗? 它只是浏览器对脚本读取的限制。用 curl、Postman 或服务端直连完全不受约束, 所以鉴权、限流、防重放一个都不能少。

Q:JSONP 还能用吗? 只能 GET,靠 <script> 不受同源限制加载并执行远端脚本,等于主动引入任意代码,风险很高。 除非对接只提供 JSONP 的老接口,否则不要用。

小结

同源策略限制的是"读",不是"发";CORS 是服务端用响应头告诉浏览器"这个源可以读我"。 配置守住三条:白名单而不是 *、带凭证时必须回具体源、响应加 Vary: Origin 防止缓存串味。 更彻底的做法是从架构上同源:反向代理把前后端收进同一个域名,跨域问题直接消失。

笔记加载中…