瀏覽器安全阻止了一個網頁中向另外一個域提交請求,這個限制叫做同域策咯(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頭,列出了允許的請求頭,如果先行請求成功,瀏覽器隨即發送上文所敘的真正的請求。