同源策略 :域名、协议、端口完全相同则称为同源。
跨域:请求的域名、协议、端口其中之一不同的情况下发生跨域
跨域解决
JSONP(JSON with Padding)
JSON with Padding:把 JSON 数据「填充」到函数中。
动态创建 <script> 去发送参数。
- 利用了
<script>的标签本身的跨域特性,同时<script>标签中的代码会自动执行 - 通过
?callback=<函数名>指明回调函数 - 服务端返回包裹 JSON 数据的回调函数执行代码来触发执行
客户端
javascript
// 1. 定义回调函数(服务端返回的代码会调用此函数)
function jsonpCallback(data) {
console.log('跨域数据:', data); // { name: '张三', age: 20 }
}
// 2. 动态创建 script 标签,发起请求
const script = document.createElement('script');
// 接口地址 + 回调函数名(服务器需识别 callback 参数)
script.src = 'https://api.other.com/data?callback=jsonpCallback';
document.body.appendChild(script);
// 3. 可选:请求完成后移除 script 标签
script.onload = () => document.body.removeChild(script);
服务端
javascript
app.get('/data', (req, res) => {
const callbackName = req.query.callback; // 获取前端传入的回调函数名
const data = { name: '张三', age: 20 }; // 要返回的数据
// 返回格式:回调函数名(JSON数据)
res.send(`${callbackName}(${JSON.stringify(data)})`);
});
局限性
- JSONP 仅支持 GET 请求,无法处理复杂数据。
- 内容暴露链接,有可能会导致数据被记录到日志系统中,存在泄密风险。
CORS(Cross-Origin Resource Sharing - 跨源资源共享)
- 在受控条件下允许跨域资源请求。
- CORS 仅对 fetch/XHR 生效,对
<script>、<link>、<img>、<iframe>等标签不生效。 - 跨域请求时浏览器会先发
OPTIONS预检请求,服务端通过 CORS 响应头声明是否允许跨域。
简单请求
不会触发 OPTIONS 预检请求,需同时满足下列条件。
-
请求方法 :
GET、POST、HEAD三种之一。 -
头部限制:不能包含自定义请求头。
-
类型限制 :
Content-Type仅允许application/x-www-form-urlencoded、multipart/form-data、text/plain。 -
请求中没有
ReadableStream对象。 -
请求中
XMLHttpRequestUpload未注册监听器。
简单请求下,服务端需在响应中返回的响应头
- Access-Control-Allow-Origin(必须) :允许发起跨域请求的源;携带凭证时不能设为
*。
预检请求
上述条件不满足,就是非简单请求,此时会触发 OPTIONS 预检请求。
OPTIONS 预检请求是实际请求前的 HTTP 请求,用于获知服务端是否允许实际请求发送。
常见预检请求请求头(非简单请求时由浏览器自动添加)
- Access-Control-Request-Method:声明将使用的请求方法,必须携带。
- Access-Control-Request-Headers:指定会额外发送的请求头字段,逗号分隔。
预检响应中需携带的响应头
- Access-Control-Allow-Methods(必须) :与
Access-Control-Request-Method对应。 - Access-Control-Allow-Headers(必须) :与
Access-Control-Request-Headers对应,自定义请求头时必设。 - Access-Control-Max-Age:预检结果缓存时间;未设置时多为 5s,最大可设为 86400(1 天)。
其他响应头(简单请求与预检响应均可使用)
-
Access-Control-Expose-Headers :设置可暴露给 JS 脚本获取的响应头;设为
*时不会带上凭证信息。 -
Access-Control-Allow-Credentials :设为
true表示允许跨域请求携带 Cookie 等凭证。- 需配合客户端的
xhr.withCredentials = true使用(axios 中对应withCredentials)。 - 此时
Access-Control-Allow-Origin不能为*,否则请求会失败。
- 需配合客户端的
执行流程
1. 浏览器发起跨域请求
浏览器判断是否为简单请求,以及是否需要预检请求。
2. 简单请求处理
- 浏览器直接发送实际请求。
- 服务端处理请求并返回响应。
- 浏览器检查响应头:是否存在
Access-Control-Allow-Origin;若携带凭证,还需校验Access-Control-Allow-Credentials。 - 校验通过后可读取响应,否则报 CORS 错误(请求已到达,被浏览器拦截)。
3. 非简单请求(预检请求)处理
- 发送
OPTIONS预检请求,携带Access-Control-Request-Method和Access-Control-Request-Headers询问服务端许可。 - 服务端响应
Access-Control-Allow-Origin、Access-Control-Allow-Methods、Access-Control-Allow-Headers和Access-Control-Max-Age。 - 校验通过后继续发送实际请求(即
OPTIONS之后的请求);校验失败则直接拦截,不发送实际请求。 - 实际请求发出后,浏览器再次校验响应头,逻辑同简单请求。
- 若携带 Cookie/Authorization(
withCredentials = true),服务端需返回Access-Control-Allow-Credentials: true。
CORB(Cross-Origin Read Blocking)跨域读阻塞
CORB(Cross-Origin Read Blocking)跨域读阻塞是浏览器的一种安全机制,用于阻止跨域响应被恶意读取。
触发条件
- 请求源与响应源不一致。
- 响应
Content-Type为text/html、application/json、application/xml。 - 响应头未设置或不匹配
Access-Control-Allow-Origin。 - 通过 XHR、fetch 等可读取响应体的请求。
错误消息提示
CORB blocked cross-origin response
若是 fetch/XHR 请求,可能看到类似 fetch at 'xxx' from origin 'xxx' has been blocked by CORS policy 的 CORS 报错。
可查看开发者工具中 Network 面板:请求已发出但被拦截多为 CORB;请求未发出则多为 CORS 预检或策略限制。
与 CORS 的关系
- CORS 是浏览器对跨域请求的授权机制。
- CORB 是浏览器在渲染进程层对高风险响应的读阻塞机制,仅拦截可被 JS 解析的敏感类型响应。
Nginx 跨域代理配置
-
使用
Access-Control-Allow-Origin且需携带 Cookie 时,必须指定具体域名。 -
可用子域名管理接口,例如
api.xxx.com。 -
使用 map 统一管理允许的域名列表,语法形如:
map $var1 $var2 { default xxx; ... }。-
$var1:输入,一般为 Nginx 内置变量(如$http_origin)。 -
$var2:输出,由 map 中 key-value 映射得到。 -
{ default xxx; ... }:default为默认值,匹配不到时使用该值;否则按后续key value映射,当$var1匹配到某key时,$var2取对应value。
-
注意 :Access-Control-Allow-Origin 在需要携带 Cookie 时必须指定具体域名(不能为 *)。
nginx
server {
listen 80;
server_name api.xxx.com; # 后端接口域名(建议单独用 api 子域名)
# 允许的前端域名列表(用 map 统一管理,便于维护)
map $http_origin $allow_origin {
default ""; # 默认拒绝
"~^https?://(a\.xxx\.com|b\.xxx\.com|www\.xxx\.com)$" $http_origin; # 允许的域名(支持 http/https)
}
location / {
# 跨域响应头(按 Origin 动态匹配允许的域名)
add_header Access-Control-Allow-Origin $allow_origin always;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always; # 支持全量常用方法
add_header Access-Control-Allow-Headers "Content-Type, Authorization, Token, X-Requested-With" always; # 补充 Token/Authorization(前后端鉴权常用)
add_header Access-Control-Allow-Credentials "true" always;
add_header Access-Control-Max-Age "86400" always; # 缓存 24 小时(减少预检请求)
add_header Vary "Origin" always; # 配合多域名,避免浏览器缓存冲突
# 处理预检请求
if ($request_method = 'OPTIONS') {
return 204;
}
# 后端代理配置(生产环境建议加超时和缓冲区配置)
proxy_pass http://backend_service; # 后端服务(可配置 upstream 集群)
proxy_connect_timeout 10s;
proxy_read_timeout 30s;
proxy_buffer_size 4k;
proxy_buffers 4 16k;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
其他
父子页面跨域通信 :可基于 <iframe> 与 postMessage API 实现;需注意避免响应头 X-Frame-Options: SAMEORIGIN 导致子页面无法被嵌入。
本地开发:可通过本地代理规避跨域,例如 webpack-dev-server、Vite 的 dev server proxy 均以代理方式转发请求。
electron 中微前端跨域场景 :在 eletron 客户端内编译渲染 HTML 页面(不是访问)实现多项目的微前端,项目内部控制发起项目域名的请求,此时请求协议可能为 app:// ,就会导致跨域,需要项目服务端放开对应协议来支持跨域请求。
总结
- 同源:协议 + 域名 + 端口一致;否则算跨域。
- JSONP:靠 script 跨域 + callback 名,只支持 GET,现在很少用。
- CORS :服务端加响应头放行;简单请求直接带
Access-Control-Allow-Origin,非简单请求先走 OPTIONS 预检,再发实际请求。带 Cookie 时不能写*,要写具体域名。 - CORB:浏览器拦「跨源 + 敏感 Content-Type + 无/错 CORS 头」的响应,避免被 JS 读到。报错像 CORS 时看 Network:已发出被拦多半是 CORB,没发出多半是 CORS。