常见跨域解决方案
前言
上期我们讲到CORS,这里就不讲解了,需要明确的是CORS是作为目前跨域http请求的根本解决方案。
本章仅对跨域方案做总结,如有遗漏或不对的地方请指正哈一、同源策略简介
跨域是指由于浏览器同源策略限制的一类请求场景。 所谓同源,就是对于两个地址,协议://域名:端口,三者必须一致,若有一个不一致都会因为非同源而产生跨域。例如:https://www.baidu.com访问http://www.baidu.com也是会产生跨域的。
同源策略总的来说是浏览器出于安全考虑,这主要表现在三个层面:
1. DOM层面。同源策略限制了来自不同源的JS脚本对当前DOM对象的读写操作。假如没有这样做,那么随便一个网站的脚本都可以对别的网址进行DOM操作,这是很危险的。
2. 数据层面。同源策略限制了不同源的站点读取当前站点的cookie、indexDB、LocalStorage等数据,如果没有同源策略,那么恶意获取用户的信息将变得很容易,这是极其不安全的。
3. 网络层面。同源策略限制了通过XMLHttpRequest等方式将站点的数据发送给不同源的站点。假如没有这么做,那作为资源提供商每天将接收处理外部链接请求,这将会加大资源提供商服务器的压力。
但是有三个特殊的标签不受此限制,他们分别是img标签、link标签、script标签。为什么是他们?我们都知道Web世界是开放的,同源策略的产生意味着HTML文件、CSS文件、JS文件、图片等资源将部署到同一个服务器下,这无疑违背了开放的初衷,假如将这些文件部署在不同的CDN上,那么资源将因为同源策略而无法相互访问。所以,img、link、script等标签就是为了在不同源的情况下可以引用外部文件资源。
然而,引用第三方文件又会带了一些问题,如浏览器首页内容被恶意程序劫持等,其中最典型的就是XXS攻击了,为了解决这一问题,浏览器又引入内容安全策略,称为CSP(核心思想是通过服务器告知浏览器哪些资源可以加载、哪些脚本可以执行)。通过这些手段可以大大减少XXS攻击。
介绍完同源策略,来个小结,跨域仅出现在浏览器,服务器与服务器通信是不存在跨域的!所以你或许有个疑问,那跨域请求到底发没发出去?
跨域请求能发出去,并且服务端也能收到请求并正常返回结果,只是结果被浏览器拦截了。
二、跨域解决方案
1.jsonp
jsonp的本质是通过script标签能够请求外部资源这一特性,让src属性链接外部资源实现跨域请求。这个跨域请求需要后端配合,让后端将数据以函数调用的形式返回,并告知浏览器以js文件执行。具体操作如下:
后端操作,以express为例:
const data = [{
a:1},{
b:2},{
c:3}]
(req,res,next) => {
const json = JSON.stringify(data);
const script = `callback(${
json})`
res.header("content-type","application/javascript") //告知浏览器这是js文件,浏览器接收到会当js文件执行
res.send(script) //发送数据
}
假如前端不进行处理发送请求到后端的这个接口,浏览器控制台会报错,报错原因是callback这个函数不存在。所以,前端需要想要接收后端返回的数据,就需要定义一个函数接收这些数据。
前端操作:
function callback(data){
//在这个函数里就可以对data进行操作了
}
所以,jsonp的缺点也是显而易见:
1. 影响服务器的响应格式。
2. 只能使用get请求,因为script标签只会发送get请求。
2.cors
具体参考这里:跨域之CORS
3.node中间件代理
我们都知道服务器与服务器是不存在跨域的,所以我们可以通过一个服务器充当媒介进行跨域处理:
大致模式如下:
4.nginx反向代理
实现思路和Node服务器代理一样,需要搭建一个nginx服务器用于转发请求。通过 nginx 配置一个代理服务器(域名与 domain1 相同,端口不同)做跳板机,反向代理访问 domain2 接口,并且可以顺便修改 cookie 中 domain 信息,方便当前域 cookie 写入,实现跨域登录。
大致模式如下:
nginx配置大致如下:
// proxy服务器
server {
listen 80;
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;
}
}
5.window.name+iframe
window.name 属性的独特之处:name 值在不同的页面(甚至不同域名)加载后依旧存在,并且可以支持非常长的 name 值(2MB)
所以我们a和b两个页面想要跨域通信可以利用iframe+window.name,需要利用c页面作为媒介实现,c页面和a页面同源才能实现父子页面的iframe读取操作。
http://localhost:6060/a.html页面:
<iframe src="http://localhost:5050/b.html" frameborder="0" onload="load()" id="iframe"></iframe>
<script>
let first = true //加锁
function load() {
if(first){
// 第1次onload(跨域页)成功后,切换到同域代理页面
let iframe = document.getElementById('iframe');
iframe.src = 'http://localhost:6060/c.html'; //设置完毕,iframe再次加载,此时加载的是c页面,a和c同源
first = false;
}else{
// 第2次onload后,此时是iframe是c页面,与a页面同源,a可以读取同域下iframe的window.name中数据
console.log(iframe.contentWindow.name);
}
}
</script>
http://localhost:5050/b.html页面:
<script>
window.name = 'hello,I am b'
</script>
该处使用的url网络请求的数据。
6.location.hash+iframe
实现原理: a.html 想和c.html 跨域通信,可以通过在a.html里嵌套iframe链接到 c.html,而c.html里又有一个iframe,链接到b.html,a和b是同域的 。 三个页面,不同域之间利用 iframe 的 location.hash 传值,相同域之间直接 js 访问来通信。
7.document.domain+iframe
实现原理:两个页面都通过 js 强制设置 document.domain 为基础主域,就实现了同域。需要注意的是该方式只能用于二级域名相同的情况。
比如xueshu.baidu.com/a.html里嵌套一个iframe(链接到tieba.baidu.com/b.html),这种情况可以将两个页面设置同一个domain以实现跨域。
8.postMessage
1.postMessage 是 HTML5 XMLHttpRequest Level 2 中的 API,且是为数不多可以跨域操作的 window 属性之一。
2.postMessage()方法允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本档、多窗口、跨域消息传递。
关于用法MDN上有详细的文档说明:window.postMessage
http://localhost:3000/a.html
<iframe src="http://localhost:6060/b.html" frameborder="0" id="frame" onload="load()"></iframe>//iframe加载完毕触发onload事件
<script>
function load() {
let frame = document.getElementById('frame')
frame.contentWindow.postMessage('hello', 'http://localhost:6060') //发送数据
window.onmessage = (e) => {
//接受返回数据
console.log(e.data) //打印出hi
}
}
</script>
http://localhost:6060/b.html
window.onmessage = (e) => {
console.log(e.data) //打印出hello
e.source.postMessage('hi', e.origin) //响应对方数据
}
9.WebSocket
1.Websocket 是 HTML5 的一个持久化的协议,它实现了浏览器与服务器的全双工通信,同时也是跨域的一种解决方案。
3.WebSocket 是一种双向通信协议,在建立连接之后,WebSocket 的 server 与 client 都能主动向对方发送或接收数据。
2.WebSocket 在建立连接时需要借助 HTTP 协议,连接建立好了之后 client 与 server 之间的双向通信就与 HTTP 无关了。
示例如下:
客户端代码:
let socket = new WebSocket('ws://localhost:6060'); //ws是WebSocket定义的协议
socket.onopen = () => socket.send('Hello');//向服务器发送数据
socket.onmessage = (e) =>console.log(e.data);//接收服务器返回的数据
服务器代码:
let express = require('express');
let app = express();
let WebSocket = require('ws');//ws 是一个第三方的 websocket 通信模块
let myWs = new WebSocket.Server({
port:6060});
myWs.on('connection',(ws)=> {
ws.on('message', (data) => {
console.log(data); //打印客户端发送过来的数据
ws.send('我不爱你')
});
})
总结
1. 跨域仅存在浏览器,脱离浏览器是不存在跨域的!
2.CORS 支持所有类型的 HTTP 请求,是跨域 HTTP 请求的根本解决方案!