打破跨域限制,7种前端跨域解决方案全解析!

6,889 阅读13分钟

本文正在参加「金石计划」

跨域问题是前端开发中常见的问题,本篇文章将介绍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

说明如下

image-20230223215742377

同源,则需要protocol(协议)、domain(域名)、port(端口)三者一致才是同源。

image-20230223220140776

同源

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 调用。

Vue axios 使用jsonp

jquery Ajax 使用jsonp


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 字段,告诉浏览器该请求被允许访问。

示例

假设我们有两个不同的域名:

我们需要在前端页面中使用 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 头部。

  1. Access-Control-Allow-Origin:该头部指定哪些源站是允许访问的。可以使用通配符 "*" 表示允许所有来源,或者指定具体的源站地址。
  2. Access-Control-Allow-Methods:该头部指定允许的 HTTP 方法。通常指定 GET、POST、PUT、DELETE 等方法。
  3. Access-Control-Allow-Headers:该头部指定允许的 HTTP 请求头部。例如,可以指定 X-Requested-With、Content-Type 等头部。
  4. 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...');

跨域资源共享 CORS 详解


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基础入门

👉 15 个JavaScript数组实用技巧

👉 JavaScript数组方法看这一篇就够了

👉 JS内置日期对象Date的使用指南