跨域问题
相关资料:
一、相关原理
URL的组成
URL的组成部分:
- 协议(protocol) :互联网支持多种协议,必须指明网址使用哪一种协议,默认是HTTP协议,HTTPS是HTTP的加密版本,处于安全考虑,越来越多的网站使用这个协议。
- 主机:主机是资源所在网站名或服务器的名字,又叫域名。
- 端口:同一域名下面可能包含多个网站,它们之间通过port分开。
- 路径:路径是资源网站的位置。如上图所示:
- 查询参数:是提供给服务器的额外参数。参数的位置在路径后面,两者之间使用
?
分隔。 - 锚点:网页内部的定位点,浏览器加载页面以后,会自动滚动到锚点所在的位置。锚点是通过网页元素的id属性命名的。
跨域
同源策略:是浏览器对 JavaScript 实施的安全限制,只要协议、域名、端口
有任何一个不同,都被当作是不同的域。
跨域:是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略
造成的。跨域只会出现在浏览器上,小程序和APP开发不会有跨域问题
跨域原理,即是通过各种方式,避开浏览器的安全限制
。
跨域场景:
- 项目上线后,访问地址与接口请求地址不一致,存在跨域
- 项目开发阶段,前后端联调接口,存在跨域
- iframe跨文档数据通信
解决跨域方案
1. CORS,跨域资源共享
CORS(Cross-Origin Resource Sharing)是基于http1.1
的一种跨域解决方案,它使用额外的 HTTP 头来告诉浏览器 让运行在一个 origin (domain) 上的 Web 应用被准许访问来自不同源服务器上的指定的资源。当一个资源从与该资源本身所在的服务器不同的域名、协议或端口请求一个资源时,资源会发起一个跨域 HTTP 请求。而在 cors 中会有 简单请求
和 复杂请求
的概念。
浏览器支持情况:CORS 需要浏览器和后端同时支持
,当使用 IE<=9, Opera<12, or Firefox<3.5 或者更加老的浏览器,这个时候请使用JSONP。
简单请求
不会触发 CORS 预检请求。这样的请求为“简单请求”,请注意,该术语并不属于 Fetch (其中定义了 CORS)规范。若请求同时满足所有下述条件,则该请求可视为“简单请求”:
-
条件一:请求方式是GET、HEAD、POST 方法之中的一种
-
条件二:请求头仅包含安全的字段,常见的安全字段如下:
Accept
、Accept-Language
Content-Language
、Content-Type
DPR
、Downlink
Save-Data
、Viewport-Width
、Width
-
条件三:请求头如果包含
Content-Type
,仅限下面的值之一:text/plain
multipart/form-data
application/x-www-form-urlencoded
非简单请求
除以上情况外的。
2. Node 正向代理
实现原理:同源策略是浏览器需要遵循的标准,而如果是服务器向服务器请求就无需遵循同源策略。 代理服务器,需要做以下几个步骤:
- 接受客户端请求。
- 将请求 转发给服务器。
- 拿到服务器 响应 数据。
- 将 响应 转发给客户端。
3. Nginx 反向代理
实现原理类似于Node中间件代理,需要你搭建一个中转nginx服务器,用于转发请求。
使用nginx反向代理实现跨域,是最简单的跨域方式。只需要修改nginx的配置即可解决跨域问题,支持所有浏览器,支持session,不需要修改任何代码,并且不会影响服务器性能。
实现思路:通过nginx配置一个代理服务器(域名与domain1相同,端口不同)做跳板机,反向代理访问domain2接口,并且可以顺便修改cookie中domain信息,方便当前域cookie写入,实现跨域登录。
4. JSONP
JSONP原理:利用 <script>
标签没有跨域限制的漏洞,网页可以得到从其他来源动态产生的 JSON 数据。JSONP请求一定需要对方的服务器做支持才可以。
使用限制:仅支持 GET 方法,如果想使用完整的 REST 接口,请使用 CORS 或者其他代理方式。
和AJAX对比:JSONP和AJAX相同,都是客户端向服务器端发送请求,从服务器端获取数据的方式。但AJAX属于同源策略,JSONP属于非同源策略(跨域请求)
JSONP优点:简单兼容性好,可用于解决主流浏览器的跨域数据访问的问题。
JSONP缺点:
- 仅支持get方法具有局限性,不安全可能会遭受XSS攻击
- JSONP 需要后端配合返回指定格式的数据
JSONP的做法是:当需要跨域请求时,不使用AJAX,转而生成一个script元素去请求服务器,由于浏览器并不阻止script元素的请求,这样请求可以到达服务器。服务器拿到请求后,响应一段JS代码,这段代码实际上是一个函数调用,调用的是客户端预先生成好的函数,并把浏览器需要的数据作为参数传递到函数中,从而间接的把数据传递给客户端
JSONP:ajax 请求受同源策略影响,不允许进行跨域请求,而 script 标签 src 属性中的链接却可以访问跨域的 js 脚本,利用这个特性,服务端不再返回 JSON 格式的数据,而是 返回一段调用某个函数的 js 代码,在 src 中进行了调用,这样实现了跨域。 步骤:
- 去创建一个script标签
- script的src属性设置接口地址
- 接口参数,必须要带一个自定义函数名,要不然后台无法返回数据
- 通过定义函数名去接受返回的数据
//动态创建 script
var script = document.createElement('script');
// 设置回调函数
function getData(data) {
console.log(data);
}
//设置 script 的 src 属性,并设置请求地址
script.src = 'http://localhost:3000/?callback=getData';
// 让 script 生效
document.body.appendChild(script);
5. Websocket
Websocket是HTML5的一个持久化的协议,它实现了浏览器与服务器的全双工通信,同时也是跨域的一种解决方案。WebSocket和HTTP都是应用层协议,都基于 TCP 协议。但是 WebSocket 是一种双向通信协议,在建立连接之后,WebSocket 的 server 与 client 都能主动向对方发送或接收数据。同时,WebSocket 在建立连接时需要借助 HTTP 协议,连接建立好了之后 client 与 server 之间的双向通信就与 HTTP 无关了。
原生WebSocket API使用起来不太方便,我们使用Socket.io
,它很好地封装了webSocket接口,提供了更简单、灵活的接口,也对不支持webSocket的浏览器提供了向下兼容。
6. window.postMessage
window.postMessage()方法可以安全地实现跨源通信。通常,对于两个不同页面的脚本,只有当执行它们的页面位于具有相同的协议(通常为 https),端口号(443 为 https 的默认值),以及主机 (两个页面的模数 Document.domain
设置为相同的值) 时,这两个脚本才能相互通信。
window.postMessage()方法提供了一种受控机制来规避此限制,只要正确的使用,这种方法就很安全。
postMessage是HTML5 XMLHttpRequest Level 2中的API,且是为数不多可以跨域操作的window属性之一,它可用于解决以下方面的问题:
- 页面和其打开的新窗口的数据传递
- 多窗口之间消息传递
- 页面与嵌套的 iframe 消息传递
- 上面三个场景的跨域数据传递
postMessage()方法允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本档、多窗口、跨域消息传递。
otherWindow.postMessage(message, targetOrigin, [transfer]);
- message: 将要发送到其他 window的数据。
- targetOrigin:通过窗口的origin属性来指定哪些窗口能接收到消息事件,其值可以是字符串"*"(表示无限制)或者一个URI。在发送消息的时候,如果目标窗口的协议、主机地址或端口这三者的任意一项不匹配targetOrigin提供的值,那么消息就不会被发送;只有三者完全匹配,消息才会被发送。
- transfer(可选):是一串和message 同时传递的 Transferable 对象. 这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权。
7. document.domain + Iframe
该方式只能用于二级域名相同的情况下,比如 a.test.com
和 b.test.com
适用于该方式。 只需要给页面添加 document.domain ='test.com'
表示二级域名都相同就可以实现跨域。
实现原理:两个页面都通过js强制设置document.domain为基础主域,就实现了同域。
例子如下:页面a.fp.cn:3000/a.html
获取页面b.fp.cn:3000/b.html
中a的值
// a.html
<body>
html-a
<iframe src="http://b.fp.cn:3000/b.html" frameborder="0" onload="load()" id="frame"></iframe>
<script>
document.domain = 'zf1.cn'
function load() {
console.log(frame.contentWindow.a);
}
</script>
</body>
// b.html
<body>
html-b
<script>
document.domain = 'fp.cn'
var a = 100;
</script>
</body>
8. window.location.hash + Iframe
原理:通过 url 带 hash ,通过一个非跨域的中间页面来传递数据。
9. window.name + Iframe
window 对象的 name 属性是一个很特别的属性,当该 window 的 location 变化,然后重新加载,它的 name 属性可以依然保持不变。 通过 iframe 的 src 属性由外域转向本地域,跨域数据即由 iframe 的 window.name 从外域传递到本地域。这个就巧妙地绕过了浏览器的跨域访问限制,但同时它又是安全操作。
10. 关闭浏览器策略
uni-app 项目 解决跨域办法
方法一:使用 HBuilderX 内置浏览器,内置浏览器是删除了浏览器的跨域限制的。
方法二:在项目根目录 mainfest.json 文件中添加 h5 配置。
"h5" : {
"devServer":{
"proxy":{
"^/api":{
"target":"http://127.0.0.1:8081",
"ws": true,
"changeOrigin": true
}
}
}
}
页面请求代码示例
uni.request({
url:'/api/1.json', // url 要与 proxy 匹配,不能写成 xx.com/api.1.json
method:'GET',
success: (res) => {
console.log(res);
}
})
Vue.js 项目 解决跨域办法
在项目根目录 vue.config.js 文件中添加如下配置
/* 开发环境配置 */
devServer:{
/* 代理目录 */
proxy:{
'^/api':{
target:'http://127.0.0.1:8081',
ws: true,
changeOrigin: true
}
}
}
页面请求代码,以 axios 库为例
request.get('/api/1.json',{
params
}).then(res=>{
console.log(res)
})