什么是跨域
- 源 = 协议 + 域名 + 端口
- 只要 协议、域名、端口 有一个不一样,就会引起跨域
跨域出现的场景
-
网络通讯
<a/>跳转: ( 轻微限制, 几乎感觉不到有限制 )- 加载 css,
js, 图片: ( 轻微限制, 几乎感觉不到有限制 ) - ajax请求: ( 严厉限制 )
-
JS API
- wind.open
- wind.parent
- iframe.contentWindow
-
存储
- webStorage
- indexedDB
🔥window.open
场景:打开新窗口
// 当前页面:http://a.com
const newWin = window.open("http://b.com");
同源时 ✅ 可以:
- 操作新窗口:
newWin.document.write() - 读数据:
newWin.location.href - 调用方法:
newWin.close()
跨域时 ❌ 报错:
Uncaught DOMException: Blocked a frame with origin "a.com" from accessing a cross-origin frame.
通俗解释:
- 你打开别人家的门可以,
- 但不能进别人家翻东西。
🔥window.parent
场景:在 iframe 里访问父页面
父页面:http://a.com
iframe 里:http://b.com
iframe 内部想访问爹:
window.parent.document.write("hello");
跨域时 ❌ 直接报错
- 浏览器不让子页面乱碰父页面
🔥iframe.contentWindow
场景:父页面想操控 iframe:
const iframe = document.querySelector("iframe");
iframe.contentWindow.document.body.style.color = "red";
跨域时 ❌ 报错
- 父不能读子、
- 子不能读父。
CORS
🔥最简单例子
后端返回这几个头,就实现了 CORS:
# 前端在 `http://localhost:3000`
# 请求后端 `http://localhost:8080/api`
# 浏览器一看:允许,就不拦了。
# 只能写单个域名
# 要支持多个域名,后端必须动态判断返回
Access-Control-Allow-Origin: http://localhost:3000
# 允许哪些请求方法
Access-Control-Allow-Methods: GET,POST,PUT,DELETE
# 允许前端传哪些请求头
Access-Control-Allow-Headers: Content-Type, Authorization, token
# 是否允许跨域带 Cookie
Access-Control-Allow-Credentials: true
# 允许前端读哪些响应头
Access-Control-Expose-Headers: Content-Type, Authorization, token
# 有效期内不再发 OPTIONS请求
Access-Control-Max-Age: 86400
🔥带 Cookie 的跨域
- 最容易踩坑
- 要同时满足 3 点
1、前端
// 前端域名: a.com
const xhr = new XMLHttpRequest();
xhr.open('POST', 'https://b.com/api');
xhr.withCredentials = true; // 核心:允许跨域携带 Cookie
xhr.send(formData);
// 或者
fetch('https://b.com/api', {
method: 'POST',
credentials: 'include', // 核心:等价于 withCredentials: true
body: formData
});
2、后端
# 后端域名: b.com
Access-Control-Allow-Origin: 你的前端域名(a.com)
Access-Control-Allow-Credentials: true
3、Cookie 属性
# 1. 必须 HTTPS
SameSite=None; Secure;
🔥跨域获取响应头
问题背景
-
默认情况:跨域 → 只能看到这几个响应头
Cache-ControlContent-LanguageContent-TypeExpiresLast-ModifiedPragma
-
其他的你通通拿不到!比如:
tokenAuthorizationsignx-request-idx-total-count(分页总数)
-
解决方案
- 因为跨域是不安全的,浏览器怕你随便拿敏感头
- 后端必须明确告诉浏览器:哪些头可以暴露给前端 JS。
- 你要暴露几个,就写几个,用逗号分隔。
- 加上之后,前端 JS 才能读到。
Access-Control-Expose-Headers: token,Authorization,x-total-count
具体例子
后端要让前端拿到分页总数 x-total-count
后端返回头:
Access-Control-Allow-Origin: https://frontend.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: x-total-count, token
x-total-count: 100
token: abc123
前端就能拿到:
// fetch
const res = await fetch(...)
const total = res.headers.get('x-total-count')
const token = res.headers.get('token')
// xhr
xhr.getResponseHeader('x-total-count')
JSONP
- JSONP 使用简单且兼容性不错,但是只限于 get 请求
- 不安全, 不建议使用
- JSONP 的原理很简单,就是利用
🔥 简单例子
// 注意: 要先定义函数,再添加<script>标签
<script>
function jsonp(data) {
console.log(data)
}
</script>
<script src="http://domain/api?param1=a¶m2=b&callback=jsonp"></script>
🔥封装 jsonp
// 在开发中可能会遇到多个 JSONP 请求的回调函数名是相同的,这时候就需要自己封装一个 JSONP,以下是简单实现
function jsonp({url, params, cb}) {
return new Promise((resolve, reject) => {
// @ 1 : 定义 call back 函数
window[cb] = function (data) {
resolve(data)
document.body.removeChild(script)
}
// @ 2 : 设置url参数 'https://www.sdf.com' ---> 'https://www.sdf.com?wd=a&cb=callback'
url = `${url}?${stringify(params)}&cb=${cb}`
// @ 3 : 添加 script 标签
let script = document.createElement("script")
script.src = url
document.body.appendChild(script) // script标签将会调用window[cb]函数
})
}
function stringify(params) {
let list = []
for(let key in params) {
list.push(`${key}=${params[key]}`)
}
return list.join('&')
}
// 使用
jsonp({
url: 'https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su', // ------>> 这是百度搜索的接口
params: {wd: "b"},
cb: 'show'
})
.then(res => console.log(res))
代理
🔥webpack 开发环境代理
✅ 前端请求 /user/list
✅ 转发到 http://localhost:8080/user/list
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
devServer: {
static: './dist',
port: 3000,
open: true,
// 🔥 代理跨域核心
proxy: {
'/': {
target: 'http://localhost:8080', // 后端地址
changeOrigin: true, // 必须开
secure: false, // 如果是 https 但证书不安全,打开
}
}
}
};
方案选择
🔥情况1
-
背景:
- 生产环境没有跨域问题
-
方案:
- 生产环境无需处理
- 开发环境使用代理(webpack)
🔥情况2
-
背景:
- 生产环境有跨域问题
- 不需要支持古老浏览器
-
方案:
- 生产环境使用CORS
- 开发环境使用CORS
🔥情况3
-
背景:
- 生产环境有跨域问题
- 需要支持古老浏览器
-
方案:
- 生产环境JSONP
- 开发环境JSONP