跨域资源共享(CORS)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了跨域资源共享(CORS)相关的知识,希望对你有一定的参考价值。

  通过 XHR 实现 Ajax 通信的一个主要限制,来源于跨域安全策略。默认情况下,XHR 对象只能访 问与包含它的页面位于同一个域中的资源。这种安全策略可以预防某些恶意行为。但是,实现合理的跨 域请求对开发某些浏览器应用程序也是至关重要的。

  CORS(Cross-Origin Resource Sharing,跨源资源共享)是 W3C 的一个工作草案,定义了在必须访 问跨源资源时,浏览器与服务器应该如何沟通。CORS 背后的基本思想,就是使用自定义的 HTTP 头部 让浏览器与服务器进行沟通,从而决定请求或响应是应该成功,还是应该失败。

  比如一个简单的使用 GET 或 POST 发送的请求,它没有自定义的头部,而主体内容是 text/plain。在 发送该请求时,需要给它(请求)附加一个额外的 Origin 头部,其中包含请求页面的源信息(协议、域名和端 口),以便服务器根据这个头部信息来决定是否给予响应。下面是 Origin 头部的一个示例:

Origin: http://www.nczonline.net

  如果服务器认为这个请求可以接受,就在 Access-Control-Allow-Origin 头部中回发相同的源信息(如果是公共资源,可以回发”*”)。例如:

Access-Control-Allow-Origin: http://www.nczonline.net

  如果没有这个头部,或者有这个头部但源信息不匹配,浏览器就会驳回请求。正常情况下,浏览器 会处理请求。注意,请求和响应都不包含 cookie 信息。

IE对CORS的实现

微软在 IE8 中引入了 XDR(XDomainRequest)类型。这个对象与 XHR 类似,但能实现安全可靠 的跨域通信。XDR 对象的安全机制部分实现了 W3C 的 CORS 规范。以下是 XDR 与 XHR 的一些不同之 处。

  • cookie 不会随请求发送,也不会随响应返回。
  • 只能设置请求头部信息中的 Content-Type 字段。
  • 不能访问响应头部信息。
  • 只支持GET和POST请求。

这些变化使 CSRF(Cross-Site Request Forgery,跨站点请求伪造)XSS(Cross-Site Scripting,跨 站点脚本)的问题得到了缓解。被请求的资源可以根据它认为合适的任意数据(用户代理、来源页面等) 来决定是否设置 Access-Control- Allow-Origin 头部。作为请求的一部分,Origin 头部的值表示 请求的来源域,以便远程资源明确地识别 XDR 请求。

XDR 对象的使用方法与 XHR 对象非常相似。也是创建一个 XDomainRequest 的实例,调用 open() 方法,再调用 send()方法。但与 XHR 对象的 open()方法不同,XDR 对象的 open()方法只接收两个 参数:请求的类型和 URL。

所有 XDR 请求都是异步执行的,不能用它来创建同步请求。请求返回之后,会触发 load 事件, 响应的数据也会保存在 responseText 属性中,如下所示。

var xdr = new XDomainRequest();
xdr.onload = function(){
    alert(xdr.responseText);
};
xdr.open("get", "http://www.somewhere-else.com/page/");
xdr.send(null);

在接收到响应后,你只能访问响应的原始文本;没有办法确定响应的状态代码。而且,只要响应有 效就会触发 load 事件,如果失败(包括响应中缺少 Access-Control-Allow-Origin 头部)就会触 发 error 事件。遗憾的是,除了错误本身之外,没有其他信息可用,因此唯一能够确定的就只有请求 未成功了。要检测错误,可以像下面这样指定一个 onerror 事件处理程序。

var xdr = new XDomainRequest();
xdr.onload = function(){
    alert(xdr.responseText);
};
?xdr.onerror = function(){ 5 alert("An error occurred.");
};
xdr.open("get","SomeUrl");
xdr.send(null);

??鉴于导致 XDR 请求失败的因素很多,因此建议你不要忘记通过 onerror 事件处 理程序来捕获该事件;否则,即使请求失败也不会有任何提示。

在请求返回前调用 abort()方法可以终止请求:

xdr.abort(); //终止请求

与 XHR 一样,XDR 对象也支持 timeout 属性以及 ontimeout 事件处理程序。下面是一个例子。

var xdr = new XDomainRequest();
xdr.onload = function(){
XDomainRequestExample01.htm
??????alert(xdr.responseText);
}; 10 xdr.onerror = function(){
?    alert("An error occurred.");
};
xdr.timeout = 1000;
xdr.ontimeout = function(){
    alert("Request took too long.");
};
xdr.open("get", "http://www.somewhere-else.com/page/");
xdr.send(null);

这个例子会在运行 1 秒钟后超时,并随即调用 ontimeout 事件处理程序。

?为支持 POST 请求,XDR 对象提供了 contentType 属性,用来表示发送数据的格式,如下面的例子所示。

var xdr = new XDomainRequest();
xdr.onload = function(){
    alert(xdr.responseText);
};
xdr.onerror = function(){
    alert("An error occurred.");
};
xdr.open("post", "http://www.somewhere-else.com/page/");
xdr.contentType = "application/x-www-form-urlencoded";
xdr.send("name1=value1&name2=value2");

这个属性是通过 XDR 对象影响头部信息的唯一方式。

 其他浏览器对CORS的实现

Firefox 3.5+、Safari 4+、Chrome、ios 版 Safari 和 android 平台中的 WebKit 都通过 XMLHttpRequest 对象实现了对 CORS 的原生支持。在尝试打开不同来源的资源时,无需额外编写代码就可以触发这个行 为。要请求位于另一个域中的资源,使用标准的 XHR 对象并在 open()方法中传入绝对 URL 即可,例如:

var xhr = createXHR();
xhr.onreadystatechange = function(){
        if (xhr.readyState == 4){
            if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
                alert(xhr.responseText);
            } else {
                alert("Request was unsuccessful: " + xhr.status);
            }
} };
xhr.open("get", "http://www.somewhere-else.com/page/", true);
xhr.send(null);

与 IE 中的 XDR 对象不同,通过跨域 XHR 对象可以访问 status 和 statusText 属性,而且还支 持同步请求。跨域 XHR 对象也有一些限制,但为了安全这些限制是必需的。以下就是这些限制。

  • 不能使用 setRequestHeader()设置自定义头部。
  • 不能发送和接收 cookie。
  • 调用 getAllResponseHeaders()方法总会返回空字符串。

由于无论同源请求还是跨源请求都使用相同的接口,因此对于本地资源,最好使用相对 URL,在访 问远程资源时再使用绝对 URL。这样做能消除歧义,避免出现限制访问头部或本地 cookie 信息等问题。

Preflighted Reqeusts

CORS 通过一种叫做 Preflighted Requests 的透明服务器验证机制支持开发人员使用自定义的头部、 GET 或 POST 之外的方法,以及不同类型的主体内容。在使用下列高级选项来发送请求时,就会向服务 器发送一个 Preflight 请求。这种请求使用 OPTIONS 方法,发送下列头部。

  • Origin:与简单的请求相同。
  • Access-Control-Request-Method:请求自身使用的方法。
  • Access-Control-Request-Headers:(可选)自定义的头部信息,多个头部以逗号分隔。

以下是一个带有自定义头部 NCZ 的使用 POST 方法发送的请求。

Origin: http://www.nczonline.net
Access-Control-Request-Method: POST
Access-Control-Request-Headers: NCZ

发送这个请求后,服务器可以决定是否允许这种类型的请求。服务器通过在响应中发送如下头部与 浏览器进行沟通。

  • Access-Control-Allow-Origin:与简单的请求相同。
  • Access-Control-Allow-Methods:允许的方法,多个方法以逗号分隔。
  • Access-Control-Allow-Headers:允许的头部,多个头部以逗号分隔。
  • Access-Control-Max-Age:应该将这个 Preflight 请求缓存多长时间(以秒表示)。

例如:

Access-Control-Allow-Origin: http://www.nczonline.net
Access-Control-Allow-Methods: POST, GET
Access-Control-Allow-Headers: NCZ
Access-Control-Max-Age: 1728000

Preflight 请求结束后,结果将按照响应中指定的时间缓存起来。而为此付出的代价只是第一次发送 这种请求时会多一次 HTTP 请求。

支持 Preflight 请求的浏览器包括 Firefox 3.5+、Safari 4+和 Chrome。IE 10 及更早版本都不支持。

带凭据的请求

默认情况下,跨源请求不提供凭据(cookie、HTTP 认证及客户端 SSL 证明等)。通过将 withCredentials 属性设置为 true,可以指定某个请求应该发送凭据。如果服务器接受带凭据的请 求,会用下面的 HTTP 头部来响应。

Access-Control-Allow-Credentials: true

如果发送的是带凭据的请求,但服务器的响应中没有包含这个头部,那么浏览器就不会把响应交给javascript(于是,responseText 中将是空字符串,status 的值为 0,而且会调用 onerror()事件处 理程序)。另外,服务器还可以在 Preflight 响应中发送这个 HTTP 头部,表示允许源发送带凭据的请求。

跨浏览器的CORS

即使浏览器对 CORS 的支持程度并不都一样,但所有浏览器都支持简单的(非 Preflight 和不带凭据 的)请求,因此有必要实现一个跨浏览器的方案。检测 XHR 是否支持 CORS 的最简单方式,就是检查 是否存在 withCredentials 属性。再结合检测 XDomainRequest 对象是否存在,就可以兼顾所有浏 览器了。

?function createCORSRequest(method, url){
    var xhr = new XMLHttpRequest();
    if ("withCredentials" in xhr){
        xhr.open(method, url, true);
    } else if (typeof XDomainRequest != "undefined"){
?        xhr = new XDomainRequest();
        xhr.open(method, url);
    } else {
       xhr = null;
    }
        return xhr;
}

var request = createCORSRequest("get", "http://www.somewhere-else.com/page/"); 
if (request){
    request.onload = function(){
        // 对request.responseText 进行处理
    };
    request.send();
}

Firefox、Safari 和 Chrome 中的 XMLHttpRequest 对象与 IE 中的 XDomainRequest 对象类似,都 提供了够用的接口,因此以上模式还是相当有用的。这两个对象共同的属性/方法如下。

  • abort():用于停止正在进行的请求。
  • onerror:用于替代 onreadystatechange 检测错误。 ? onload:用于替代 onreadystatechange 检测成功。 - responseText:用于取得响应内容。
  • send():用于发送请求。

以上成员都包含在 createCORSRequest()函数返回的对象中,在所有浏览器中都能正常使用。

其他跨域技术

在 CORS 出现以前,要实现跨域 Ajax 通信颇费一些周折。开发人员想出了一些办法,利用 DOM 中 能够执行跨域请求的功能,在不依赖 XHR 对象的情况下也能发送某种请求。虽然 CORS 技术已经无处 不在,但开发人员自己发明的这些技术仍然被广泛使用,毕竟这样不需要修改服务器端代码。

图像Ping

上述第一种跨域请求技术是使用<img>标签。我们知道,一个网页可以从任何网页中加载图像,不 用担心跨域不跨域。这也是在线广告跟踪浏览量的主要方式。正如第 13 章讨论过的,也可以动态地创 建图像,使用它们的 onload 和 onerror 事件处理程序来确定是否接收到了响应。

动态创建图像经常用于图像 Ping。图像 Ping 是与服务器进行简单、单向的跨域通信的一种方式。 请求的数据是通过查询字符串形式发送的,而响应可以是任意内容,但通常是像素图或 204 响应。通过 图像 Ping,浏览器得不到任何具体的数据,但通过侦听 load 和 error 事件,它能知道响应是什么时 候接收到的。来看下面的例子。

var img = new Image();
img.onload = img.onerror = function(){
    alert("Done!");
};
img.src="https://gss0.baidu.com/7051cy89RMgCncy6lo7D0j9wexYrbOWh7c50/pi-loading.png" data-src = "http://www.example.com/test?name=Nicholas";

这里创建了一个 Image 的实例,然后将 onload 和 onerror 事件处理程序指定为同一个函数。这 样无论是什么响应,只要请求完成,就能得到通知。请求从设置 src="https://gss0.baidu.com/7051cy89RMgCncy6lo7D0j9wexYrbOWh7c50/pi-loading.png" data-src 属性那一刻开始,而这个例子在请 求中发送了一个 name 参数。

图像 Ping 最常用于跟踪用户点击页面或动态广告曝光次数。图像 Ping 有两个主要的缺点,一是只 能发送 GET 请求,二是无法访问服务器的响应文本。因此,图像 Ping 只能用于浏览器与服务器间的单向通信。

JSONP

JSONP 是 JSON with padding(填充式 JSON 或参数式 JSON)的简写,是应用 JSON 的一种新方法, 在后来的 Web 服务中非常流行。JSONP 看起来与 JSON 差不多,只不过是被包含在函数调用中的 JSON, 4就像下面这样。

callback({ "name": "Nicholas" });

JSONP 由两部分组成:回调函数和数据。回调函数是当响应到来时应该在页面中调用的函数。回调 函数的名字一般是在请求中指定的。而数据就是传入回调函数中的 JSON 数据。下面是一个典型的 JSONP 请求。

http://freegeoip.net/json/?callback=handleResponse

这个 URL 是在请求一个 JSONP 地理定位服务。通过查询字符串来指定 JSONP 服务的回调参数是很 常见的,就像上面的 URL 所示,这里指定的回调函数的名字叫 handleResponse()。

JSONP 是通过动态<script>元素来使用的,使用时可以为src属性指定一个跨域 URL。这里的<script>元素与<img>元素类似,都有能力不受限制地从其他域 加载资源。因为 JSONP 是有效的 JavaScript 代码,所以在请求完成后,即在 JSONP 响应加载到页面中 以后,就会立即执行。来看一个例子。 ?

function handleResponse(response){
alert("You’re at IP address " + response.ip + ", which is in " +
          response.city + ", " + response.region_name);
}
var script = document.createElement("script");
script.src="http://freegeoip.net/json/?callback=handleResponse"; 
document.body.insertBefore(script, document.body.firstChild);

这个例子通过查询地理定位服务来显示你的 IP 地址和位置信息。
JSONP 之所以在开发人员中极为流行,主要原因是它非常简单易用。与图像 Ping 相比,它的优点 在于能够直接访问响应文本,支持在浏览器与服务器之间双向通信。不过,JSONP 也有两点不足。

    • 首先,JSONP 是从其他域中加载代码执行。如果其他域不安全,很可能会在响应中夹带一些恶意代码,而此时除了完全放弃 JSONP 调用之外,没有办法追究。因此在使用不是你自己运维的 Web 服务时, 一定得保证它安全可靠。

    • 其次,要确定 JSONP 请求是否失败并不容易。虽然 html5 给<script>元素新增了一个 onerror 事件处理程序,但目前还没有得到任何浏览器支持。为此,开发人员不得不使用计时器检测指定时间内是否接收到了响应。但就算这样也不能尽如人意,毕竟不是每个用户上网的速度和带宽都一样。

 

以上是关于跨域资源共享(CORS)的主要内容,如果未能解决你的问题,请参考以下文章

CORS(跨域资源共享)

cors,跨域资源共享,Java配置

跨域资源共享 CORS 详解

跨域资源共享 CORS 详解

CORS跨域资源共享学习记录

Spring Security (CORS)跨域资源访问配置