JavaScript中的跨域

Posted chenxj

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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 详解

以上是关于JavaScript中的跨域的主要内容,如果未能解决你的问题,请参考以下文章

JavaScript中的跨域详解(内附源码)

Javascript中开启器上的跨域更新哈希

如何修复 Firefox 中的跨域请求失败

在不受主机页面 CSS 影响的跨域主机页面中创建嵌入式 JavaScript?

web项目中的跨域问题解决方法

使用 JSONP 或 CORS 的跨域 JavaScript 调用