【HTTP之跨域请求以及Cookie携带的限制】

一、什么是跨域?

跨域:是指浏览器端发起的请求,其目标服务器的「协议、域名、端口」三者中任意一个与当前页面所在服务器不一致的情况。

同源策略(Same-Origin Policy):浏览器为保护用户身份凭证(Cookie、Session 等)设计的核心安全机制,核心规则是「仅允许同源页面的 JS 读取同源服务器的响应数据、操作同源存储(Cookie/LocalStorage)」;若请求跨域,浏览器默认拦截 JS 对响应的读取,本质是防止「跨域 JS 冒用用户 Cookie 发起恶意请求(CSRF)」。

跨域方案:是指在浏览器同源策略的安全框架内,合法放行跨域请求响应的技术手段.

同源策略核心理解:

同源策略只存在于浏览器 ,是为了防止 CSRF 攻击、保护用户 Cookie 等身份凭证 ,不是为了阻止跨域本身。它只限制一件事:禁止一个域名下的 JS,跨域读取另一个域名的接口响应或存储内容,避免恶意网站偷偷用用户身份操作敏感接口。

2. 同源策略不是为了 "防止跨域",而是为了防止用户被盗号、被盗数据

例如:

你登录了银行网站 ->浏览器存了你的 Cookie(用户凭证)->你不小心打开恶意网站 ->恶意网站 JS 代码 偷偷用你的 Cookie 请求银行接口

如果没有同源策略:任何网站都能拿着你的身份,随便调用你已登录网站的接口,转走你的钱。 所以:同源策略 = 保护用户身份凭证(Cookie、Token)不被恶意网站盗用。

补充Cookie的自动携带机制:浏览器发请求时,会自动带上对应域名的 Cookie。 这是浏览器的**默认行为。**说明如下:

当你登录 bank.com

  • 服务器种下 Cookie:sessionId=xxx
  • 浏览器保存:只给 bank.com 使用

之后你任何 请求 bank.com

  • 浏览器都会自动在请求头里带上 Cookie
  • 不需要你写代码
  • 不需要你同意
  • 只要你访问,就自动带

3. 同源策略到底拦什么?

它拦的不是:

  • 页面跳转(a 标签跳转、location.href 完全不拦)
  • 加载图片、CSS、JS(<img> <script> <link> 都不拦)
  • 表单提交(可以提交,但拿不到返回结果)

只拦 2 件事

  1. 跨域的 AJAX /fetch 请求(拿不到响应体)
  2. 跨域的 DOM / Cookie / LocalStorage 访问(iframe 之间不能互相读数据)

即:浏览器允许跨域 "发出请求",但不允许跨域 "读取数据"(到这里你可能会有疑惑:为什么跨域情况下浏览器能向服务器发送请求?CORS描述中会告诉你答案)。

4. 什么叫「服务器向服务器发请求」?为什么不受限?

后端代码:

  • Node 发请求给百度
  • Java 发请求给腾讯接口
  • Python 爬取别的网站

这些完全不受同源策略限制

原因:同源策略保护的是用户浏览器里的身份 ,不是保护服务器之间的通信。服务器之间请求没有浏览器环境 ,不会自动携带用户的 Cookie 和登录态,不存在身份冒用风险,因此不受同源策略限制 。这也是代理、Nginx 反向代理能解决跨域的根本原因:让服务器替前端转发请求,绕开浏览器限制。

二、跨域方案

1.CORS

1.1 CORS 核心概念与本质

CORS 全称跨域资源共享(Cross-Origin Resource Sharing),是W3C 标准 ,本质是浏览器为跨域请求制定的一套「安全规则」------ 核心逻辑是:跨域请求的放行权在服务器,浏览器仅做「规则校验」

CORS 是服务器通过设置特定 HTTP 响应头,告诉浏览器「该跨域请求是被允许的」,从而绕过同源拦截。

CORS 本质即:服务器告诉浏览器:这个域名是我信任的,它可以用我的接口。

1.2为什么跨域时浏览器向服务器发请求不被阻止?
1) 浏览器无法阻止请求发出去

HTTP 请求是底层网络行为,浏览器拦不住。

  • 你发了,网络就会传
  • 服务器就会收到
  • 服务器就会处理、返回数据
2)浏览器能控制、且唯一能控制的是:响应能不能进入你的 JS 代码

这才是同源策略 & CORS 的真正作用:

  • 同源:允许 JS 读响应
  • 跨域且无 CORS:不允许 JS 读响应
  • 跨域且有 CORS:允许 JS 读响应

即:浏览器只做 "权限门卫",不做 "网络拦截器"。

浏览器永远不会阻止请求发送,请求一定能发出去,服务器一定能收到并返回。同源策略、CORS 都只控制一件事:响应回来后,浏览器要不要把数据交给前端 JS。不是请求被拦,是响应不给用。这就是整个跨域体系最底层、最不变的铁律。


1.3CORS执行模式
1.3.1简单请求 vs 预检请求(OPTIONS)

浏览器会根据请求特征自动判断是否需要发「预检请求」:

(1)简单请求(无需预检)

满足所有以下条件才是简单请求:

  • 请求方法:仅限 GETPOSTHEAD
  • 请求头:仅包含浏览器默认允许的头(如 AcceptAccept-LanguageContent-LanguageContent-Type( Content-Type仅允许 application/x-www-form-urlencodedmultipart/form-datatext/plain));
  • 无自定义请求头(如 X-ABCToken 等);
  • XMLHttpRequest.upload 事件监听(文件上传相关)。

简单请求流程

  1. 浏览器发送请求时,自动在请求头加 Origin 字段(如 Origin: https://a.com),标识请求来源;
  2. 服务器返回响应时,携带 Access-Control-Allow-Origin 等头(后端决定);
  3. 浏览器校验:若 Access-Control-Allow-Origin 包含当前 Origin(或为 *),则放行响应;否则拦截,抛出 CORS 错误。

(2)预检请求(OPTIONS,复杂请求)

只要不满足「简单请求」的任一条件,浏览器会先发送OPTIONS 预检请求(「探路请求」),确认服务器允许该跨域请求后,才会发送真正的业务请求。

预检请求流程

  1. 浏览器发送 OPTIONS 请求,携带以下关键请求头:

    • Origin:请求来源;
    • Access-Control-Request-Method:后续真正请求要使用的方法(如 POST);
    • Access-Control-Request-Headers:后续真正请求要携带的自定义头(如 X-ABC, Content-Type);
  2. 服务器校验后,返回响应头:

    javascript 复制代码
    Access-Control-Allow-Origin: https://a.com  # 允许的来源(不能与Origin不一致,*仅支持简单请求)
    Access-Control-Allow-Methods: POST, GET, OPTIONS  # 允许的请求方法
    Access-Control-Allow-Headers: X-ABC, Content-Type  # 允许的自定义头
    Access-Control-Max-Age: 86400  # 预检结果缓存时间(秒),避免重复发OPTIONS
    Access-Control-Allow-Credentials: true  # 允许携带Cookie(若前端带withCredentials=true)
  3. 浏览器校验预检响应:若符合规则,等待 Access-Control-Max-Age 时间内,同类请求无需再发预检;然后发送真正的业务请求;若不符合,直接拦截,不发业务请求。

预检目的:兼容老服务器、避免危险请求直接执行


1.3.2预检请求之后的业务请求也需要配" Methods/Headers"吗?

正解:不需要!只有 OPTIONS 预检请求需要这两个头,真实的业务请求只需要 Origin 即可 ------ 因为预检已经确认过服务器支持这些方法 / 头了。

1.4 CORS 核心响应头
响应头 作用 注意事项
Access-Control-Allow-Origin 允许的跨域来源 只能是具体域名(如 https://a.com)或 *;若前端带 Cookie,不能用 *,必须指定具体域名
Access-Control-Allow-Methods 允许的请求方法 预检请求专用,需包含后续真正请求的方法
Access-Control-Allow-Headers 允许的自定义请求头 预检请求专用,需包含前端要携带的自定义头
Access-Control-Allow-Credentials 是否允许跨域携带 Cookie / 认证信息 前后端需一致:前端 xhr.withCredentials=true,后端设为 true;且 Allow-Origin 不能是 *
Access-Control-Expose-Headers 允许前端通过 xhr.getResponseHeader() 获取的响应头 默认前端只能获取 Cache-ControlContent-Language 等少数头,自定义头需在此声明
Access-Control-Max-Age 预检结果缓存时间 减少 OPTIONS 请求次数,优化性能
1.4.1带Cookie为什么不能用*?

Access-Control-Allow-Origin: * + 允许凭证 = 任何网站都能冒用用户身份,极度危险。所以浏览器强制:跨域带 Cookie 时,必须指定具体域名,不能用通配符


1.5CORS 的简单请求和预检请求的区别?如何触发预检请求?
  • 核心区别:简单请求无需先发 OPTIONS 预检,直接发业务请求;预检请求需先发送 OPTIONS 探路,确认服务器允许后再发业务请求。
  • 触发预检请求的条件(满足任一即可):
    1. 请求方法非 GET/POST/HEAD(如 PUT、DELETE、PATCH);
    2. 请求头包含自定义头(如 Token、X-ABC)或非默认 Content-Type(如 application/json);
    3. 请求包含 XMLHttpRequest.upload 事件监听(文件上传)。

2. 其他跨域方案

方案 核心原理 适用场景 优缺点
Nginx 反向代理 跨域仅存在于浏览器与服务器之间,服务器间无同源限制;Nginx 作为中间层,将前端请求转发到目标服务器,响应再返回给前端(前端→Nginx→目标服务器,前端与 Nginx 同源) 生产环境首选(前后端分离项目) 优点:无前端代码侵入、性能好;缺点:需配置 Nginx
window.postMessage H5 新特性,允许不同窗口 /iframe 之间跨域通信,通过 postMessage 发送消息,message 事件监听接收 页面间跨域通信(如 iframe 嵌套、多窗口) 优点:专门解决页面通信;缺点:仅适用于前端页面间,无法解决 Ajax 跨域
JSONP 利用 <script> 标签无跨域限制,动态创建 script 标签,请求后端返回回调函数 + 数据的 JS 代码 仅支持 GET 请求(老旧项目兼容) 优点:兼容性好;缺点:仅 GET、有 XSS 风险、无法处理错误
Node 代理 同 Nginx 原理,前端启动 Node 服务作为代理层,转发请求到目标服务器 开发环境(如 Vue 的 devServer.proxy) 优点:开发便捷;缺点:生产环境需部署代理服务

2.1Nginx 反向代理

本质生产级网关 / 入口服务器

作用域生产环境

底层:高性能 HTTP 服务器,做路由、转发、负载均衡。

目的:

  • 统一入口
  • 隐藏后端真实地址
  • 负载均衡
  • HTTPS 卸载
  • 静态资源托管
2.1.1Nginx 反向代理解决跨域的核心配置?原理是什么?
  • 核心配置示例:

    javascript 复制代码
    server {
        listen 80;
        server_name localhost;
    
        # 前端请求 /api 开头的接口,转发到目标服务器
        location /api {
            # 转发目标地址
            proxy_pass https://target-server.com;
            # 告诉目标服务器真实的请求来源(可选)
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            # 关键:让响应头的 Access-Control-Allow-Origin 匹配前端域名(也可直接设为 *)
            add_header Access-Control-Allow-Origin http://localhost:8080;
            add_header Access-Control-Allow-Methods GET,POST,PUT,DELETE,OPTIONS;
            add_header Access-Control-Allow-Credentials true;
        }
    }
  • 原理:跨域是浏览器的安全限制,服务器之间无同源限制。Nginx 作为中间代理,前端请求发送到 Nginx(与前端同源),Nginx 再转发请求到目标服务器,目标服务器的响应通过 Nginx 返回给前端,从而绕过浏览器的跨域拦截。

代理策略完整底层流程:

  1. 前端页面运行在浏览器,必须遵守同源策略
  2. 前端不直接请求目标服务器,而是请求同域代理服务器
  3. 浏览器看到是同域完全不拦截,直接把响应交给 JS。
  4. 代理服务器收到请求,转发给目标服务器。
  5. 服务器之间没有任何跨域限制,想发就发。
  6. 目标服务器返回数据 → 代理服务器 → 浏览器 → JS。

2.2Proxy 代理(如 webpack devServer proxy)

本质 :开发时用的 Node 中间代理服务

作用域开发环境

底层:本地起一个 HTTP 服务器,做请求转发

目的前端开发不想依赖后端配 CORS

特点:前端自己配置、只在本地生效、生产环境不用。

Proxy 是开发阶段的 "临时绕过方案";Nginx 是生产环境的 "正式入口架构"


2.3CORS 和 JSONP 的区别?为什么现在主流用 CORS?
  • 区别:
    1. 请求方式:JSONP 仅支持 GET,CORS 支持所有 HTTP 方法;
    2. 错误处理:JSONP 无法捕获请求失败(如 404、500),CORS 可通过 XHR 事件监听错误;
    3. 安全性:JSONP 有 XSS 风险(后端返回恶意代码),CORS 可通过响应头严格控制来源、方法;
    4. 侵入性:JSONP 需要前端写回调函数,后端配合返回回调格式,CORS 仅需后端配置响应头,前端无侵入。
  • 主流用 CORS 的原因:支持所有 HTTP 方法、安全性高、前后端解耦、能处理错误,符合现代 Web 开发需求。

2.4window.postMessage 如何实现跨域通信?举个例子?

postMessage 是 H5 提供的跨窗口通信 API,核心是「发送方 postMessage 传消息,接收方监听 message 事件」。示例(A 页面:http://a.com,B 页面:http://b.com,A 嵌套 B 的 iframe):

  • A 页面(发送消息):

    javascript 复制代码
    // 获取 B 页面的 iframe 窗口
    const iframe = document.getElementById('b-iframe');
    // 页面加载完成后发送消息
    iframe.onload = () => {
        // postMessage(消息内容, 目标域名,* 表示任意域名)
        iframe.contentWindow.postMessage('Hello B', 'http://b.com');
    };
  • B 页面(接收消息):

    javascript 复制代码
    // 监听 message 事件
    window.addEventListener('message', (e) => {
        // 校验消息来源(安全)
        if (e.origin !== 'http://a.com') return;
        console.log('收到 A 的消息:', e.data); // Hello B
        // 回复消息
        e.source.postMessage('Hello A', e.origin);
    });

三、Cookie携带的限制

1.Cookie 携带的本质

Cookie 是浏览器按域名存储的身份凭证,默认行为是:

  • 同域请求:浏览器自动携带对应域名的 Cookie;
  • 跨域请求:浏览器默认不携带,且即使手动配置,也受严格规则限制。

Cookie 跨域携带的所有限制,本质都是浏览器为了防止 CSRF 攻击、保护用户身份安全设计的。


2.Cookie 跨域携带的限制

2.1纯 CORS 方案(前端直连后端)

这是最常见的场景,限制集中在「前后端必须严格匹配配置」,缺一不可:

2.1.1 前端必须显式开启 withCredentials(带上凭证)

无论用 XHR 还是 Fetch,必须手动配置携带凭证:

javascript 复制代码
// XHR
const xhr = new XMLHttpRequest();
xhr.withCredentials = true; // 关键:开启携带Cookie

// Fetch
fetch('https://b.com/api', {
  credentials: 'include' // 关键:include 表示跨域携带,same-origin 仅同域,omit 不携带
});

注意:如果前端没开这个配置,浏览器绝对不会携带跨域 Cookie,哪怕后端配置全对也没用。

2.1.2后端必须配置 2 个响应头(核心限制)
  • Access-Control-Allow-Origin不能用 *(通配符) ,必须指定前端具体域名(如 https://a.com);
  • Access-Control-Allow-Credentials: true:明确告诉浏览器 "允许跨域携带凭证"。
2.1.3 预检请求(OPTIONS)也需配套配置

如果是复杂请求(触发 OPTIONS 预检),OPTIONS 响应也必须包含上述 2 个头,否则预检失败,后续业务请求不会发。

2.1.4Cookie 本身的属性限制

即使前后端配置对了,Cookie 自身的属性也会影响携带:

  • HttpOnly:不影响跨域携带,但前端 JS 无法读取(安全属性,防止 XSS);
  • Secure:仅在 HTTPS 协议下携带,HTTP 协议下不会带;
  • SameSite
    • SameSite=Strict:仅同站请求携带(最严格,跨域绝对不带);
    • SameSite=Lax:默认值,跨域的 GET 请求(如链接跳转)可带,POST/AJAX 跨域不带;
    • SameSite=None:允许跨域携带,但必须配合 Secure 属性(仅 HTTPS),否则浏览器忽略。
2.2代理方案(Nginx / 前端 proxy)

代理方案下 Cookie 携带无跨域限制,但有「域名匹配」限制:

  • 原理:前端请求的是「同域代理服务器」,浏览器认为是同域请求,会自动携带代理服务器域名的 Cookie
  • 限制:Cookie 必须是「代理服务器域名」下的,而非目标服务器域名;
  • 举例:
    • 前端 http://localhost:8080 → 代理 http://localhost:8080/api → 目标 https://b.com
    • 浏览器会携带 localhost 的 Cookie,而非 b.com 的 Cookie;
    • 若要携带 b.com 的 Cookie,需代理服务器转发请求时,把 Cookie 透传给目标服务器(Nginx 可配置 proxy_cookie_domain 实现)。

3.限制背后的安全逻辑(总结版)

浏览器对 Cookie 跨域携带做严格限制,核心是防止「CSRF 攻击」:

  • 如果允许 Access-Control-Allow-Origin: * + 携带 Cookie,意味着任何恶意网站都能拿着用户的 Cookie 调用接口,等于完全暴露用户身份;
  • SameSite 属性的设计,进一步缩小了跨域携带 Cookie 的场景,仅允许用户主动触发的跨域请求(如点击链接)携带,阻断被动的 AJAX 跨域携带。

4.常见问题 & 排错

  1. 为什么跨域带 Cookie 总是失败?排查步骤:

    • 前端是否开了 withCredentials/include
    • 后端 Allow-Origin 是否是具体域名(非 *);
    • 后端是否返回 Allow-Credentials: true
    • Cookie 的 SameSite 是否为 None + Secure(跨域携带需配置);
    • 协议是否为 HTTPS(Secure/SameSite=None 依赖)。
  2. 代理方案下 Cookie 带不过去怎么办?解决:Nginx 配置 proxy_cookie_domain 重写 Cookie 域名:

    javascript 复制代码
    location /api {
      proxy_pass https://b.com;
      # 把目标服务器返回的 b.com Cookie 域名改成代理服务器域名
      proxy_cookie_domain b.com localhost;
    }

5.Cookie携带的限制总结

  1. CORS 带 Cookie 三大铁律 :前端开 withCredentials、后端指定具体 Allow-Origin、后端配 Allow-Credentials: true
  2. Cookie 自身属性限制Secure 要求 HTTPS,SameSite=None 需配合 SecureStrict/Lax 限制跨域携带;
  3. 代理方案无跨域限制:但 Cookie 域名需匹配代理服务器,可通过 Nginx 重写域名适配。

四、总结

  1. CORS 核心 :服务器通过设置 Access-Control-* 响应头,告诉浏览器允许跨域请求;简单请求直接发,复杂请求先发 OPTIONS 预检。
  2. 跨域方案对比:生产环境首选 Nginx 反向代理(无侵入、性能好),页面间通信用 postMessage,老旧项目兼容用 JSONP,现代项目优先 CORS。
  3. 面试关键:掌握预检请求触发条件、Cookie 携带的限制、Nginx 代理原理,以及 CORS 与其他方案的对比。

好啦,主包要去吃饭啦~😀😀😀

相关推荐
程序猿编码2 小时前
深入解析:一款能识别TLS流量特征的Linux内核连接跟踪模块
linux·网络·安全·内核模块·tls
默 语2 小时前
TypeScrip+React 全栈生态实战:从架构选型到工程落地,告别开发踩坑
前端·react.js·架构
代码丰2 小时前
简历保险箱:一款本地存储、快捷填表的 Chrome 简历助手
前端·chrome
SuperEugene2 小时前
Promise 从入门到实战:同步异步、回调地狱、then/catch/finally 全解
前端·javascript·面试
前端 贾公子2 小时前
uniapp 小程序获取后端的二进制 保存到手机相册
java·前端·javascript
qq_437100662 小时前
echarts图表相关 电量进度图
前端·flask·echarts
智擎软件测评小祺2 小时前
信息安全性测试:守护数字世界的安全防线
网络·安全·安全性测试·cma·第三方检测·cnas·信息安全性测试
Thomas.Sir2 小时前
Vue 3:现代前端框架的架构革命
前端·vue.js·web·大前端
merlin-mm2 小时前
云平台构建 RDMA高性能网络
网络·云原生·容器·kubernetes