您的位置:首页 > 理论基础 > 计算机网络

HTTP访问控制(CORS)

2017-03-25 17:03 387 查看
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS

当一个资源请求一个其它域名或者另外一个端口的资源时会产生一个跨域HTTP请求(cross-originHTTPrequest)。比如说,http://domaina.example的某HTML页面通过<img>的src请求http://domainb.foo/image.jpg。在当今的Web开发中,许多页面都会从另外一个站点加载各类资源(包括CSS、图片、JavaScript脚本以及其它类资源)。

出于安全考虑,浏览器会限制脚本中发起的跨域请求。比如,使用
XMLHttpRequest
Fetch发起HTTP请求就必须遵守同源策略。所以,Web应用程序通过
XMLHttpRequest
对象或Fetch能且只能向同域名的资源发起HTTP请求,而不能向任何其它域名发起请求。为了改进/提升Web应用程序,开发人员要求浏览器供应商允许跨域请求。(译者注:这段描述跨域不准确,跨域并非浏览器限制了发起跨站请求,而是跨站请求可以正常发起,但是返回结果被浏览器拦截了。最好的例子是CSRF跨站攻击原理,请求是发送到了后端服务器无论是否跨域!注意:有些浏览器不允许从HTTPS的域跨域访问HTTP,比如Chrome和Firefox,这些浏览器在请求还未发出的时候就会拦截请求,这是一个特例。)



跨源资源共享(CORS)机制让Web应用服务器能支持跨站访问控制,从而使得安全地进行跨站数据传输成为可能。需要特别注意的是,这个规范是针对API容器的(比如说
XMLHttpRequest
或者Fetch),以减轻跨域HTTP请求的风险。

这篇文章适用于网站管理员、服务器端程序开发人员以及前端开发人员。现代浏览器支持跨源共享的客户端组件,包括请求头和策略执行。同样,服务器端则需要解析这些新的请求头,并按照策略返回相应的响应头以及所请求的资源。对于服务器端程序开发人员,还可以阅读补充材料cross-originsharingfromaserverperspective(withPHPcodesnippets)。

跨源资源共享标准(cross-originsharingstandard)使得以下场景可以使用跨站HTTP请求:

如上所述,使用
XMLHttpRequest
Fetch发起跨站HTTP请求。

Web字体(CSS中通过
@font-face
使用跨站字体资源),因此,网站就可以发布TrueType字体资源,并只允许已授权网站进行跨站调用。

WebGL贴图

使用
drawImage
将Images/video画面绘制到canvas.

样式表(使用CSSOM)

Scripts(未处理的异常)

接下来的文章,会对跨源资源共享做一个总览,并介绍下需要使用的HTTP头。

概述EDIT

跨源资源共享标准通过新增一系列HTTP头,让服务器能声明哪些来源可以通过浏览器访问该服务器上的资源。另外,对那些会对服务器数据造成副作用的HTTP请求方法(特别是
GET
以外的HTTP方法,或者搭配某些MIME类型的
POST
请求),标准强烈要求浏览器必须先以
OPTIONS
请求方式发送一个预请求(preflightrequest),从而获知服务器端对跨源请求所支持HTTP方法。在确认服务器允许该跨源请求的情况下,以实际的HTTP请求方法发送那个真正的请求。服务器端也可以通知客户端,是不是需要随同请求一起发送信用信息(包括Cookies和HTTP认证相关数据)。

随后的章节,将对相关情景及用到的HTTP请求进行讨论。

一些访问控制场景EDIT

在此,我们会用三个场景来解释跨源共享是怎么运行的。其中,所有例子使用的都是能发起跨站请求
XMLHttpRequest
对象。

如果对以下章节中的JavaScript代码片段感兴趣,可以访问http://arunranga.com/examples/access-control/。在所有支持跨站
XMLHttpRequest
请求的浏览中,可以看到实际运行效果。

而如果想继续了解服务器端对跨源请求的处理,则可以访问Server-Side_Access_Control(CORS)。

简单请求

一些请求不会触发CORSpreflight。而这部分在本文中被称为“简单请求”,虽然Fetch(定义CORS的)不使用这个术语。满足下述条件的就是“简单请求”:

只允许下列方法:

GET


HEAD


POST


除了用户代理自动设置的头部外(比如
Connection
User-Agent
,或者其他任意的Fetch规范定义的“禁止的头部名”),唯一允许人工设置的头部是Fetch规范定义的“CORS-safelistedrequest-header”,如下:

Accept


Accept-Language


Content-Language


Content-Type
(butnotetheadditionalrequirementsbelow)

DPR


Downlink


Save-Data


Viewport-Width


Width


允许的
Content-Type
值有:

application/x-www-form-urlencoded


multipart/form-data


text/plain


注意:这些跨站请求与以往浏览器发出的跨站请求并无异同。并且,如果服务器不给出适当的响应头,则不会有任何数据返回给请求方。因此,那些不允许跨站请求的网站无需为这一新的HTTP访问控制特性担心。
注意:WebKitNightly和SafariTechnologyPreview添加了额外的限制在
Accept
,
Accept-Language
,and
Content-Language
头部的值。如果这些头部的值是非标准的,WebKit/Safari就不会将这些请求视为“简单请求”。WebKit/Safari并没有将非标准的头部值列个文档出来,但是可以在WebKitbug里体现:Requirepreflightfornon-standardCORS-safelistedrequestheadersAccept,Accept-Language,andContent-Language,AllowcommasinAccept,Accept-Language,andContent-LanguagerequestheadersforsimpleCORS,andSwitchtoablacklistmodelforrestrictedAcceptheadersinsimpleCORSrequests。其它的浏览器并没有这个限制的实现,因为这个不在规范的内容里。
比如说,假如站点http://foo.example的网页应用想要访问http://bar.other的资源。以下的JavaScript代码应该会在foo.example上执行:

[code;'>varinvocation=newXMLHttpRequest()]varurl='http://bar.other/resources/public-data/';

functioncallOtherDomain(){
if(invocation){
invocation.open('GET',url,true);
invocation.onreadystatechange=handler;
invocation.send();
}
}
这将导致客户端和服务器之间发生简单交换,使用CORS头来处理跨域:



让我们看看,在这个场景中,浏览器会发送什么的请求到服务器,而服务器又会返回什么给浏览器:

[code;'>GET/resources/public-data/HTTP/1.1
Host:bar.other
User-Agent:Mozilla/5.0(Macintosh][code=plain]Origin,它表明了该请求来自于
http://foo.exmaple


第13~22行则是http://bar.other服务器的响应。如第16行所示,服务器返回了响应头
Access-Control-Allow-Origin
。使用Origin和Access-Control-Allow-Origin就能完成最简单的访问控制。本例中,服务器返回了
Access-Control-Allow-Origin:*
,这意味着该资源跨域在跨域行为里可以被任意站点访问。如果服务器端仅允许来自http://foo.example的跨站请求,它可以返回:

Access-Control-Allow-Origin:'target='_blank'>http://foo.example[/code]
现在,除了http://foo.example,其它站点就不能跨站访问http://bar.other的资源了(在请求头里ORIGIN中定义,见上述第10行)。
Access-Control-Allow-Origin
需要为*或者包含由Origin指明的站点。

预请求

不同于上面讨论的简单请求,“预请求”要求必须先发送一个
OPTIONS
方法请求给目的站点,来查明这个跨站请求对于目的站点是不是安全的可接受的。这样做,是因为跨站请求可能会对目的站点的数据产生影响。当请求具备以下条件,就会被当成预请求处理:

使用下述方法以外的请求:

GET


HEAD


POST


除了用户代理自动设置的头部外(比如
Connection
User-Agent
,或者其他任意的Fetch规范定义的“禁止的头部名”),预请求不包括Fetch规范定义的“CORS-safelistedrequest-header”:

Accept


Accept-Language


Content-Language


Content-Type
(butnotetheadditionalrequirementsbelow)

DPR


Downlink


Save-Data


Viewport-Width


Width


Content-Type
头部的值除了下列之外的:

application/x-www-form-urlencoded


multipart/form-data


text/plain


注意:WebKitNightly和SafariTechnologyPreview添加了额外的限制在
Accept
,
Accept-Language
,and
Content-Language
头部的值。如果这些头部的值是非标准的,WebKit/Safari就不会将这些请求视为“简单请求”。WebKit/Safari并没有将非标准的头部值列个文档出来,但是可以在WebKitbug里体现:Requirepreflightfornon-standardCORS-safelistedrequestheadersAccept,Accept-Language,andContent-Language,AllowcommasinAccept,Accept-Language,andContent-LanguagerequestheadersforsimpleCORS,andSwitchtoablacklistmodelforrestrictedAcceptheadersinsimpleCORSrequests。其它的浏览器并没有这个限制的实现,因为这个不在规范的内容里。

示例如下:

varinvocation=newXMLHttpRequest();
varurl='http://bar.other/resources/post-here/';
varbody='<?xmlversion="1.0"?><person><name>Arun</name></person>';

functioncallOtherDomain(){
if(invocation)
{
invocation.open('POST',url,true);
invocation.setRequestHeader('X-PINGOTHER','pingpong');
invocation.setRequestHeader('Content-Type','application/xml');
invocation.onreadystatechange=handler;
invocation.send(body);
}
}

......

如上,以XMLHttpRequest创建了一个POST请求,为该请求添加了一个自定义请求头(X-PINGOTHER:pingpong),并指定数据类型为application/xml。所以,该请求是一个“预请求”形式的跨站请求。

让我们看看服务器与浏览器之间具体的交互过程:



[code;'>OPTIONS/resources/post-here/HTTP/1.1
Host:bar.other
User-Agent:Mozilla/5.0(Macintosh][code=language-html]Access-Control-Request-Method:POST
Access-Control-Request-Headers:X-PINGOTHER

请求头Access-Control-Request-Method
可以提醒服务器跨站请求
将使用POST方法,而请求头
Access-Control-Request-Headers则告知服务器该跨站请求将携带一个自定义请求头
X-PINGOTHER。这样,服务器就可以决定,在当前情况下,是否接受该跨站请求访问。


第15至27行是服务器的响应。该响应表明,服务器接受了客服端的跨站请求。具体可以看看第18至21行:

Access-Control-Allow-Origin:http://foo.exampleAccess-Control-Allow-Methods:POST,GET,OPTIONS
Access-Control-Allow-Headers:X-PINGOTHER
Access-Control-Max-Age:1728000

响应头
Access-Control-Allow-Methods表明服务器可以接受
POST
,
GET和
OPTIONS
的请求方法。请注意,这个响应头类似于HTTP/1.1Allow:responseheader,但仅限于访问控制的场景下。而响应头
Access-Control-Allow-Headers则表示服务器接受自定义请求头X-PINGOTHER。
就像[code]Access-Control-Allow-Methods一样,Access-Control-Allow-Headers允许以逗号分隔,传递一个可接受的自定义请求头列表。最后,[code]响应头Access-Control-Max-Age告诉浏览器,本次“预请求”的响应结果有效时间是多久。在上面的例子里,1728000秒代表着20天内,浏览器在处理针对该服务器的跨站请求,都可以无需再发送“预请求”,只需根据本次结果进行判断处理。

附带凭证信息的请求

XMLHttpRequest
和访问控制功能,最有趣的特性就是,发送凭证请求(HTTPCookies和验证信息)的功能。一般而言,对于跨站请求,浏览器是不会发送凭证信息的。但如果将
XMLHttpRequest
的一个特殊标志位设置为true,浏览器就将允许该请求的发送。

http://foo.example站点的脚本向http://bar.other站点发送一个GET请求,并设置了一个Cookies
值。脚本代码如下:

[code;'>varinvocation=newXMLHttpRequest()]varurl='http://bar.other/resources/credentialed-content/';

functioncallOtherDomain(){
if(invocation){
invocation.open('GET',url,true);
invocation.withCredentials=true;
invocation.onreadystatechange=handler;
invocation.send();
}
如你所见,第七行代码将[code]XMLHttpRequest的
withCredentials标志设置为true,
从而使得Cookies可以随着请求发送。因为这是一个简单的GET请求,所以浏览器不会发送一个“预请求”。但是,如果服务器端的响应中,如果没有返回
Access-Control-Allow-Credentials:true的响应头,那么浏览器将不会把响应结果传递给发出请求的脚本程序,以保证信息的安全。


客户端与服务器端交互示例如下:

[code;'>GET/resources/access-control-with-credentials/HTTP/1.1
Host:bar.other
User-Agent:Mozilla/5.0(Macintosh]查看这里.下一部分将讨论实际的HTTP头信息.

HTTP响应头EDIT

这部分里列出了跨域资源共享(Cross-OriginResourceSharing)时,服务器端需要返回的响应头信息.上一部分内容是这部分内容在实际运用中的一个概述.

Access-Control-Allow-Origin

返回的资源需要有一个Access-Control-Allow-Origin头信息,语法如下:

Access-Control-Allow-Origin:<origin>|*

origin参数指定一个允许向该服务器提交请求的URI.对于一个不带有credentials的请求,可以指定为'*',表示允许来自所有域的请求.

举个例子,允许来自http://mozilla.com的请求,你可以这样指定:

Access-Control-Allow-Origin:'target='_blank'>http://mozilla.com[/code]如果服务器端指定了域名,而不是'*',那么响应头的Vary值里必须包含Origin.它告诉客户端:响应是根据请求头里的Origin的值来返回不同的内容的.

Access-Control-Expose-Headers

RequiresGecko2.0(Firefox4/Thunderbird3.3/SeaMonkey2.1)

设置浏览器允许访问的服务器的头信息的白名单:

Access-Control-Expose-Headers:X-My-Custom-Header,X-Another-Custom-Header

这样,
X-My-Custom-Header
X-Another-Custom-Header这两个头信息,都可以被浏览器得到.


Access-Control-Max-Age

这个头告诉我们这次预请求的结果的有效期是多久,如下:

Access-Control-Max-Age:<delta-seconds>

delta-seconds
参数表示,允许这个预请求的参数缓存的秒数,在此期间,不用发出另一条预检请求.

Access-Control-Allow-Credentials

告知客户端,当请求的credientials属性是true的时候,响应是否可以被得到.当它作为预请求的响应的一部分时,它用来告知实际的请求是否使用了credentials.注意,简单的GET请求不会预检,所以如果一个请求是为了得到一个带有credentials的资源,而响应里又没有Access-Control-Allow-Credentials头信息,那么说明这个响应被忽略了.

Access-Control-Allow-Credentials:true|false

带有credential的请求在上面讨论.

Access-Control-Allow-Methods

指明资源可以被请求的方式有哪些(一个或者多个).这个响应头信息在客户端发出预检请求的时候会被返回.上面有相关的例子.

Access-Control-Allow-Methods:<method>[,<method>]*

发出预检请求的例子见上,这个例子里就有向客户端发送Access-Control-Allow-Methods响应头信息.

Access-Control-Allow-Headers

也是在响应预检请求的时候使用.用来指明在实际的请求中,可以使用哪些自定义HTTP请求头.比如

Access-Control-Allow-Headers:X-PINGOTHER

这样在实际的请求里,请求头信息里就可以有这么一条:

[code;'>X-PINGOTHER:pingpong

可以有多个自定义HTTP请求头,用逗号分隔.

Access-Control-Allow-Headers:<]>[,<field-name>]*

HTTP请求头EDIT

这部分内容列出来当浏览器发出跨域请求时可能用到的HTTP请求头.注意这些请求头信息都是在请求服务器的时候已经为你设置好的,当开发者使用跨域的XMLHttpRequest的时候,不需要手动的设置这些头信息.

Origin

表明发送请求或者预请求的域

Origin:<origin>

参数origin是一个URI,告诉服务器端,请求来自哪里.它不包含任何路径信息,只是服务器名.

Note:Origin的值可以是一个空字符串,这是很有用的.
注意,不仅仅是跨域请求,普通请求也会带有ORIGIN头信息.

Access-Control-Request-Method

在发出预检请求时带有这个头信息,告诉服务器在实际请求时会使用的请求方式

Access-Control-Request-Method:<method>

相关的例子可以在这里找到

Access-Control-Request-Headers

在发出预检请求时带有这个头信息,告诉服务器在实际请求时会携带的自定义头信息.如有多个,可以用逗号分开.

Access-Control-Request-Headers:<field-name>[,<field-name>]*

相关的例子可以在这里找到

浏览器支持EDIT

Desktop

Mobile

FeatureChromeFirefox(Gecko)InternetExplorerOperaSafari
Basicsupport43.58(viaXDomainRequest)
10
124

注意:

InternetExplorer8和9通过XDomainRequest对象来实现CORS,但是在IE10中有完整的实现。Firefox3.5就引入了对跨站XMLHttpRequests和Web字体的支持,尽管存在着一些直到后续版本才取消的限制。特别的,Firefox7引入了对跨站WebGL纹理的HTTP请求的支持,而且Firefox9添加对通过drawImage在canvas上绘图的支持。

相关链接EDIT

CodeSamplesShowing
XMLHttpRequest
andCross-OriginResourceSharing

Cross-OrigingResourceSharingFromaServer-SidePerspective(PHP,etc.)

Cross-OriginResourceSharingspecification

XMLHttpRequest


FurtherDiscussionoftheOriginHeader

UsingCORSwithAll(Modern)Browsers

UsingCORS-HTML5Rocks
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: