• JavaScript中的跨域


    跨域是什么

      跨域就是指从一个域名的网页去请求另一个域名的资源,因为JavaScript同源策略的限制,资源无法获取。比如从www.baidu.com 页面去请求 www.google.com 的资源,这是就要用到跨域请求了。严格一点来说就是:只要 协议,域名,端口有任何一个的不同,就被当作是跨域。详情如下:

    特别注意两点:

      第一,如果是协议和端口造成的跨域问题“前台”是无能为力的,

      第二:在跨域问题上,域仅仅是通过URL的首部”来识别而不会去尝试判断相同的ip地址对应着两个域或两个域是否在同一个ip上。

    浏览器为什么要限制跨域访问

      原因当然就是安全问题:如果没有限制跨域访问,那么一个页面就可以随意地访问另外一个网站的资源,那么就有可能在客户完全不知情的情况下出现安全问题。比如下面的操作就有安全问题:

      ·用户访问http://www.icbc.com.cn/icbc/ ,登陆并进行网银操作,这时一些个人信息就可以通过cookie等方式生成并存放在浏览器中

      ·假如此时用户去访问其他恶意网站

      ·这时该恶意网站就可以在它的页面中,拿到银行的cookie,比如用户名,登陆token等,然后发起对http://www.icbc.com.cn/icbc/ 的操作。

       · 如果这时浏览器不予限制,并且银行也没有做响应的安全处理的话,那么用户的信息有可能就这么泄露了。

    那为什么还要使用跨域呢

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

    跨域的几种方式

    1、JSONP

      虽然浏览器默认禁止了跨域访问,但是跨域访问中有图片、css、javascript脚本文件等是不限制,因此你可以在页面渲染时动态在<script>标签设置src路径,而这个路径返回回来的就是json对象。根据这一点,可以方便地通过创建script节点的方法来实现完全跨域的通信,可以自由执行引入的JS文件中的function(包括操作cookie、Dom等等)。具体的做法可以参考YUIGet Utility

    jsonp是一种跨域通信的手段,它的原理其实很简单:

      1首先是利用script标签的src属性来实现跨域。

      2通过将前端方法作为参数传递到服务器端,然后由服务器端注入参数(我们需要的json数据)之后再返回,实现服务器端向客户端通信。

      3由于使用script标签的src属性,因此只支持get方法

    优点是兼容性好,简单易用,支持浏览器与服务器双向通信。缺点是只支持GET请求。

    实现流程

      1设定一个script标签

    <script src="http://jsonp.js?callback=xxx"></script>

      2callback定义了一个函数名,而远程服务端通过调用指定的函数并传入参数来实现传递参数,将fn(response)传递回客户端

    $callback = !empty($_GET['callback']) ? $_GET['callback'] : 'callback';
    
    echo $callback.'(.json_encode($data).)';

      3、客户端接收到返回的js脚本,开始解析和执行fn(response)

    jsonp简单实现

      一个简单的jsonp实现,其实就是拼接url,然后将动态添加一个script元素到头部。

    function jsonp(req){
        var script = document.createElement('script');
        var url = req.url + '?callback=' + req.callback.name;
        script.src = url;
        document.getElementsByTagName('head')[0].appendChild(script);
    }

      前端js示例

    function hello(res){
        alert('hello ' + res.data);
    }
    
    jsonp({
        url : '',
        callback : hello
    });

      服务器端代码

    var http = require('http');
    var urllib = require('url');
    var port = 8080;
    var data = {'data':'world'};
    http.createServer(function(req,res){
        var params = urllib.parse(req.url,true);
        if(params.query.callback){
            console.log(params.query.callback);
            //jsonp
            var str = params.query.callback + '(' + JSON.stringify(data) + ')';
            res.end(str);
        } else {
            res.end();
        }
    }).listen(port,function(){
        console.log('jsonp server is on');
    });

      然而,这个实现虽然简单,但有一些不足的地方:

        1我们传递的回调必须是一个全局方法,我们都知道要尽量减少全局的方法。

        2需要加入一些参数校验,确保接口可以正常执行。

    可靠的jsonp

    (function (global) {
        var id = 0,
            container = document.getElementsByTagName("head")[0];
    
        function jsonp(options) {
            if(!options || !options.url) return;
    
            var scriptNode = document.createElement("script"),
                data = options.data || {},
                url = options.url,
                callback = options.callback,
                fnName = "jsonp" + id++;
    
            // 添加回调函数
            data["callback"] = fnName;
    
            // 拼接url
            var params = [];
            for (var key in data) {
                params.push(encodeURIComponent(key) + "=" + encodeURIComponent(data[key]));
            }
            url = url.indexOf("?") > 0 ? (url + "&") : (url + "?");
            url += params.join("&");
            scriptNode.src = url;
    
            // 传递的是一个匿名的回调函数,要执行的话,暴露为一个全局方法
            global[fnName] = function (ret) {
                callback && callback(ret);
                container.removeChild(scriptNode);
                delete global[fnName];
            }
    
            // 出错处理
            scriptNode.onerror = function () {
                callback && callback({error:"error"});
                container.removeChild(scriptNode);
                global[fnName] && delete global[fnName];
            }
    
            scriptNode.type = "text/javascript";
            container.appendChild(scriptNode)
        }
    
        global.jsonp = jsonp;
    
    })(this);
    使用示例
    
    jsonp({
        url : "www.example.com",
        data : {id : 1},
        callback : function (ret) {
            console.log(ret);
        }
    });

    2、CORS

      CORS是一个W3C标准,全称是"跨域资源共享"Cross-origin resource sharing)。它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。

      CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。

      整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。

      因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。

      浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。

      只要同时满足以下两大条件,就属于简单请求。
        (1) 请求方法是以下三种方法之一:
          HEAD
          GET
          POST
        (2)HTTP的头信息不超出以下几种字段:
          Accept
          Accept-Language
          Content-Language
          Last-Event-ID
          Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
      凡是不同时满足上面两个条件,就属于非简单请求。
      浏览器对这两种请求的处理,是不一样的。

    简单请求的实现

      对于简单请求,浏览器直接发出CORS请求。具体来说,就是在头信息之中,增加一个Origin字段。

      下面是一个例子,浏览器发现这次跨源AJAX请求是简单请求,就自动在头信息之中,添加一个Origin字段。

    GET /cors HTTP/1.1
    Origin: http://api.bob.com
    Host: api.alice.com
    Accept-Language: en-US
    Connection: keep-alive
    User-Agent: Mozilla/5.0...

      上面的头信息中,Origin字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求。

      如果Origin指定的源,不在许可范围内,服务器会返回一个正常的HTTP回应。浏览器发现,这个回应的头信息没有包含Access-Control-Allow-Origin字段(详见下文),就知道出错了,从而抛出一个错误,被XMLHttpRequest的onerror回调函数捕获。注意,这种错误无法通过状态码识别,因为HTTP回应的状态码有可能是200。

      如果Origin指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段。

    Access-Control-Allow-Origin: http://api.bob.com
    Access-Control-Allow-Credentials: true
    Access-Control-Expose-Headers: FooBar
    Content-Type: text/html; charset=utf-8

      上面的头信息之中,有三个与CORS请求相关的字段,都以Access-Control-开头。

      (1)Access-Control-Allow-Origin

        该字段是必须的。它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求。

      (2)Access-Control-Allow-Credentials

        该字段可选。它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。这个值也只能设为true,如果服务器不要浏览器发送Cookie,删除该字段即可。

      (3)Access-Control-Expose-Headers

      该字段可选。CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-ControlContent-LanguageContent-TypeExpiresLast-ModifiedPragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。上面的例子指定,getResponseHeader('FooBar')可以返回FooBar字段的值。

    withCredentials 属性

      上面说到,CORS请求默认不发送Cookie和HTTP认证信息。如果要把Cookie发到服务器,一方面要服务器同意,指定Access-Control-Allow-Credentials字段。

    Access-Control-Allow-Credentials: true

      另一方面,开发者必须在AJAX请求中打开withCredentials属性。

    var xhr = new XMLHttpRequest();
    xhr.withCredentials = true;

      否则,即使服务器同意发送Cookie,浏览器也不会发送。或者,服务器要求设置Cookie,浏览器也不会处理。

      但是,如果省略withCredentials设置,有的浏览器还是会一起发送Cookie。这时,可以显式关闭withCredentials

    xhr.withCredentials = false;

      需要注意的是,如果要发送Cookie,Access-Control-Allow-Origin就不能设为星号,必须指定明确的、与请求网页一致的域名。同时,Cookie依然遵循同源政策,只有用服务器域名设置的Cookie才会上传,其他域名的Cookie并不会上传,且(跨源)原网页代码中的document.cookie也无法读取服务器域名下的Cookie。

    JSONP比较

      CORSJSONP的使用目的相同,但是比JSONP更强大。

      JSONP只支持GET请求,CORS支持所有类型的HTTP请求。JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。

    3、document.domain+iframe

      对于主域相同而子域不同的例子,可以通过设置document.domain的办法来解决。

      前提条件:这两个域名必须属于同一个基础域名!而且所用的协议,端口都要一致,否则无法利用document.domain进行跨域

      具体的做法是可以在http://www.a.com/a.htmlhttp://script.a.com/b.html两个文件中分别加上document.domain = a.com’;然后通过a.html文件中创建一个iframe,去控制iframecontentDocument,这样两个js文件之间就可以“交互”了。当然这种办法只能解决主域相同而二级域名不同的情况,如果你异想天开的把script.a.comdomian设为alibaba.com那显然是会报错地!代码如下:

    www.a.com上的a.html

    document.domain = 'a.com';
    var ifr = document.createElement('iframe');
    ifr.src = 'http://script.a.com/b.html';
    ifr.style.display = 'none';
    document.body.appendChild(ifr);
    ifr.onload = function(){
        var doc = ifr.contentDocument || ifr.contentWindow.document;
        // 在这里操纵b.html
        alert(doc.getElementsByTagName("h1")[0].childNodes[0].nodeValue);
    };

    script.a.com上的b.html

    document.domain = 'a.com';

    问题:

      1、安全性,当一个站点(b.a.com)被攻击后,另一个站点(c.a.com)会引起安全漏洞。

      2、如果一个页面中引入多个iframe,要想能够操作所有iframe,必须都得设置相同domain

      还有一点要注意,这个方法需要在iframe加载后才能使用!

    4、还有几种方式,暂且不说,未完待续。。。。。。

    使用window.name来进行跨域

      window对象有个name属性,该属性有个特征:即在一个窗口(window)的生命周期内,窗口载入的所有的页面都是共享一个window.name的,每个页面对window.name都有读写的权限,window.name是持久存在一个窗口载入过的所有页面中的

    使用HTML5中新引进的window.postMessage方法来跨域传送数据

      还有flash、在服务器上设置代理页面等跨域方式。个人认为window.name的方法既不复杂,也能兼容到几乎所有浏览器,这真是极好的一种跨域方法。

    参考:

    浅谈JSONP

    说说JSON和JSONP,也许你会豁然开朗,含jQuery用例

    jsonp的原理与实现

    你所不知道的跨域资源共享(CORS)

    跨域资源共享 CORS 详解

  • 相关阅读:
    EF Load之详讲
    WPF系列 自定控件
    EF6 的性能优化
    WPF系列 Path表示语法详解(Path之Data属性语法)
    WPFTookit Chart 高级进阶
    WPFTookit Chart 入门
    WPF系列-CheckBox
    WPF系列 Style
    ASP.NET MVC 5 with EF 6 上传文件
    WPF Prism
  • 原文地址:https://www.cnblogs.com/Chen-XiaoJun/p/6195648.html
Copyright © 2020-2023  润新知