HarleyWang93 / learning

学习

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

跨域

HarleyWang93 opened this issue · comments

同源策略(Same origin Policy)

说起跨域,我们就要先了解一下同源策略。同源策略是针对浏览器的,不是针对 JS。

同源策略限制从一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的关键的安全机制。

换句话说,浏览器出于安全方面的考虑,只允许与本域下的接口交互。不同源的客户端脚本在没有明确授权的情况下,不能读写对方的资源。

本域

同协议:如都是http或者https
同域名:如都是http://github.com/ahttp://github.com/b
同端口:如都是80端口

http://harleywang93.com/为例
该 url 的协议为 http,域名是 harleywang93.com,端口为443,默认端口可以省略。

http://harleywang93.com/http://harleywang93.com/about (同源,协议、域名、端口都相同)
http://harleywang93.com/https://harleywang93.com (不同源,协议不同)
http://harleywang93.com/http://other.harleywang93.com (不同源,域名不同)
http://harleywang93.com/http://harleywang93.com:442 (不同源,端口不同)

需要注意的是: 对于当前页面来说页面存放的 JS 文件的域不重要,重要的是加载该 JS 页面所在什么域

那么,我就是想访问其他域(非同源)的资源 — 即跨域,该怎么做呢?

常见的跨域方法

  • JSONP
  • CORS
  • 降域
  • window.postMessage

JSONP(JSON Padding)

HTML 中 Script 通过 src 属性可以引入其他域下的 JS,比如引入线上的 jQuery库。也可以引入非 JS 的文件,利用这个特性,可实现跨域访问接口。该方法需要后端支持。

  1. 定义数据处理函数 _fun
  2. 创建 Script 标签,src 的地址执行后端接口,最后加个参数 callback=_fun
  3. 服务端在收到请求后,解析参数,计算返还数据,输出 fun(data) 字符串。
  4. fun(data)会放到 Script 标签做为 JS 执行。此时会调用 fun 函数,将 data 做为参数。

实现方式

//b.html
<ul>
  <li>十九大10月18日在北京召开 喜迎十九大</li>
  <li>***欢迎塔吉克斯坦总统 世界之问的**答案</li>
  <li>丁来杭中将履新空军司令员 接替马晓天上将</li>
</ul>
<button>换一组</button>
<script>
  var $ = function (str) {
    return document.querySelector(str);
  };
  $('button').addEventListener('click', function () {
    var script = document.createElement('script');
    script.src = 'http://a.jrg.com:8080/getNews?callback=fn';
    document.head.appendChild(script);
    document.head.removeChild(script);
  });

  var fn = function (news) {
    for(var i=0; i<3; i++)
      $('ul>li').remove();
    for(var i=0; i<news.length; i++){
      var li = document.createElement('li');
      li.innerText = news[i];
      $('ul').appendChild(li);
    }
  };
</script>
//router.js
app.get('/getNews', function (req, res) {
    var news = [
        '**公民在美购敏感材料获刑3年 疑遭钓鱼执法',
        '十九大10月18日在北京召开 喜迎十九大',
        '***欢迎塔吉克斯坦总统 世界之问的**答案',
        '丁来杭中将履新空军司令员 接替马晓天上将',
        '上海警察"一招擒拿"抱娃妇女摔落孩子 已被停职',
        '技校学生拳击玻璃致身亡 家属抬棺材堵校门',
        '女博士被人推下几十级台阶 男子自首称"想听尖叫"'
    ];

    var data = [];
    for(var i=0; i<3; i++){
        var index = parseInt(Math.random()*news.length);
        data.push(news[index]);
        news.splice(index, 1);
    }

    var cb = req.query.callback;
    if(cb)
        res.send(cb + '(' + JSON.stringify(data) + ')');
    else
        res.send(data);
});

CORS(Cross-Origin Resource Sharing)

CORS 全称是跨域资源共享(Cross-Origin Resource Sharing),是一种 ajax 跨域请求资源的方式,支持现代浏览器,IE支持10以上。 实现方式很简单,当你使用 XMLHttpRequest 发送请求时,浏览器发现该请求不符合同源策略,会给该请求加一个请求头:Origin,后台进行一系列处理,如果确定接受请求则在返回结果中加入一个响应头:Access-Control-Allow-Origin; 浏览器判断该相应头中是否包含 Origin 的值,如果有则浏览器会处理响应,我们就可以拿到响应数据,如果不包含浏览器直接驳回,这时我们无法拿到响应数据。所以 CORS 的表象是让你觉得它与同源的 ajax 请求没啥区别,代码完全一样。

CORS 有简单请求和复杂请求之分

  • 简单请求
    比如发送了一个 origin 的头部
    Origin : http://www.nczonline.net
    如果服务器认为这个请求可以接受,就在Access-Control-Allow-Origin头部回发相同的源信息
    Access-Control-Allow-Origin : http://www.nczonline.net

  • 复杂请求
    复杂请求是那种对服务器有特殊要求的请求,比如请求方法是 PUT 或 DELETE ,或者Content-Type 字段的类型是 application/json 。
    复杂请求的 CORS 请求,会在正式通信之前,增加一次 HTTP 查询请求,称为“预检”请求(preflight)。
    浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些 HTTP 动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的 XMLHttpRequest 请求,否则就报错。

实现方式

//a.html
<ul>
  <li>十九大10月18日在北京召开 喜迎十九大</li>
  <li>***欢迎塔吉克斯坦总统 世界之问的**答案</li>
  <li>丁来杭中将履新空军司令员 接替马晓天上将</li>
</ul>
<button>换一组</button>
<script>
    var $ = function (str) {
        return document.querySelector(str);
    };
    $('button').addEventListener('click', function () {
        var xhr = new XMLHttpRequest();
        xhr.onreadystatechange = function () {
            if(xhr.readyState === 4 && xhr.status === 200)
                fn(JSON.parse(xhr.responseText));
        };
        xhr.open('get', 'http://b.jrg.com:8080/getNews', true);
        xhr.send();
    });
    var fn = function (news) {
        for(var i=0; i<3; i++)
            $('ul>li').remove();
        for(var i=0; i<news.length; i++){
            var li = document.createElement('li');
            li.innerText = news[i];
            $('ul').appendChild(li);
        }
    };
</script>
//router.js
app.get('/getNews', function (req, res) {
    var news = [
        '**公民在美购敏感材料获刑3年 疑遭钓鱼执法',
        '十九大10月18日在北京召开 喜迎十九大',
        '***欢迎塔吉克斯坦总统 世界之问的**答案',
        '丁来杭中将履新空军司令员 接替马晓天上将',
        '上海警察"一招擒拿"抱娃妇女摔落孩子 已被停职',
        '技校学生拳击玻璃致身亡 家属抬棺材堵校门',
        '女博士被人推下几十级台阶 男子自首称"想听尖叫"'
    ];

    var data = [];
    for(var i=0; i<3; i++){
        var index = parseInt(Math.random()*news.length);
        data.push(news[index]);
        news.splice(index, 1);
    }

    res.header('Access-Control-Allow-Origin', 'http://a.jrg.com:8080');
    res.send(data);
});

降域

当两个页面不同域,但是它们的父域之上都相同(端口),那么可以使用降域的方法来实现跨域。
对于主域相同而子域不同的情况下,可以通过设置 document.domain 的办法来解决,
具体做法是可以在 url 为 a.github.com:8080 下的 a.htmlb.github.com:8080 下的 b.html 两个文件分别加上 document.domain = “github.com”;然后通过 a.html 文件创建一个 iframe,去控制 iframewindow,从而进行交互,当然这种方法只能解决主域相同、而二级域名不同的情况。

实现方式

//a.html
<input type="text">
<iframe src="http://b.jrg.com:8080/b.html" frameborder="0"></iframe>
<script>
    var $ = function (str) {
        return document.querySelector(str);
    };
    $('input').addEventListener('input', function () {
        window.frames[0].$('input').value = this.value;
    });

    document.domain = 'jrg.com';
</script>
//b.html
<input type="text">
<script>
    var $ = function (str) {
        return document.querySelector(str);
    };
    $('input').addEventListener('input',function () {
        window.parent.$('input').value = this.value;
    });
    document.domain = 'jrg.com';
</script>

window.postMessage

window.postMessage 是一个安全的跨源通信的方法。一般情况下,当且仅当执行脚本的页面使用相同的协议(通常都是 http )、相同的端口( http 默认使用80端口)和相同的 host(两个页面的 document.domain 的值相同)时,才允许不同页面上的脚本互相访问。 window.postMessage 提供了一个可控的机制来安全地绕过这一限制,当其在正确使用的情况下。

window.postMessage 解决的不是浏览器与服务器之间的交互,解决的是浏览器不同的窗口之间的通信问题,可以做的就是同步两个网页,当然这两个网页应该是属于同一个基础域名。

实现方式

//a.html
<input type="text">
<iframe src="http://b.jrg.com:8080/b.html" frameborder="0"></iframe>
<script>
    var $ = function (str) {
        return document.querySelector(str);
    };
    $('input').addEventListener('input', function () {
        window.frames[0].postMessage(this.value, 'http://b.jrg.com:8080');
    });
</script>
//b.html
<input type="text">
<script>
    var $ = function (str) {
        return document.querySelector(str);
    };
    window.addEventListener('message',function (e) {
        $('input').value = e.data;
    });
</script>

推荐阅读

关于跨域,你想知道的全在这里
阮一峰 - CORS通信
跨域解决方案大全
Web开发中跨域的几种解决方案