一、什么是跨域?
跨域:是指浏览器端发起的请求,其目标服务器的「协议、域名、端口」三者中任意一个与当前页面所在服务器不一致的情况。
同源策略(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 件事:
- 跨域的 AJAX /fetch 请求(拿不到响应体)
- 跨域的 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)简单请求(无需预检)
满足所有以下条件才是简单请求:
- 请求方法:仅限
GET、POST、HEAD; - 请求头:仅包含浏览器默认允许的头(如
Accept、Accept-Language、Content-Language、Content-Type(Content-Type仅允许application/x-www-form-urlencoded、multipart/form-data、text/plain)); - 无自定义请求头(如
X-ABC、Token等); - 无
XMLHttpRequest.upload事件监听(文件上传相关)。
简单请求流程:
- 浏览器发送请求时,自动在请求头加
Origin字段(如Origin: https://a.com),标识请求来源; - 服务器返回响应时,携带
Access-Control-Allow-Origin等头(后端决定); - 浏览器校验:若
Access-Control-Allow-Origin包含当前Origin(或为*),则放行响应;否则拦截,抛出 CORS 错误。
(2)预检请求(OPTIONS,复杂请求)
只要不满足「简单请求」的任一条件,浏览器会先发送OPTIONS 预检请求(「探路请求」),确认服务器允许该跨域请求后,才会发送真正的业务请求。
预检请求流程:
-
浏览器发送 OPTIONS 请求,携带以下关键请求头:
Origin:请求来源;Access-Control-Request-Method:后续真正请求要使用的方法(如POST);Access-Control-Request-Headers:后续真正请求要携带的自定义头(如X-ABC, Content-Type);
-
服务器校验后,返回响应头:
javascriptAccess-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) -
浏览器校验预检响应:若符合规则,等待
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-Control、Content-Language 等少数头,自定义头需在此声明 |
Access-Control-Max-Age |
预检结果缓存时间 | 减少 OPTIONS 请求次数,优化性能 |
1.4.1带Cookie为什么不能用*?
Access-Control-Allow-Origin: * + 允许凭证 = 任何网站都能冒用用户身份,极度危险。所以浏览器强制:跨域带 Cookie 时,必须指定具体域名,不能用通配符。
1.5CORS 的简单请求和预检请求的区别?如何触发预检请求?
- 核心区别:简单请求无需先发 OPTIONS 预检,直接发业务请求;预检请求需先发送 OPTIONS 探路,确认服务器允许后再发业务请求。
- 触发预检请求的条件(满足任一即可):
- 请求方法非 GET/POST/HEAD(如 PUT、DELETE、PATCH);
- 请求头包含自定义头(如 Token、X-ABC)或非默认 Content-Type(如 application/json);
- 请求包含 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 反向代理解决跨域的核心配置?原理是什么?
-
核心配置示例:
javascriptserver { 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 返回给前端,从而绕过浏览器的跨域拦截。
代理策略完整底层流程:
- 前端页面运行在浏览器,必须遵守同源策略。
- 前端不直接请求目标服务器,而是请求同域代理服务器。
- 浏览器看到是同域 ,完全不拦截,直接把响应交给 JS。
- 代理服务器收到请求,转发给目标服务器。
- 服务器之间没有任何跨域限制,想发就发。
- 目标服务器返回数据 → 代理服务器 → 浏览器 → JS。
2.2Proxy 代理(如 webpack devServer proxy)
本质 :开发时用的 Node 中间代理服务
作用域 :开发环境
底层:本地起一个 HTTP 服务器,做请求转发
目的 :前端开发不想依赖后端配 CORS
特点:前端自己配置、只在本地生效、生产环境不用。
Proxy 是开发阶段的 "临时绕过方案";Nginx 是生产环境的 "正式入口架构"
2.3CORS 和 JSONP 的区别?为什么现在主流用 CORS?
- 区别:
- 请求方式:JSONP 仅支持 GET,CORS 支持所有 HTTP 方法;
- 错误处理:JSONP 无法捕获请求失败(如 404、500),CORS 可通过 XHR 事件监听错误;
- 安全性:JSONP 有 XSS 风险(后端返回恶意代码),CORS 可通过响应头严格控制来源、方法;
- 侵入性: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.常见问题 & 排错
-
为什么跨域带 Cookie 总是失败?排查步骤:
- 前端是否开了
withCredentials/include; - 后端
Allow-Origin是否是具体域名(非*); - 后端是否返回
Allow-Credentials: true; - Cookie 的
SameSite是否为None+Secure(跨域携带需配置); - 协议是否为 HTTPS(
Secure/SameSite=None依赖)。
- 前端是否开了
-
代理方案下 Cookie 带不过去怎么办?解决:Nginx 配置
proxy_cookie_domain重写 Cookie 域名:javascriptlocation /api { proxy_pass https://b.com; # 把目标服务器返回的 b.com Cookie 域名改成代理服务器域名 proxy_cookie_domain b.com localhost; }
5.Cookie携带的限制总结
- CORS 带 Cookie 三大铁律 :前端开
withCredentials、后端指定具体Allow-Origin、后端配Allow-Credentials: true; - Cookie 自身属性限制 :
Secure要求 HTTPS,SameSite=None需配合Secure,Strict/Lax限制跨域携带; - 代理方案无跨域限制:但 Cookie 域名需匹配代理服务器,可通过 Nginx 重写域名适配。
四、总结
- CORS 核心 :服务器通过设置
Access-Control-*响应头,告诉浏览器允许跨域请求;简单请求直接发,复杂请求先发 OPTIONS 预检。 - 跨域方案对比:生产环境首选 Nginx 反向代理(无侵入、性能好),页面间通信用 postMessage,老旧项目兼容用 JSONP,现代项目优先 CORS。
- 面试关键:掌握预检请求触发条件、Cookie 携带的限制、Nginx 代理原理,以及 CORS 与其他方案的对比。
好啦,主包要去吃饭啦~😀😀😀