Heading for the future

跨域方式及其实现

产生原因

为什么会产生跨域呢,因为浏览器为了安全采用了一系列的安全机制,其中有一个是同源策略。何为同源策略(same-origin policy)。简单来讲同源策略就是浏览器为了保证用户信息的安全,防止恶意的网站窃取数据,禁止不同域之间的JS进行交互。对于浏览器而言只要域名、协议、端口其中一个不同就会引发同源策略。

关于浏览器安全和同源策略详解 https://www.yuque.com/suihangadam/liulanqi/pog4pf

为何要跨域

公司内部可能有多个不同的子域,比如一个是location.company.com ,而应用是放在app.company.com , 这时想从 app.company.com去访问 location.company.com 的资源就属于跨域。

跨域方式

JSONP

jsonp是一种非正式的传输协议

跨域原理: 利用了src不受同源策略的影响 ,可以访问其他页面的数据。

注意⚠️:jsonp并不能解决所有的跨域问题,因为使用jsonp跨域需要被提供jsonp接口

// 1.创建一个全局函数
function callBack (data) {
    console.log(data);
}
// 2.动态创建一个script标签
var currentScript = document.createElement("script");
// 3.给标签的src赋值(即接口的url),并将函数附加到url上,注意:大部分jonsp接口都为callback,百度的jsonp接口为cb
currentScript.src = "http:www.baidu.com?a=1&b=2&cb=callBack";
// 4.将标签插入到页面上
document.body.appendChild(script1);
// 5.将标签加载完后删除 
script1.onload = function(){
    this.remove()
}

修改document.domain来跨子域

跨域原理:两个网页一级域名相同,只是二级域名不同,浏览器允许通过设document.domain共享 Cookie或者处理iframe。

注意⚠️:用来处理Cookie 和 iframe,

处理Cookie

document.domain = 'example.com';
//现在,A网页通过脚本设置一个 Cookie。
document.cookie = "test1=hello";
//B网页就可以读到这个 Cookie。
var allCookie = document.cookie;

注意⚠️:这种方法只适用于 Cookie 和 iframe 窗口,LocalStorage 和 IndexDB 无法通过这种方法,规避同源政策,而要使用下文介绍的PostMessage API。 

另外,服务器也可以在设置Cookie的时候,指定Cookie的所属域名为一级域名,比如.example.com。

Set-Cookie: key=value; domain=.example.com; path=/
//这样的话,二级域名和三级域名不用做任何设置,都可以读取这个Cookie。

处理iframe

不同的iframe 之间(父子或同辈),是能够获取到彼此的window对象的,但是你却不能使用获取到的window对象的属性和方法(html5中的postMessage方法是一个例外,还有些浏览器比如ie6也可以使用top、parent等少数几个属性),总之,你可以当做是只能获取到一个几乎无用的window对象。 

首先说明一下同域之间的iframe是可以操作的。比如http://127.0.0.1/JSONP/a.html里面嵌入一个iframe指向http://127.0.0.1/myPHP/b.html。那么在a.html里面是可以操作iframe里面的DOM的。

<iframe src="http://127.0.0.1/myPHP/b.html" frameborder="1"></iframe>
<body>
<script type="text/javascript">
var iframe = document.querySelector("iframe");
iframe.onload = function(){
    var win = iframe.contentWindow;
    var doc = win.document;
    var ele = doc.querySelector(".text1");
    var text = ele.innerHTML="123456";
}
</script>

注意⚠️:如果两个网页不同源,就无法拿到对方的DOM。典型的例子是iframe窗口和window.open方法打开的窗口,它们与父窗口无法通信。如果两个窗口一级域名相同,只是二级域名不同,那么document.domain属性,就可以规避同源政策,拿到DOM。 

window.name + iframe

跨域原理:即在一个窗口(window)的生命周期内,窗口载入的所有的页面都是共享一个window.name的,每个页面window.name都有读写的权限。

注意⚠️:是在一个窗口!并且window.name的值只能是字符串的形式,这个字符串的大小最大能允许2M左右甚至更大的一个容量,具体取决于不同的浏览器。

简单栗子🌰

1.创建一个a.html

<script>
    window.name = "哈哈,我是页面a设置的值哟!";
    //设置window.name的值
    setTimeout(function(){
        window.location = 'b.html';
    },3000);//3秒后把一个新页面载入当前window
</script>

2.在b.html读取

<script>
    alert(window.name);//读取window.name的值
</script>
3.a.html载入3秒后,跳转到b.html页面中,结果为

image.png

跨域栗子🌰

比如:有一个example.com/a.html页面,需要通过a.html页面里的js来获取另一个位于不同域上的页面cnblogs.com/data.html里的数据。

1.创建cnblogs.com/data.html代码

<script>
    window.name = "我是data.html的数据,所有可以转化为字符串来传递的数据都可以在这里使用,比如这里可以传递Json数据";
</script>

2.创建example.com/a.html的代码

想要即使a.html页面不跳转也能得到data.html里的数据。在a.html页面中使用一个隐藏的iframe来充当一个中间人角色,由iframe去获取data.html的数据,然后a.html再去得到iframe获取到的数据。

<iframe id = "iframe" src = "cnblogs.com/data.html" style = "display:none" onload = "getData()"></iframe>
<script>
    function getData(){
    //iframe载入data.html页面会执行此函数
        var ifr = document.getElementById("iframe");
        ifr.onload = function(){
        //这个时候iframe和a.html已经处于同一源,可以互相访问
            var data = ifr.contentWindow.name;
//获取iframe中的window.name,也就是data.html中给它设置的数据
            alert(data);
        }
        ifr.src = 'b.html';//这里的b.html为随便一个页面,只要与a.html同源就行,目的是让a.html能够访问到iframe中的东西,否则访问不到
    }
</script>

window.postMessage + iframe

跨域原理:可以使用window.postMessage()来向其它的window对象发送消息,无论这个window对象是属于同源或不同源(可实现跨域),其他的页面通过window.onmessage来接收。

注意⚠️:window.postMessage(message,targetOrigin) 方法是html5新引进的特性,目前IE8+、FireFox、Chrome、Opera等浏览器都已经支持window.postMessage方法。但IE6、IE7不支持

message:为要发送的消息,类型只能为字符串;

targetOrigin:用来限定接收消息的那个window对象所在的域,如果不想限定域,可以使用通配符 “*”。

栗子🌰

1.创建www.test.com/a.html页面代码

<iframe id="iframe" src="www.script.com/b.html" onload="onLoad()"></iframe>
<script>
function onLoad(){
    var iframe = document.getElementById("iframe");
    var win = iframe.contentWindow;
    win.postMessage('哈哈,我是来自页面a.html的信息哟!','*');//向不同域的www.script.com/b.html发送消息
}
</script>

2.创建www.script.com/b.html页面代码

<script>
window.onmessage = function(e){ //注册message时间来接收消息
    e = e || event;             //获取时间对象
    alert(e.data);              //通过data属性来得到传送的消息
}
</script>

使用location.hash+iframe

跨域原理:使用location.hash和iframe来绕过同源策略

注意⚠️:需要借用两个iframe

假设域名test.com下的文件a.html要和csdnblogs.com域名下的b.html传递信息

1.创建test.com下的a.html页面, 同时在a.html上加一个定时器,隔一段时间来判断location.hash的值有没有变化,一旦有变化则获取获取hash值

www.test.com下a.html代码

<script>
function startRequest(){
    var ifr = document.createElement('iframe');
    //创建一个隐藏的iframe
    ifr.style.display = 'none';
    ifr.src = 'http://www.csdnblogs.com/b.html#paramdo';
    //传递的location.hash 
    document.body.appendChild(ifr);
}

function checkHash() {
    try {
        var data = location.hash ? location.hash.substring(1):'';
        if (console.log) {
            console.log('Now the data is ' + data);
        }
    } catch (e) {};
}
setInterval(checkHash, 5000);
window.onload = startRequest;
</script>

2.b.html响应请求后再将通过修改test.com域名下的文件c.html来间接a.html的hash值来传递数据,代码如下:

www.csdnblogs.com域名下b.html代码

<script>
function checkHash() {
    var data = '';
    //模拟一个简单的参数处理操作
    switch (location.hash) {
        case '#paramdo':
            data = 'somedata';
            break;
        case '#paramset':
             //do something……
            break;
        default:
            break;
    }
    data && callBack('#' + data);
}

function callBack(hash) {
    // ie、chrome的安全机制无法修改parent.location.hash
    //所以要利用一个中间的www.csdnblogs.com域下的代理iframe
    var proxy = document.createElement('iframe');
    proxy.style.display = 'none';
    proxy.src = 'http://www.csdnblogs.com/c.html' + hash; 
    // 注意该文件在"www.test.com"域下
    document.body.appendChild(proxy);
}
window.onload = checkHash;
</script>

www.test.com下c.html代码

<script>
//因为parent.parent和自身属于同一个域,所以可以改变其location.hash的值
parent.parent.location.hash = self.location.hash.substring(1);
</script>

跨域资源共享(CORS)

跨域原理:一种跨域访问的机制,可以让AJAX实现跨域访问;CORS允许一个域上的网络应用向另一个域提交跨域AJAX请求。

注意⚠️:

  • 只有服务器设置Access-Control-Allow-Origin HTTP响应头之后,浏览器将会允许跨域请求。就是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功,还是应该失败。
  • CORS机制是同源策略出让的安全性之一。其余两个为允许嵌入第三方资源和跨文档消息机制(window.postmessage)。详情:https://www.yuque.com/suihangadam/liulanqi/pog4pf

IE中对CORS的实现是通过xdr

var xdr = new XDomainRequest();
xdr.onload = function(){
    console.log(xdr.responseText);
}
xdr.open('get', 'http://www.test.com');
......
xdr.send(null);

其它浏览器中的实现就在xhr中

var xhr =  new XMLHttpRequest();
xhr.onreadystatechange = function () {
  if(xhr.readyState === 4 && xhr.status === 200){
        console.log(xhr.responseText);
        } 
    }
}
xhr.open('get', 'http://www.test.com');
......
xhr.send(null);

实现跨浏览器的CORS

function createCORS(method, url){
    var xhr = new XMLHttpRequest();
    if('withCredentials' in xhr){
        xhr.open(method, url, true);
    }else if(typeof XDomainRequest != 'undefined'){
        var xhr = new XDomainRequest();
        xhr.open(method, url);
    }else{
        xhr = null;
    }
    return xhr;
}
var request = createCORS('get', 'http://www.test.com');
if(request){
    request.onload = function(){
        ......
    };
    request.send();
}

Web sockets

跨域原理: 是一种浏览器的API,它的目标是在一个单独的持久连接上提供全双工、双向通信。(同源策略对web sockets不适用),在JS创建了web socket之后,会有一个HTTP请求发送到浏览器以发起连接。取得服务器响应后,建立的连接会使用HTTP升级从HTTP协议交换为web sockt协议。

<script>
  var socket = new WebSockt('ws://www.test.com');
  //http->ws; https->wss
  socket.send('hello WebSockt');
  socket.onmessage = function(event){
      var data = event.data;
  }
</script>

使用nginx进行反向代理

跨域原理:Nginx反向代理将对真实服务器的请求转移到本机服务器来避免浏览器的"同源策略限制"。

Nginx是一个高性能的HTTP和反向代理web服务器

image.png

注意⚠️: 反向代理隐藏了真实的服务器

扩展:正向代理隐藏了真实的客户端。

Nginx 反向代理模块 proxy_pass

proxy_pass 后面跟着一个 URL,用来将请求反向代理到 URL 参数指定的服务器上。例如我们上面例子中的 proxy_pass https://api.shanbay.com,则将匹配的请求反向代理到 https://api.shanbay.com

通过在配置文件中增加proxy_pass 你的服务器ip,就可以完成反向代理。

server {
    listen       80;
    server_name  localhost;
    ## 用户访问 localhost,则反向代理到https://api.shanbay.com
    location / {
        root   html;
        index  index.html index.htm;
        proxy_pass https://api.shanbay.com;
    }
}

nginx反向代理详细配置:https://www.cnblogs.com/ddlove/p/9945988.html

使用webpack配置代理

前提条件

1、需要使用本地开发插件:webpack-dev-server

2、webpack-dev-server使用的是http-proxy-middleware来实现跨域代理的。

module.exports = {
  //...
  devServer: {
    proxy: {
      '/api': {
        target: 'http://www.baidu.com/',
        pathRewrite: {'^/api' : ''},
        changeOrigin: true,     // target是域名的话,需要这个参数,
        secure: false,          // 设置支持https协议的代理
      },
      '/api2': {
          .....
      }
    }
  }
};

参考自

百度百科

https://blog.csdn.net/wangchengiii/article/details/78081032

https://www.cnblogs.com/mmy67/p/10032915.html

https://www.cnblogs.com/mmy67/p/10032915.html

 

posted @ 2020-09-06 00:03  一只菜鸟攻城狮啊  阅读(942)  评论(0编辑  收藏  举报