跨域9大解决方案(超详细) 总结

article/2025/9/20 7:00:02

前面的话

我们经常听到跨域这词,这是由于浏览器同源策略限制的一类请求场景。这样做的目的使得 浏览器不容易受到攻击。

推荐文章:九种跨域方式实现原理(完整版)

什么是同源策略?

同源策略(Same origin policy)是一种约定,所谓同源是指“协议、域名、端口”三者都相同,如果没有同源策略,浏览器很容易受到XSS、CSRF等攻击。

同源策略限制了什么?

1)Cookie、LocalStorage和IndexDB无法获取
2) DOM 和JS对象无法获取
3)AJAX请求不能发送

不满足协议、域名、端口相同的都不能通信:
在这里插入图片描述

跨域的解决方法

  • CORS(跨域资源共享)
  • 通过jsonp跨域
  • document.domain + iframe跨域
  • location.hash + iframe跨域
  • window.name + iframe跨域
  • postMessage跨域
  • WebSocket协议跨域
  • nginx代理跨域
  • nodejs中间件代理跨域

CORS (Cross-origin resource sharing跨域资源共享)

CORS的背后基本思想:使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求响应是应该成功还是应该失败。

  • 使用node创建两个http服务器,监听端口8081和8084
  • cors.html运行在8081下

cors1.js服务,监听8081

var express = require('express');
var app = express();
// public下有cors.html页面
app.use(express.static('public')).listen(8081);

cors.html请求代码:

<script>let xhr = new XMLHttpRequest();document.cookie = 'name=xiaoqi';// cookie不能跨域xhr.withCredentials = true;// 前端设置是否带cookiexhr.open('PUT', 'http://localhost:8084/getData', true);xhr.setRequestHeader('name', 'xiaoqi');// 设置请求头xhr.onreadystatechange = function() {if(xhr.readyState===4 && xhr.status===200){console.log(xhr.response);// 返回响应头的name字段的值console.log(xhr.getResponseHeader('name'));}}xhr.send();
</script>

cors2.js服务,监听8084:

 var express = require('express');
var app = express();
// 设置白名单
let whiteList = ['http://localhost:8081'];
app.use(function(req, res, next) {let origin = req.headers.origin;if(whiteList.includes(origin)){// 设置哪个源可以访问我res.setHeader('Access-Control-Allow-Origin', origin);// 允许携带哪个头来访问我res.setHeader('Access-Control-Allow-Headers', 'name');// 允许哪个方法访问我res.setHeader('Access-Control-Allow-Methods', 'PUT');// 允许携带cookieres.setHeader('Access-Control-Allow-Credentials', 'true');// 预检的存在时间res.setHeader('Access-Control-Max-Age',6);// 允许返回的头res.setHeader('Access-Control-Expose-Headers', 'name');if(req.method === 'OPTIONS'){res.end()// OPTIONS请求不做任何处理}}next();
})
app.put('/getData', function(req, res){console.log(req.headers);res.setHeader('name', 'jw');res.end('hhh');
});
app.use(express.static('public1')).listen(8084);

显然8081与8084不在同一个域,在后端(cors2.js)代码中自动设置HTTP的响应头,来规定哪些域能访问。

上面代码的结果:
在这里插入图片描述

通过jsonp跨域

jsonp跨域原理是通过<script>标签来实现的。在HTML页面中通过相应的<script>标签从不同域名下加载资源,这种方式是被浏览器运行的。

注意: jsonp只能实现GET方法一种请求

我们也可以动态创建script标签,再请求一个带参网址实现跨域通信。

前端页面请求:

  • 原生实现
     <script>var script = document.createElement('script');script.type = 'text/javascript';script.src = 'http://localhost:8081?user=admin&callback=onBack';document.head.appendChild(script);// 回调函数function onBack(res) {// 把一个对象转为json的字符串类型alert(JSON.stringify(res));}// 服务器下的返回// onBack({"status": true,"user": "admin"});
    </script>
    
  • jquery ajax:
    $.ajax({url: 'http://localhost:8081',type: 'get',dataType: 'jsonp',  // 请求方式为jsonpjsonpCallback: "handleCallback",    // 自定义回调函数名data: {}
    });
    
  • vue.js
    this.$http.jsonp('http://localhost:8081', {params: {},jsonp: 'handleCallback'
    }).then((res) => {console.log(res); 
    })
    

后端node.js代码实现:

var qs = require('qs');
var querystring = require('querystring');
var http = require('http');
// 创建服务器
var server = http.createServer();// 监听请求
server.on('request', function(req, res) {// qs.parse()将URL解析成对象的形式: params为{ user: 'admin', callback: 'onBack' }var params = qs.parse(req.url.split('?')[1]);var fn = params.callback;// 'onBack'// jsonp返回设置res.writeHead(200, {'Content-Type': 'text/javascript'})// res.write用来向请求的客户端发送响应的内容res.write(fn + '(' + JSON.stringify({"status": true, "user": "admin"})+ ')');res.end();
})// 监听端口8081
server.listen('8081');
console.log('Server is running at port 8081...');

请求结果:
在这里插入图片描述

document.domain + iframe跨域

document.domain是用来得到当前网页的域名。

比如,在百度(https://www.baidu.com)页面控制台中输入:

alert(document.domain);  // `www.baidu.com`

我们也可以给document.domain属性赋值,不过是有限的, 只能赋值为当前域名或者一级域名

alert(document.domain = "baidu.com"); // `baidu`
alert(document.domain = "www.baidu.com"); // `www.baidu.com`

上面的赋值都是成功的:
在这里插入图片描述
下面的赋值则无效:

alert(document.domain = "qq.com"); // `baidu`
alert(document.domain = "www.qq.com"); // `www.baidu.com`

qq.com与baidu.com的一级域名不相同,所以出错。
在这里插入图片描述

如何使用document.domain + iframe实现跨域?
  • 实现原理:两个页面都通过js强制设置document.domain为基础主域,就实现了同域。
  • 前提条件: 这两个域名必须属于同一个一级域名,并且所用协议,端口相同,否则无法使用其方法来实现跨域。

注意:该方案仅限主域相同、子域不同的跨域场景

实例:

	news.baidu.com里的一个网页(news.html)引入了map.baidu.com里的一个
网页(map.html),这两个域是不相同的,但它们的主域都相同,都是baidu.com,
可以使用该方法实现跨域,将两个页面的domain都改为“baidu.com”.在news.html
与map.html中都加入:document.domain = "baidu.com"

代码:

new.baidu.com下的news.html:

 <iframe id="iframe1" src="http://map.baidu.com/map/html"></iframe><script>document.domain = 'baidu.com';var iframe1  = document.getElementById("iframe1");// 返回iframe中的文档。兼容写法var doc = iframe1.contentDocument || iframe1.contentWindow.document;var p1 =  doc.getElementById('p1');alert(p1.innerHTML);
</script>

动态设置iframe的src更好,这样可以避免阻塞页面加载。

map.baidu.com下的map.html:

 <p id="p1"> 我是map.baidu.com中的p</p>
<script>document.domain = 'baidu.com';
</script>
iframe阻塞页面加载

这里iframe,就不得不说一下使用它的性能问题。

  • 及时触发 window 的 onload 事件是非常重要的。onload 事件触发使浏览器的 “忙” 指示器停止,告诉用户当前网页已经加载完毕。当 onload 事件加载延迟后,它给用户的感觉就是这个网页非常慢。

  • window 的 onload 事件需要在所有 iframe 加载完毕后(包含里面的元素)才会触发。在 Safari 和 Chrome 里,通过 JavaScript 动态设置 iframe 的 SRC 可以避免这种阻塞情况。

location.hash + iframe跨域

原理: 利用location.hash来进行传值。在url:http://a.com#helloword中的#helloword就是location.hash,改变hash并不会导致页面刷新,所以可以利用hash值来进程数据传递。

例如:在域名a.com下的a1.html文件要与域名b.com下的b.html文件进行数据传递。
步骤:

  • a1.html下创建一个隐藏的iframe(不影响页面格局),其src指向域名b.com下的b.html页面(http://b.com#hello) (#hello为hash值,用来传递数据)
  • b.html响应请求后,可以通过修改a1.html的hash值来传递数据。(只不过在不同域名下,IE、Chrome是不允许直接在b.html中直接通过parent.lacation.hash来改变a1.html的hash值;fireFox可以)。所以为了兼容,在IE、Chrome环境下,要借助a.com域名下的一个代理iframe(a2.html)。
  • 如果是IE、Chrome浏览器,就在b.html文件下创建一个隐藏的iframe,其src指向a2.html。通过parent.parent.location.hash来改变a1.html的hash值。

代码如下:

a.com下的a.html:

<script>// a1.html// 创建一个隐藏的iframevar iframe = document.createElement('iframe');iframe.src = "http://b.com/b.html#hello";iframe.style.display = 'none';document.body.appendChild(iframe);// 监听hash值function checkHash() {var data = location.hash? location.hash.substring(1): '';console.log(data);}// 每隔1s检测以下hash值是否变化setTimeout(checkHash,1000);
</script>

b.com下的b.html:

<script>// b.html// 模拟简单的location.hash值的改变switch(location.hash){case "#hello":// 如果hash值是hello,执行回调函数callback();break;case '#hhh':// ...}function callback(){try {// 如果是FireFox可以直接修改a1.html的hash值parent.location.hash = '#hello';} catch (error) {// 如果是在IE、Chrome下,就要创建a.com下的一个代理var iframe = document.createElement('iframe');iframe.style.display = 'none';// 将其hash值设置为b.html的hash值iframe.src = 'http://a.com/a2.html#hello';document.body.appendChild(iframe);}} 
</script>

域名a.com下的a2.html:

<script>// a2.html// a2.html与a1.html同域,可以通过a2.html来改变a1.html的hash值。parent.parent.location.hash = self.location.hash.substring(1);
</script> 

这种方法有很多缺点:数据都暴露在URL、数据容量有限等。

window.name + iframe跨域

window.name属性的独特之处:name值在不同的页面(甚至不同域名)加载后依旧存在,并且可以支持非常长的 name 值(2MB)。

利用这个特性,可以与iframe结合实现跨域。

实例:

  • 使用node创建两个http服务器,监听端口8081和8088
  • a.html运行在8081下
  • b.html运行在8088下

代码如下:

监听端口8081:

// 导入 express
const express = require('express');
const app = express();// 将public目录导入,public下有a.html文件
app.use(express.static('public')).listen(8081);

localhost8081下的a.html代码:

<script>var iframe = document.createElement('iframe');iframe.src = "http://localhost:8088/b.html";iframe.style.display = 'none';var flag = 0;// onload会触发两次,第一次触发保存b.html下的数据,第二次重定向之后触发,读取同域的window.nameiframe.onload = function() {if(flag == 1){// 返回b.html下的数据数据console.log("跨域资源", iframe.contentWindow.name);}else if(flag == 0) {// 得到b.html下的数据之后,立即将src设置为同域的proxy.htmlflag = 1;// iframe.src重定向iframe.contentWindow.location = "http://localhost:8081/proxy.html";}}document.body.appendChild(iframe);
</script>

上面的代码中将iframe.src重定向,指向了http://localhost:8081/proxy.html
,这个与a.html同域,并且就是一个空文件。为什么这么做?原因是:尽管window.name的值在不同域也存在,但是iframe有一个特性规定:如果a.html页面和该页面里的iframe框架的src不同源的话,就无法操作框架里的任何东西。 既然要同源,那就当保存了b.html下的name之后,将src指向一个同域的空页面,这样就可以顺利的读出b.html下的window.name值。

注意: 改变iframe的src之后会重新触发onload。

监听端口8088:

// 导入 express
const express = require('express');
const app = express();// 将public目录导入,b.html文件里面
app.use(express.static('public1')).listen(8088);

localhost8088下的b.html代码:

 <script>window.name = "This is domain data";
</script>

结果:
在这里插入图片描述

postMessage跨域

postMessage是HTML5 XMLHttpRequest Level2中的API,且是为数不多可以跨域操作的window属性之一。
postMessage(data,origin)方法的使用:

  • data: html5规范支持任意类型,但部分浏览器只支持字符串,所以传参时最好用JSON.stringify()序列化。
  • origin:协议+主机+端口号,也可以设置为“*”,表示可以传递给任何窗口,如果要指定和当前同源的话就设置为“/”

实例:

还是像上面一样:

  • 使用node创建两个http服务器,监听端口8081和8088
  • aa.html运行在8081下
  • bb.html运行在8088下

localhost8081下的aa.html代码:

<script>var iframe = document.createElement('iframe');iframe.style.display = 'none';iframe.src = "http://localhost:8088/bb.html";document.body.appendChild(iframe);iframe.onload = function() {var data = {name: 'xiaoqi'}// 向bb.html传输跨域数据iframe.contentWindow.postMessage(JSON.stringify(data),"http://localhost:8088");}// 接受bb.html返回数据window.onmessage = function(event) {console.log('data from bb.html:' + event.data);}</script>

localhost8088下的bb.html代码:

<script>// 接受aa.html的数据window.onmessage = function(event) {console.log('data from aa.html:' + event.data);// 将aa.html传来的数据转对象var data = JSON.parse(event.data);if(data) {data.number = 16;}// 将处理后的数据再发回aa.htmlwindow.parent.postMessage(JSON.stringify(data), 'http://localhost:8081');} 
</script>

结果:
在这里插入图片描述

WebSocket协议跨域

WebSocket 是HTML5的之中新协议。它实现了浏览器与服务器全双工通信,同时允许跨域,是server push技术的一种很好的实现。

原生WebSocket API使用起来不太方便,我们使用Socket.io,它很好的封装了webSoket接口,提供了更简单的接口,也对不支持webSocket的浏览器提供了向下兼容。

前端代码:

 <div>use input: <input type="text"> </div><script src="https://cdn.bootcss.com/socket.io/2.2.0/socket.io.js"></script>
<script>// 通过io()生成客户端所需要的socket对象var socket = io('http://localhost:8081');//socket.on()用于接收服务器端发来的消息  连接成功处理socket.on('connect', function() {// 监听服务端消息socket.on('message', function(msg) {console.log('data from server:' + msg);});// 监听服务端关闭socket.on('disconnect', function() {console.log('Server socket has closed');});});// document.getElementsByTagName('input')[0].onblur = function(){// 向服务端发消息socket.send(this.value);}
</script>

后端代码:

 var http = require('http');
var socket = require('socket.io');
// 启动http服务
var server = http.createServer(function (req, res) {res.writeHead(200,{'Content-type': 'text/html'});res.end();
})server.listen(8081);
console.log('server is running at port 8081');// 监听socket连接
socket.listen(server).on('connection', function (client){// 监听客户端传来的信息client.on('message', function (msg) {console.log('data from client:'+ msg);// 向客户端发消息client.send(msg);  })// 监听客户端断开连接 client.on('disconnect', function() {console.log('Client socket  has closed');});
});

在这里插入图片描述

nginx代理跨域与nodejs中间件代理跨域见参考文章3

参考:

  • location.hash+iframe跨域
  • window.name + iframe实现跨域
  • 前端跨域方法总结

http://chatgpt.dhexx.cn/article/NWUJYWu6.shtml

相关文章

前端跨域解决方案

文章目录 注意&#xff0c;本文已迁移 1.同源政策2.跨域解决方案2.1 CORS普通跨域请求&#xff1a;只需服务端设置Access-Control-Allow-Origin即可携带cookie跨域请求&#xff1a;前后端都需设置 2.2 postMessage跨域2.3 JSONP原理缺点数据格式jsonp跨域实现 2.4 WebSocket属性…

解决跨域的三种方案

解决跨域的三种方案 到目前为止&#xff0c;我们编写的 GET 和 POST 接口&#xff0c;存在一个很严重的问题&#xff1a;不支持跨域请求 解决接口跨域问题的方案主要有三种 CORS (主流的解决方案&#xff0c;推荐使用) 代理 (推荐使用) JSONP (有缺陷的解决方案&#xff1a…

跨域的五种解决方案详解

1.跨域解决方案一:cors技术 CORS :全称cross origin resource share &#xff08;资源共享&#xff09; 工作原理&#xff1a; 服务器 在返回响应报文的时候&#xff0c;在响应头中 设置一个允许的header res.setHeader(‘Access-Control-Allow-Origin’, ‘*’) CORS :全称…

Java中parseInt用法(double类似)

1.将字符直接解析为十进制数进行输出 2.如果方法有两个参数&#xff0c; 使用第二个参数指定的基数&#xff0c;将字符串参数解析为有符号的10进制整数。&#xff08;所谓指定基数&#xff1a;就是将字符串指定为2&#xff0c;8&#xff0c;16等进制数&#xff0c;然后用解析为…

javascript:parseInt用法,特殊用法,进制转换

parseInt( string,radix ) 一、功能&#xff1a; 除了我们众所周知的字符串转换为整数以外&#xff0c;还涉及到进制问题&#xff1a; 将 [ 指定进制的 ] 字符串转换为十进制整数型 用汉语翻一下这个方法&#xff1a; 二、参数&#xff1a; 1、string: 要解析的字符串&a…

前端JS字符串转数值 Number和parseInt用法

Number() 只包含数值的字符串字符串为空则为0 parseInt() 字符串不能为空字符串第一个必须为数值从第一个数值开始取&#xff0c;到最后一个连续数值结束

java中Integer.parseInt用法详细分析(全)

目录 前言函数讲解 前言 将数字字符串转化成原生整型数据 属于java.lang.Integer 是原生类型整型的包裹类 函数讲解 1.parseInt(String s) 将字符串s转换为十进制的数字&#xff0c;默认为十进制 public static void main(String[] args) {int numInteger.parseInt("10…

parseInt用法

MDN: 从给定的字符串中解析出的一个整数。 代码&#xff1a; <script>var aparseInt("100px");console.log("a:"a" type:"typeof(a));</script>效果&#xff1a;

parseInt鲜为人知的用法

用法一&#xff1a;将小数转换成整数 var float_num 3.14;float_num parseInt(float_num);console.log(float_num);输出结果&#xff1a; 用法二&#xff1a;以数字开头的字符变量转换成整数 var str_num 3.14string;str_num parseInt(str_num);console.log(str_num);输出…

javax.servlet.http.HttpServletRequest错误

javax.servlet.http.HttpServletRequest错误 javax这个api出错 问题分析&#xff1a;javax.api找不到。 解决方案&#xff1a; 方案一&#xff1a; 可以再maven的pom.xml文件中导入方案二&#xff1a; idea没有导入tomcat下的lib目录下的api,我们手动导入就可以。 步骤如下&a…

java: 程序包javax.validation不存在

之前&#xff0c;有位同学反馈说&#xff0c;在运行newbee-mall-api项目时遇到了下面这个问题&#xff0c;无法正常编译项目&#xff0c;错误截图如下&#xff1a; 看了一下应该是NotEmpty、Valid这几个验证注解引起的&#xff0c;因为这几个注解都是定义在javax.validation包…

maven install 时提示程序包javax.crypto不存在

maven install 时提示程序包javax.crypto不存在 大家好&#xff0c;我是酷酷的韩~ 一.maven install报错原因 javax.crypto是在jdk的jre\lib目录下的,需要在编译的时候引入jdk的rt.jar包和jce.jar包。 二.解决办法 <build><plugins><plugin><groupId&…

javax.crypto.AEADBadTagException: Tag mismatch 的解决办法

问题概述 关于这个问题&#xff0c;博主是在微信支付开发与配置过程中遇到的&#xff0c;在使用工具 “ CertificateDownloader-1.1.jar ” 生成平台证书时&#xff0c;报&#xff1a;“ javax.crypto.AEADBadTagException: Tag mismatch! ” ,提示标签不匹配&#xff0c; 如下…

java javax.servlet_java.lang.NoClassDefFoundError: javax/servlet/ServletOutputStream 报错解决

报错的方法栈 在ssm中写了一个 utils 类&#xff0c;定义了main方法测试和查看工具实际数据 运行main方法时报错了&#xff0c;之前都没错&#xff0c;莫名其妙报了错 java.lang.NoClassDefFoundError: javax/servlet/ServletOutputStream at java.lang.Class.getDeclaredMetho…

javax 和hibernate 的NotBlank

问题&#xff1a;hibernate版本在5 的时候&#xff0c;如果你用了 import javax.validation.constraints.NotBlank; 在校验的时会报错 HV000030: No validator could be found for constraint javax.validation.constraints.NotBlank validating type java.lang.String. Chec…

java和javax的区别

java与javax的区别分析 Java是一种受C语言影响的编程语言。Java和Javax本质上是与Java编程语言的上下文一起使用的包。实际上Java和Javax没有区别。这只是不同的名字。Java是一种编程语言&#xff0c;受到C语言的影响。它源自C和C的大部分语法&#xff0c;但是它的低级别设施比…

使用 javax.mail 发送邮件

发邮件在 java web 项目中是一个常用功能&#xff0c;之前在项目中刚好用到了邮件发送功能&#xff0c;现在用博客进行记录&#xff0c;方便以后查阅。这篇文章简单介绍使用 javax.mail 发送邮件的步骤&#xff0c;并提供封装好的邮件发送方法&#xff0c;希望对自己和别人有用…

javax.validation校验整理

文章目录 前言一、非空校验二、长度校验三、数值校验四、正则校验五、自定义校验注解六、校验组 前言 javax.validation校验总是混淆&#xff0c;特此整理。如有错误&#xff0c;请不吝指正。 一、非空校验 序号注解解释适用场景1NotNull不能为null&#xff0c;但可以为empt…

Java API学习(二)javax包

在线中文api&#xff1a;http://tool.oschina.net/apidocs/apidoc?apijdk-zh Java版本&#xff1a;1.8 Android版本&#xff1a;23 java的api有java和javax2个包 java和javax都是Java的API包&#xff0c;java是核心包&#xff0c;javax的x是extension的意思&#xff0c;也就…

知识图谱和图神经网络

知识图谱 理论知识知识图谱嵌入模型TransEDistMult 知识图谱的抽取与构建知识图谱工程知识抽取——实体识别与分类知识抽取——实体关系抽取与属性补全 知识图谱的推理常见知识图谱推理方法分类 图表示学习随机游走同构图算法异构图算法 图神经网络系列监督学习或半监督学习模型…