打破跨域限制,7种前端跨域解决方案全解析!
跨域问题是前端开发中常见的问题,本篇文章将介绍7种前端跨域解决方案,包括JSONP、CORS、WebSocket、Hash、postMessage、Nginx代理和Node中间件代理。通过了解这些方案的原理和使用场景,你就可以根据不同的需求选择适合自己的方案,从而顺利解决跨域问题。
什么是跨域?
当浏览器向一个不同域名、不同端口或不同协议的资源发出请求时,产生的问题,称为跨域问题。这是由于同源策略(Same-Origin Policy)导致的。
什么是同源策略及限制?
同源策略是一种用于隔离潜在恶意文件的关键的安全机制(如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击),它会限制以下的的种行为。
- Cookie、LocalStorage 和 IndexDB 无法读取
- DOM和JS对象无法获得
- AJAX 请求不能发送
同源策略是一个重要的安全策略,它用于限制一个origin的文档或者它加载的脚本如何能与另一个源的资源进行交互。它能帮助阻隔恶意文档,减少可能被攻击的媒介。--来源 MDN
怎么样才算是同源?
URL构成
理解同源,需要先搞懂URL的构成,来看一个URL示例
http://www.example.com:80/path/to/myfile.html?key1=value1&key2=value2#SomewhereInTheDocument
说明如下
同源,则需要protocol(协议)、domain(域名)、port(端口)三者一致才是同源。
同源
http://example.com/app1/index.html
http://example.com/app2/index.html
非同源
//协议不同
http://example.com/app1
https://example.com/app2
//主机名不同
http://example.com
http://www.example.com
http://myapp.example.com
//端口不同
http://example.com
http://example.com:8080
注意
以下两个地址是同源的,因为服务器默认从 80 端口传送 HTTP 内容。(https默认443)
http://Example.com:80
http://example.com
https://www.example.com:443
https://www.example.com
- 网站的域名是不区分大小写的
- 网站后面的路径是区分大小写的
如何解决跨域
JSONP跨域
原理,利用<script>
标签没有跨域限制。
示例:
<script>
function handleResponse(response) {
console.log(response); // 处理响应数据
}
var script = document.createElement('script');
script.src = 'https://example.com/api?callback=handleResponse';
document.head.appendChild(script);
</script>
解析:
前端动态创建一个<script>
标签,并将它的src
属性设置为需要访问的 API 地址,同时在 URL 中携带一个callback
参数,参数值为一个客户端定义的回调函数名称,例如handleResponse
后端会根据前端传入的callback
参数,生成一个包含 JSON 数据的响应,并将回调函数名称和 JSON 数据作为参数包裹在一个函数调用中返回给前端。
//例如,如果客户端传入的回调函数名称为`handleResponse`,那么后端返回内容则为
handleResponse({"name":"John","age":30})
前端收到响应后,自动将响应内容作为<script>
标签的内容插入到页面,并执行回调函数,我们则在回调函数中处理后端返回的数据。
注意:
JSONP 只支持 GET 请求,而且只能接收 JSON 格式的数据,无法处理其他格式的数据。同时,JSONP 在传输数据时是通过在页面中动态创建<script>
标签来实现的,因此需要保证返回的数据是可执行的 JavaScript 代码,避免被注入恶意代码导致安全问题。
JSONP常见应用场景
- 跨域数据获取:如果我们需要从一个不同域名下的服务器获取数据,由于同源策略的限制,我们无法直接获取数据,此时可以使用 JSONP 进行跨域数据获取。
- 跨域数据展示:如果我们需要在一个网页中展示另一个不同域名下的网页的数据,同样可以使用 JSONP 进行跨域数据展示。
- 跨域 API 调用:如果我们正在开发一个单页应用程序,而且需要使用多个不同域名下的 API 接口,也可以使用 JSONP 进行跨域 API 调用。
Hash跨域
通过改变URL中的hash值来进行跨域通信。
这里的hash指的是什么?就是URL # 号后面的内容
示例:
假设我们有两个域名为 a.com 和 b.com 的网站,它们需要进行跨域通信,a.com 的页面需要向 b.com 发送一个消息,让 b.com 的页面展示该消息。
在 a.com 的页面中,使用JavaScript代码改变 b.com 页面的URL,将一个特定的hash值传递给 b.com 页面
var message = 'Hello, b.com!';
var url = 'http://b.com/#' + encodeURIComponent(message);
window.location.href = url;
在 b.com 的页面中,使用JavaScript代码监听URL的hash值变化,如果发现hash值发生了改变,就获取传递过来的消息,并展示出来。
window.onhashchange = function() {
var message = decodeURIComponent(location.hash.substr(1));
alert(message);
}
解析:
以上示例中,使用了Hash解决跨域问题时,首先是在URL中添加一个特定的hash值来传递数据。然后通过监听 window 对象的 onhashchange 事件来检测hash值的变化。如果hash值发生了改变,就从URL中获取传递过来的消息。
由于hash值的改变不会导致页面的刷新,所以可以通过改变hash值来触发特定的JavaScript代码,从而完成跨域通信。
注意:
Hash解决跨域问题只适用于前端跨域通信,不能解决后端跨域问题(如果我们需要进行后端跨域通信,就需要使用其他技术,比如JSONP、CORS等)。同时,使用Hash传递数据的大小也有限制,通常不建议在Hash中传递大量数据。
Hash常见应用场景
- 页面嵌套:如果我们需要在一个页面中嵌套另一个域名下的页面,而且需要让这两个页面之间进行通信,那么可以使用Hash来实现跨域通信。
- 单页应用:如果我们正在开发一个单页应用程序,而且需要使用多个不同域名下的API接口,那么可以使用Hash来进行跨域通信。
postMessage跨域
postMessage 是一种 HTML5 新增的跨文档通信方式,它可以在两个不同窗口之间进行安全跨域通信。postMessage 的基本原理是在一个窗口中发送消息,在另一个窗口中监听消息并进行处理,从而完成跨域通信。
示例:
发消息
var targetWindow = window.parent; // 获取目标窗口
var message = 'Hello, parent window!';
var targetOrigin = 'http://parent.window.com'; // 指定接收消息的窗口的源
targetWindow.postMessage(message, targetOrigin); // 发送消息postMessage(data,origin)
接收消息
window.addEventListener('message', function(event) {
var message = event.data; // 获取消息内容
var origin = event.origin; // 获取消息来源
if (origin === 'http://child.window.com') { // 判断消息来源
console.log('Received message from child window:', message);
}
});
解析:
在发送消息的窗口中,使用 postMessage() 方法发送消息给目标窗口。postMessage() 方法的参数包括要发送的消息、目标窗口的 origin(指定接收消息的窗口的源)。
在接收消息的窗口中,监听 message 事件,当接收到消息时进行处理。接收到的消息是一个包含消息内容和消息来源信息的 event 对象,我们可以从 event 对象中获取消息内容并进行处理。
注意:
使用 postMessage 跨域通信时需要注意安全性问题,可以通过指定目标窗口的 origin 来限制接收消息的窗口,防止消息被非法窗口篡改。同时,在接收消息时也需要对消息来源进行判断,以保证消息来源的合法性。
postMessage常见应用场景
- 窗口嵌套:如果我们在一个网页中嵌套了另一个网页,而且需要这两个网页之间进行通信,就可以使用 window.postMessage 方法进行跨文档通信。
- 跨域消息传递:如果我们需要在两个不同域名下的网页之间进行通信,由于同源策略的限制,我们无法直接访问对方的 DOM,此时可以使用 window.postMessage 方法进行跨域消息传递。
- Web Worker 通信:如果我们正在使用 Web Worker 进行多线程编程,而且需要让不同的 Worker 之间进行通信,也可以使用 window.postMessage 方法进行通信。
WebSocket协议跨域
WebSocket 是一种基于 TCP 协议的双向通信协议,它提供了一种浏览器和服务器之间实时、低延迟、高效率的全双工通信方式,同时允许跨域通讯。
浏览器在发送 WebSocket 请求时,会在请求头中携带 Origin 字段,用于告诉服务器该请求的来源。服务器在收到请求后,会根据 Origin 字段判断是否允许该跨域请求,如果允许,则在响应头中添加 Access-Control-Allow-Origin 字段,告诉浏览器该请求被允许访问。
示例:
假设我们有两个不同的域名:
- example.com:前端页面所在的域名。
- api.example.com:后端 API 服务器所在的域名。
我们需要在前端页面中使用 WebSocket 向后端 API 服务器发送请求,并接收服务器返回的数据。
前端代码
const socket = new WebSocket('wss://api.example.com/ws');
socket.addEventListener('open', (event) => {
console.log('WebSocket connection established.');
socket.send('Hello, server!');
});
socket.addEventListener('message', (event) => {
console.log('Received message from server:', event.data);
});
socket.addEventListener('close', (event) => {
console.log('WebSocket connection closed.');
});
socket.addEventListener('error', (event) => {
console.error('WebSocket connection error:', event);
});
后端代码(使用 Node.js 和 ws 模块)
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', (socket) => {
console.log('WebSocket connection established.');
socket.on('message', (message) => {
console.log('Received message from client:', message);
socket.send(`Hello, client! You said: "${message}"`);
});
socket.on('close', () => {
console.log('WebSocket connection closed.');
});
socket.on('error', (error) => {
console.error('WebSocket connection error:', error);
});
});
解析:
通过创建一个 WebSocket 对象来建立与后端 API 服务器的连接,并监听 WebSocket 的 open、message、close 和 error 事件。在发送消息时,我们可以使用 WebSocket 对象的 send() 方法向服务器发送消息。服务器收到消息后,会将消息内容原样返回给客户端。通过监听 message 事件,我们可以接收服务器返回的数据,并进行处理。
注意:
在使用 WebSocket 进行跨域通信时,需要进行一些安全设置,例如对消息内容进行验证和过滤,避免被注入恶意代码导致安全问题。此外,在使用 WebSocket 进行跨域通信时,服务器也需要进行相应的设置,确保连接的安全性和稳定性。
原生WebSocket API使用起来不太方便,可以使用Socket.io,它很好地封装了webSocket接口,提供了更简单、灵活的接口,也对不支持webSocket的浏览器提供了向下兼容。
跨域资源共享(CORS)
CORS(Cross-Origin Resource Sharing)是一种机制,用于允许浏览器向跨源服务器发送 XMLHttpRequest 请求,从而克服浏览器的同源限制。
CORS需要浏览器和服务器同时支持。整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。
因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。
服务端配置:
服务端需要在 HTTP 响应头中添加 CORS 相关的头信息。具体来说,需要添加 Access-Control-Allow-Origin、Access-Control-Allow-Methods、Access-Control-Allow-Headers 和 Access-Control-Allow-Credentials 头部。
- Access-Control-Allow-Origin:该头部指定哪些源站是允许访问的。可以使用通配符 "*" 表示允许所有来源,或者指定具体的源站地址。
- Access-Control-Allow-Methods:该头部指定允许的 HTTP 方法。通常指定 GET、POST、PUT、DELETE 等方法。
- Access-Control-Allow-Headers:该头部指定允许的 HTTP 请求头部。例如,可以指定 X-Requested-With、Content-Type 等头部。
- Access-Control-Allow-Credentials:该头部指定是否允许发送 Cookie、授权头等数据。如果设置为 true,则表示允许发送这些数据;否则,表示禁止发送这些数据。
NodeJS代码
var http = require('http');
var server = http.createServer();
var qs = require('querystring');
server.on('request', function(req, res) {
var postData = '';
// 数据块接收中
req.addListener('data', function(chunk) {
postData += chunk;
});
// 数据接收完毕
req.addListener('end', function() {
postData = qs.parse(postData);
// 跨域后台设置
res.writeHead(200, {
'Access-Control-Allow-Credentials': 'true', // 后端允许发送Cookie
'Access-Control-Allow-Origin': 'http://www.domain1.com', // 允许访问的域(协议+域名+端口)
/*
* 此处设置的cookie还是domain2的而非domain1,因为后端也不能跨域写cookie(nginx反向代理可以实现),
* 但只要domain2中写入一次cookie认证,后面的跨域接口都能从domain2中获取cookie,从而实现所有的接口都能跨域访问
*/
'Set-Cookie': 'l=a123456;Path=/;Domain=www.domain2.com;HttpOnly' // HttpOnly的作用是让js无法读取cookie
});
res.write(JSON.stringify(postData));
res.end();
});
});
server.listen('8080');
console.log('Server is running at port 8080...');
nginx代理跨域
nginx代理跨域,实质和CORS跨域原理一样,通过配置文件设置请求响应头Access-Control-Allow-Origin…等字段。
示例:
1)nginx配置解决iconfont跨域
浏览器跨域访问js、css、img等常规静态资源被同源策略许可,但iconfont字体文件(eot|otf|ttf|woff|svg)例外,此时可在nginx的静态资源服务器中加入以下配置。
location /fonts {
proxy_pass https://example.com/fonts;
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
add_header Access-Control-Allow-Headers 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
}
这个配置表示,当客户端向 Nginx 发送请求 /fonts 时,Nginx 会将请求转发给 example.com/fonts。 同时,我们还在响应头中添加了Access-Control-Allow-Origin、Access-Control-Allow-Methods 和 Access-Control-Allow-Headers 等头部信息,以允许跨域访问字体文件。
2)nginx反向代理接口跨域
浏览器是禁止跨域的,但是服务端不禁止
实现思路:通过nginx配置一个代理服务器(域名与domain1相同,端口不同)做中间件,反向代理访问domain2接口。
nginx具体配置:
#proxy服务器
server {
listen 81;
server_name www.domain1.com;
location / {
proxy_pass http://www.domain2.com:8080; #反向代理
proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie里域名
index index.html index.htm;
# 当用webpack-dev-server等中间件代理接口访问nignx时,此时无浏览器参与,故没有同源限制,下面的跨域配置可不启用
add_header Access-Control-Allow-Origin http://www.domain1.com; #当前端只跨域不带cookie时,可为*
add_header Access-Control-Allow-Credentials true;
}
}
此处的意思为:nginx 反向代理服务监听 www.domain1.com 的81端口,如果有请求过来,则转到proxy_pass配置的对应服务器上。
正向代理和反向代理的区别,一句话就是:如果我们客户端自己用,就是正向代理。如果是服务器在用,我们用户无感知,就是反向代理。
nodejs中间件代理跨域
node中间件实现跨域代理,原理大致与nginx相同,都是通过启一个代理服务器,实现数据的转发,也可以通过设置cookieDomainRewrite参数修改响应头中cookie中域名,实现当前域的cookie写入,方便接口登录认证。
比如比较常见的vue框架的跨域
node + vue + webpack + webpack-dev-server搭建的项目,跨域请求接口,直接修改webpack.config.js配置。开发环境下,vue渲染服务和接口代理服务都是webpack-dev-server同一个,所以页面与代理接口之间不再跨域。
webpack.config.js部分配置:
module.exports = {
entry: {},
module: {},
...
devServer: {
historyApiFallback: true,
proxy: [{
context: '/login',
target: 'http://www.domain2.com:8080', // 代理跨域目标接口
changeOrigin: true,
secure: false, // 当代理某些https服务报错时用
cookieDomainRewrite: 'www.domain1.com' // 可以为false,表示不修改
}],
noInfo: true
}
}
总结
jsonp(只支持get请求,支持老的IE浏览器)适合加载不同域名的js、css,img等静态资源;
CORS(支持所有类型的HTTP请求,但浏览器IE10以下不支持)适合做ajax各种跨域请求;
Nginx代理跨域和nodejs中间件跨域原理都相似,都是搭建一个服务器,直接在服务器端请求HTTP接口,这适合前后端分离的前端项目调后端接口。
postMessage、websocket都是HTML5新特性,兼容性不是很好,只适用于主流浏览器和IE10+。
🎨【点赞】【关注】不迷路,更多前端干货等你解锁
往期推荐
👉 Vite基础入门
转载自:https://juejin.cn/post/7211774164464566333