HTTP 访问控制(CORS)

  跨域请求:请求不属于自己域(domain)下资源。例如,一个来自http://domain-a.com域下的HTML page请求http://domain-b.com域的图片资源。当今,网站中许多页面从别的域下加载像CSS stylesheets, images and scripts资源。
  出于安全考虑,浏览器会限制跨域脚本执行请求。例如,XMLHttpRequest遵循同源策略(所谓同源是指,域名,协议,端口相同;举个简单例子,当浏览器的百度tab页执行一个脚本的时候会检查这个脚本是属于哪个页面的,即检查是否同源,只有和百度同源的脚本才会被执行)。因此,一个网站使用XMLHttpRequest只能请求它自己域下的资源。为了提升web应用,开发者请求浏览器供应商允许XMLHttpRequest做出跨域请求。
  W3C Web Applications Working Group推出了一种新的机制叫Cross-Origin Resource Sharing(跨域资源共享,简写为CORS)。CORS对服务器跨域做了访问控制,使跨域数据能够安全传输。现代浏览器在API容器当中使用CORS,比如,XMLHttpRequest,来降低跨域请求的风险。
  现代浏览器会处理客户端跨域资源共享的请求内容,包括请求头以及策略的执行。添加了新的标准意味着服务器就要处理新的请求头以及返回对应新的响应头。
跨域共享标准可以使以下进行跨站点请求:
1. 向上面讲述的一样,在跨域行为中调用XMLHttpRequest API
2. Web字体,以至于服务器可以部署那些只能被跨域站点加载和那些被允许使用的网站字体
3. WebGL textures
4. Images/video frames drawn to a canvas using drawImage
5. css样式
6. script脚本


概述

  跨域资源共享标准通过增加新的HTTP请求头,允许服务器描述一系列被允许使用浏览器读取信息的请求。除此之外,对于那些可能给用户数据造成负面影响HTTP请求方式,浏览器会对请求进行”预检“,通过OPTIONS请求方式从服务器上拉取被支持的请求方式,那时,得到了服务器的允许,再发送真正的HTTP请求方式。服务器也可以通知客户端是否请求时要带上“证书”。
接下来的部分讨论一些使用场景,以及对HTTP请求头使用分解。


访问控制场景例子

  在这里,我们呈现三个场景以阐述跨域资源共享是怎么工作的。所有的这些例子都是使用XMLHttpRequest对象,它能够被所有支持的浏览器进行跨站点调用。
  这些javascript代码能够执行在支持跨站点的XMLHttpRequest浏览器上。现在从服务器的角度来讨论跨域资源共享。


简单请求

一个简单的跨站点请求需要满足以下条件:

只允许如下请求方式:

  • GET
  • HEAD
  • POST

除了被用户代理自动设置的请求头,下面请求头可以允许手动设置:

  • Accept
  • Accept-Language
  • Content-Language(表示当前页面的语言)
  • Content-Type
 <meta http-equiv="Content-Type" content="text/html;charset=utf-8"> 
 <meta http-equiv="Content-Language" content="zh-CN">

charset和Content-Language的含义就是,当前页面的语言是中文的,请浏览器使用UTF-8编码字符集进行解码显示
1. Content-Type被允许使用的值为:
* application/x-www-form-urlencoded
* multipart/form-data
* text/plain
例如,假设在域http://foo.example下的内容希望调用在域http://bar.other下的内容。那么写在域foo.example中的javascript代码如下:

var invocation = new XMLHttpRequest();
var url = 'http://bar.other/resources/public-data/'; 
function callOtherDomain() {
  if(invocation) {    
    invocation.open('GET', url, true);
    invocation.onreadystatechange = handler;
    invocation.send(); 
  }
}

让我们来看一下发往服务器端的请求内容,以及服务器端的响应内容:

GET /resources/public-data/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Referer: http://foo.example/examples/access-control/simpleXSInvocation.html
Origin: http://foo.example


HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 00:23:53 GMT
Server: Apache/2.0.61 
Access-Control-Allow-Origin: *
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/xml

[XML Data]

空白行下面的内容,是服务端响应内容。在响应内容中,服务器返回了Access-Control-Allow-Origin头部。在这个例子中,服务端返回Access-Control-Allow-Origin: *表明任何跨域访问站点都可以访问http://bar.other域下的资源。如果资源的拥有域http://bar.other只希望http://foo.example域访问它的资源那么可以修改成如下:

Access-Control-Allow-Origin: http://foo.example

预检请求

  不像简单的请求,预检请求第一次以OPTIONS方式请求其他域的资源,这样是为了确保真正的请求安全发送。由于跨站点请求可能对用户数据产生影响,因此它需要进行预检。下面列出了一些需要预检请求特点:
- 请求的方式不是GET、HEAD、POST其中之一。还有,如果POST发送数据类型(Content-Type)不是application/x-www-form-urlencoded、multipart/form-data、text/plain,比如POST发送的数据是XML格式,application/xml、text/xml,那时该请求需要预检。
- 设置自定义的请求头部(比如X-PINGOTHER)
下面是例子:

var invocation = new XMLHttpRequest();
var url = 'http://bar.other/resources/post-here/';
var body = '<?xml version="1.0"?><person><name>Arun</name></person>';  
function callOtherDomain(){
  if(invocation)
    {
      invocation.open('POST', url, true);
      invocation.setRequestHeader('X-PINGOTHER', 'pingpong');
      invocation.setRequestHeader('Content-Type', 'application/xml');
      invocation.onreadystatechange = handler;
      invocation.send(body); 
    }
}

例子中,第3行创建了XML内容并且以POST方式进行请求。还有,在第8行自定义了一个请求头(X-PINGOTHER:pingpong)。这样的请求头不是HTTP/1.1一部分,但是普遍的应用在web应用中。由于这个请求使用了POST并且Content-Type值不是上面讲到的三个之一,并且自定义了一个头部,因此这个请求就要进行预检。
让我们看看客户端与服务端完整的请求与响应内容:

OPTIONS /resources/post-here/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Origin: http://foo.example
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type


HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain

POST /resources/post-here/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
X-PINGOTHER: pingpong
Content-Type: text/xml; charset=UTF-8
Referer: http://foo.example/examples/preflightInvocation.html
Content-Length: 55
Origin: http://foo.example
Pragma: no-cache
Cache-Control: no-cache

<?xml version="1.0"?><person><name>Arun</name></person>


HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:40 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://foo.example
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 235
Keep-Alive: timeout=2, max=99
Connection: Keep-Alive
Content-Type: text/plain

[Some GZIP'd payload]

1~12行就是OPTIONS方式进行预检请求。OPTIONS方式是HTTP/1.1的请求方式,是用来进一步确定来自服务端的信息,并且它是一个幂等(在编程中,一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同;幂等函数,或幂等方法,是指可以使用相同参数重复执行,并能获得相同结果的函数,GET,HEAD, PUT, DELETE方式都是幂等的)的方法,这意味着它不能改动资源,有两个请求头随着OPTIONS请求发送(第10行和第11行):

Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type

Access-Control-Request-Method就是告知服务器,当我发起真正请求时,请求方式是POST。The Access-Control-Request-Headers通知服务器,当我发起真正请求时,将发送自定义值X-PINGOHER和Content-Type的请求头。现在服务器就可以有机会决定,在这样的环境下它是否希望接受请求。
14~26行服务器端送回的响应表明请求方式POST和请求头X-PINGOTHER可接受。特别看一下17~20行:

Access-Control-Allow-Origin: http://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400

Access-Control-Allow-Methods表明POST、GET、OPTIONS是可以访问资源请求方式;Access-Control-Allow-Headers表明请求时允许使用X-PINGOTHER、Content-Type头部。它们的值之间以逗号隔开。
Access-Control-Max-Age表明经过多久才会进行下一次预检。


认证请求

  最令人感兴趣的是XMLHttpRequest和Access Control能够发送认证请求,认证请求浏览器默认是关闭的。具体认证请求看下面例子:

var invocation = new XMLHttpRequest();
var url = 'http://bar.other/resources/credentialed-content/';

function callOtherDomain(){
  if(invocation) {
    invocation.open('GET', url, true);
    invocation.withCredentials = true;
    invocation.onreadystatechange = handler;
    invocation.send(); 
  }
}

启用认证只需要也就是withCredentials=true。这样浏览器将拒绝任何不带 Access-Control-Allow-Credentials: true头部的响应,具体看下面内容:

GET /resources/access-control-with-credentials/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Referer: http://foo.example/examples/credential.html
Origin: http://foo.example
Cookie: pageAccess=2


HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:34:52 GMT
Server: Apache/2.0.61 (Unix) PHP/4.4.7 mod_ssl/2.0.61 OpenSSL/0.9.7e mod_fastcgi/2.4.2 DAV/2 SVN/1.4.2
X-Powered-By: PHP/5.2.6
Access-Control-Allow-Origin: http://foo.example
Access-Control-Allow-Credentials: true
Cache-Control: no-cache
Pragma: no-cache
Set-Cookie: pageAccess=3; expires=Wed, 31-Dec-2008 01:34:53 GMT
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 106
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain


[text/plain payload]

尽管11行向http://bar.other发送了Cookie,如果bar.other不响应 Access-Control-Allow-Credentials: true头部,这个服务端响应将被客户端拒绝。重要提示:当响应认证请求时,服务端必须指定域,不能使用通配符,例如:Access-Control-Allow-Origin: *。


从这篇文章当中了解到跨域访问很多知识,自己也明朗了许多,写的非常好,于是就翻译出来,如有翻译不当的地方还望见谅!

                                                            -_-没有天才,只有勤奋的天才

被译文地址:https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值