在ASP.NET 5应用程序中的跨域请求功能详解
浏览器安全阻止了一个网页中向另外一个域提交请求,这个限制叫做同域策咯(same-origin policy),这组织了一个恶意网站从另外一个网站读取敏感数据,但是一些特殊情况下,你需要允许另外一个站点跨域请求你的网站。
跨域资源共享(CORS:Cross Origin Resources Sharing)是一个W3C标准,它允许服务器放宽对同域策咯的限制,使用CORS,服务器可以明确的允许一些跨域的请求,并且拒绝其它的请求。CORS要比JSONP要相对安全而且更加灵活,这一个章节主要讲述怎么在你的ASP.NET 5应用程序中开启CORS。
什么是“同域”
两个URL含有同样的协议、主机地址和端口号即为同域,或者称为同源。
以下两个URL即为同域
下文中的URL都不是同域
注意:IE在判断同域时忽略了端口号
添加CORS包
在项目的project.json文件中,添加以下内容
"dependencies": {
"Microsoft.AspNet.Cors": "1.0.0-beta6"
},
在应用程序中配置CORS
这一节展示如何配置CORS,首先,添加CORS服务,在Startup.cs中添加以下内容:
public void ConfigureServices(IServiceCollection services)
{
services.AddCors();
}
下一步,配置跨域规则,使用CorsPolicyBuilder类,有两种方法来配置,第一种,调用UseCors方法并使用lambda表达式:
public void Configure(IApplicationBuilder app) { app.UseCors(builder => builder.WithOrigins("http://example.com")); }
稍后将详细解析所有的配置细节,现在只需要知道在这个示例中,这个规则仅允许从http://example.com的跨域请求。
注意这个CorsPolicyBuilder有一个流式的API,所以你可以这样链式调用方法:
app.UseCors(builder => builder.WithOrigins("http://example.com") .AllowAnyHeader() );
第二种方式你首先定义一或多个CORS策咯,然后在运行时使用name选择策咯:
public void ConfigureServices(IServiceCollection services) { services.AddCors(); services.ConfigureCors(options => { options.AddPolicy("AllowSpecificOrigin", builder => builder.WithOrigins("http://example.com")); }); } public void Configure(IApplicationBuilder app) { app.UseCors("AllowSpecificOrigin"); }
这里定义了一个名为AllowSpecificOrigin的CORS规则,运行时传入了这个名字给UseCors方法。
CORS策略选项
这一节介绍在配置CORO策略时的若干个选项。
设置允许的域
允许一或多个指定的域:
options.AddPolicy("AllowSpecificOrigins", builder => { builder.WithOrigins("http://example.com", "http://www.contoso.com"); });
允许所有的域:
options.AddPolicy("AllowAllOrigins", builder => { builder.AllowAnyOrigin(); });
在允许所有域之前需要仔细考虑,这将意味着任何web站点都将可以通过AJAX请求调用你的应用。
设置允许的HTTP方法
指定哪些HTTP方法允许访问资源:
options.AddPolicy("AllowSpecificMethods", builder => { builder.WithOrigins("http://example.com") .WithMethods("GET", "POST", "HEAD"); });
允许所有的HTTP方法:
options.AddPolicy("AllowAllMethods", builder => { builder.WithOrigins("http://example.com") .AllowAnyMethod(); });
这将影响先行请求和Access-Control-Allow-Methods请求头。
设置允许的请求头
一个CORS先行请求也许包含了Access-Request-Headers头,列出应用程序的HTTP请求头。
指定允许的请求头到白名单:
options.AddPolicy("AllowHeaders", builder => { builder.WithOrigins("http://example.com") .WithHeaders("accept", "content-type", "origin", "x-custom-header"); });
允许所有的请求头:
options.AddPolicy("AllowAllHeaders", builder => { builder.WithOrigins("http://example.com") .AllowAnyHeader(); });
所有的浏览器对于设置什么Access-Control-Request-Headers的表现并不完全一致,所以加入你设置除了“*”意外的任何其他的头,你应该至少包含“accept”、“content-type”、“origin”,然后加上你想要支持的请求头。
设置暴露的响应头
默认情况下,浏览器并不暴露所有的响应头,默认可用的响应头如下所示:
- Cache-Control
- Content-Language
- Content-Type
- Expires
- Last-Modified
- Pragma
CORS可以通过简单的方法调用,让其他的响应头可用:
options.AddPolicy("ExposeResponseHeaders", builder => { builder.WithOrigins("http://example.com") .WithExposedHeaders("x-custom-header"); });
跨域请求中的凭据
凭据需要在CORS中做特殊的处理,默认情况下,浏览器在跨域请求中不发送任何凭据。凭据包含除HTTP认证方案之外的cookies。为了在跨域请求中发送凭据,客户端需要用设置XMLHttpRequest的withCredentials属性为true:
var xhr = new XMLHttpRequest(); xhr.open('get', 'http://www.example.com/api/test'); xhr.withCredentials = true;
在jQuery中:
$.ajax({ type: 'get', url: 'http://www.example.com/home', xhrFields: { withCredentials: true }
同样,服务器端也必须允许凭证:
options.AddPolicy("AllowCredentials", builder => { builder.WithOrigins("http://example.com") .AllowCredentials(); });
现在,HTTP响应将会包含一个Access-Control-Allow-Credentials头,告诉浏览器,服务端允许在跨域请求中包含凭证。
假如浏览器发送凭据,但是请求不包含一个有效的Access-Control-Allow-Credentials头,浏览器将不会在应用程序中暴露这个响应,并且AJAX请求将出错。
在允许凭证时候要相当注意,它意味着一个它域的网站在用户不知情的情况下将可以发送一个登陆成功用户的凭据给你的应用程序。CORS还规定如果允许凭证存在,那么将域设置为“*”是无效的。
设置先行请求的过期时间
Access-Control-Max-Age头指定了先行请求的响应可以缓存的时间。设置这个头:
options.AddPolicy("SetPreflightExpiration", builder => { builder.WithOrigins("http://example.com") .SetPreflightMaxAge(TimeSpan.FromSeconds(2520)); });
CORS是怎么样工作的
这一节将介绍在HTTP消息级别CORS请求中发生了什么。这对理解CORS如何工作非常重要,进而让你可以正确的配置自己的CORS策略,分析你的应用程序为什么不像预期的那样工作。
CORS规定提出了几个新的HTTP头来打开跨域请求。假如你的浏览器支持CORS,它将会自动的为设置跨域设置请求头,你不需要在Javascript中做任何特殊的处理。
下文是一个跨域请求的示例,Origin头设置了哪个域发出请求的信息:
GET http://myservice.azurewebsites.net/api/test HTTP/1.1 Referer: http://myclient.azurewebsites.net/ Accept: */* Accept-Language: en-US Origin: http://myclient.azurewebsites.net Accept-Encoding: gzip, deflate User-Agent: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0) Host: myservice.azurewebsites.net
假如服务器允许这个请求,它将设置一个Access-Control-Allow-Origin头,这个值和请求的Origin值匹配或者是一个*通配符,代表所有的域都是被允许的:
HTTP/1.1 200 OK Cache-Control: no-cache Pragma: no-cache Content-Type: text/plain; charset=utf-8 Access-Control-Allow-Origin: http://myclient.azurewebsites.net Date: Wed, 20 May 2015 06:27:30 GMT Content-Length: 12 Test message
假如响应不包含Access-Control-Allow-Origin头,AJAX请求就会失败,但是如果浏览器不允许这个请求,即使服务器翻译一个成功的响应,浏览器也不会正确的使用这个响应内容。
先行请求
一些CORS请求中,浏览器在发送真实的请求资源的请求之前,发送一个附加的请求叫做“preflight request”(本文中的先行请求),在以下条件都满足的情况下,浏览器可以忽略这个先行请求:
- 请求方法是GET、HEAD或者POST
- 应用程序除了Accept-Language, Content-Language, Content-Type和 Last-Event-ID以为不设置任何其他请求头
- Content-Type头是以下中的一个:
- application/x-www-form-urlencoded
- multipart/form-data
- text/plain
设置在头中的规则是通过应用程序调用XMLHttpRequest的setRequestHander方法来应用的,规则不应用浏览器自己的头,例如User-Agent、Hosts、Content-Length。
以下是一个先行请求的示例:
OPTIONS http://myservice.azurewebsites.net/api/test HTTP/1.1 Accept: */* Origin: http://myclient.azurewebsites.net Access-Control-Request-Method: PUT Access-Control-Request-Headers: accept, x-my-custom-header Accept-Encoding: gzip, deflate User-Agent: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0) Host: myservice.azurewebsites.net Content-Length: 0
先行请求使用HTTP OPTIONS方法,它包含两个特殊的头:
- Access-Control-Request-Method:在真正请求中将会被使用的HTTP方法
- Access-Control-Request-Headers::设置在真正请求中的头的列表(同样不包含浏览器自己的请求头)
下文中是一个示例,并且假设服务端允许请求:
HTTP/1.1 200 OK Cache-Control: no-cache Pragma: no-cache Content-Length: 0 Access-Control-Allow-Origin: http://myclient.azurewebsites.net Access-Control-Allow-Headers: x-my-custom-header Access-Control-Allow-Methods: PUT Date: Wed, 20 May 2015 06:33:22 GMT
响应包含一个Access-Control-Allow-Methods头,它列出了允许的方法,还有一个附加的Access-Control-Allow-Headers头,列出了允许的请求头,如果先行请求成功,浏览器随即发送上文所叙的真正的请求。