本文翻譯自Enabling Cross-Origin Requests in ASP.NET Web API 2
瀏覽器安全防止web頁(yè)面發(fā)出AJAX請(qǐng)求到另一個(gè)領(lǐng)域。這種限制稱(chēng)為同源策略,這是為了防止惡意網(wǎng)站讀取敏感數(shù)據(jù)滤钱。然而,有時(shí)候与境。您可能想要讓其他網(wǎng)站調(diào)用您的web API。
Cross Origin Resource Sharing(CORS)是一種W3C標(biāo)準(zhǔn),允許服務(wù)器放松同源策略窘行。CROS,服務(wù)器可以允許一些跨域源而拒絕其他域的請(qǐng)求崭放。CORS比之前JSONP等技術(shù)更安全电禀、更靈活。本教程展示了如何在Web API的應(yīng)用程序中啟用CROS己莺。
介紹
本教程演示了ASP.NET Web API.中使用 CORS奏甫。我們將首先創(chuàng)建兩個(gè)ASP.NET 項(xiàng)目。一個(gè)包含Web API控制器的“WebService”,另外一個(gè)其他“WebClient”,它調(diào)用WebService的接口凌受。因?yàn)閮蓚€(gè)應(yīng)用程序在不同的領(lǐng)域,一個(gè)AJAX請(qǐng)求從WebClient到WebService是一個(gè)跨源的要求阵子。
什么是同源
如果兩個(gè)URL他們有相同的域名,端口號(hào),這兩個(gè)URL就是有相同的源.即:同源
判斷是否同源有三個(gè)要素胜蛉,我們暫且稱(chēng)它們?yōu)椤巴慈亍保簠f(xié)議挠进,域名,端口號(hào)誊册。只要三要素中任何一個(gè)不一樣领突,就不同源。
下面是同源的兩個(gè)URL
- 1
http://example.com/foo.htm
- 2
http://example.com/bar.html
下面幾個(gè)URL相比上面兩個(gè)URL是不同源的 - 1
http://example.net
//域名不一樣 - 2
http://example.com:9000/foo.html
//端口號(hào)不一樣 - 3
https://example.com/foo.html
//協(xié)議不一樣案怯,采用了Https - 4
https://www.example.com/foo.html
創(chuàng)建WebService 項(xiàng)目
添加一個(gè) 名為 TestControllerWeb API 控制器
using System.Net.Http;
using System.Web.Http;
namespace WebService.Controllers
{
public class TestController : ApiController
{
public HttpResponseMessage Get()
{
return new HttpResponseMessage()
{
Content = new StringContent("GET: Test message")
};
}
public HttpResponseMessage Post()
{
return new HttpResponseMessage()
{
Content = new StringContent("POST: Test message")
};
}
public HttpResponseMessage Put()
{
return new HttpResponseMessage()
{
Content = new StringContent("PUT: Test message")
};
}
}
}
你可以在本地運(yùn)行應(yīng)用程序或部署到Azure君旦。(本教程中的截圖,我Web應(yīng)用程序部署到Azure應(yīng)用服務(wù)。)驗(yàn)證web API是否啟動(dòng)成功,導(dǎo)航到http://hostname/api/test/,主機(jī)名是署應(yīng)用程序時(shí)使用的域名嘲碱。您應(yīng)該看到響應(yīng)報(bào)文,“GET: Test Message”金砍。
創(chuàng)建WebClient 項(xiàng)目
在解決方案資源管理器,打開(kāi)文件/ Home / Index.cshtml 。用以下代碼替換該文件中的代碼:
<div>
<select id="method">
<option value="get">GET</option>
<option value="post">POST</option>
<option value="put">PUT</option>
</select>
<input type="button" value="Try it" onclick="sendRequest()" />
<span id='value1'>(Result)</span>
</div>
@section scripts {
<script>
// TODO: Replace with the URL of your WebService app
var serviceUrl = 'http://mywebservice/api/test';
function sendRequest() {
var method = $('#method').val();
$.ajax({
type: method,
url: serviceUrl
}).done(function (data) {
$('#value1').text(data);
}).error(function (jqXHR, textStatus, errorThrown) {
$('#value1').text(jqXHR.responseText || textStatus);
});
}
</script>
}
備注:serviceUrl變量,使用WebService 項(xiàng)目的URI÷缶猓現(xiàn)在在本地運(yùn)行WebClient應(yīng)用程序或發(fā)布到另一個(gè)網(wǎng)站捞魁。
點(diǎn)擊"Try It”按鈕提交一個(gè)AJAX請(qǐng)求到WebService應(yīng)用程序,使用下拉框中列出的HTTP方法(GET、POST离咐、或者put)谱俭。這讓我們檢查不同跨源請(qǐng)求。現(xiàn)在, WebService應(yīng)用程序不支持CORS,所以如果你單擊按鈕,你會(huì)得到一個(gè)錯(cuò)誤宵蛀。
允許CORS
現(xiàn)在讓我們?cè)赪ebService應(yīng)用CORS昆著。首先,添加CORSNuGet包。在Visual Studio中,從“工具”菜單上,選擇庫(kù)軟件包管理器,然后選擇包管理器控制臺(tái)术陶。在包管理器控制臺(tái)窗口中,鍵入以下命令:
nstall-Package Microsoft.AspNet.WebApi.Cors
這個(gè)命令安裝最新的包和更新所有依賴(lài)項(xiàng),包括核心Web API庫(kù)凑懂。User version標(biāo)志針對(duì)一個(gè)特定的版本。 CORS包需要Web API 2.0或更高版本梧宫。
打開(kāi)文件App_Start / WebApiConfig.cs接谨。將下面的代碼添加到WebApiConfig.Register方法摆碉。
using System.Web.Http;
namespace WebService
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// New code
config.EnableCors();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
}
接下來(lái),TestController類(lèi)添加EnableCors屬性:
using System.Net.Http;
using System.Web.Http;
using System.Web.Http.Cors;
namespace WebService.Controllers
{
[EnableCors(origins: "http://mywebclient.azurewebsites.net", headers: "*", methods: "*")]
public class TestController : ApiController
{
// Controller methods not shown...
}
}
origins參數(shù),使用WebClient應(yīng)用程序部署時(shí)用用的的URI,這允許跨域源來(lái)自WebClient的請(qǐng)求,同時(shí)還禁止所有其他跨域請(qǐng)求脓豪。之后,我將詳細(xì)描述[EnableCors]的參數(shù)巷帝。
備注:URI與URL不同,URI :Uniform Resource Identifier扫夜,統(tǒng)一資源標(biāo)識(shí)符
URL:Uniform Resource Locator楞泼,統(tǒng)一資源定位符
URI以scheme和冒號(hào)開(kāi)頭。Scheme用大寫(xiě)/小寫(xiě)字母開(kāi)頭笤闯,后面為空或者跟著更多的大寫(xiě)/小寫(xiě)字母堕阔、數(shù)字、加號(hào)颗味、減號(hào)和點(diǎn)號(hào)超陆。冒號(hào)把scheme與scheme-specific-part分開(kāi)了,并且scheme-specific-part的語(yǔ)法和語(yǔ)義(意思)由URI的名字空間決定浦马。如下面的例子:
http://域名侥猬,其中http是scheme,//域名 是scheme-specific-part捐韩,并且它的scheme與scheme-specific-part被冒號(hào)分開(kāi)了退唠。
重新部署更新WebService 的應(yīng)用程序。你不需要更新WebClient』缧玻現(xiàn)在WebClient的AJAX請(qǐng)求應(yīng)該成功瞧预。GET、PUT和POST方法都是允許的仅政。
CORS工作原理
本節(jié)描述了在Http協(xié)議標(biāo)準(zhǔn)上http跨域請(qǐng)求中究竟發(fā)生了什么垢油。重要的是要理解CORS是如何工作的,這樣你就可以正確配置[EnableCors]屬性,和如果CORS不像您預(yù)期的那樣工作怎樣排除錯(cuò)誤。
CORS為了允許使跨源請(qǐng)求引入了幾個(gè)新的HTTP頭圆丹。如果瀏覽器支持CORS,它自動(dòng)設(shè)置這些請(qǐng)求頭滩愁,你不需要在你的JavaScript代碼做任何修改。
這是一個(gè)跨域請(qǐng)求的例子辫封。Origin請(qǐng)求頭提供了產(chǎn)生這個(gè)跨域請(qǐng)求的網(wǎng)站域名硝枉。
如果服務(wù)器允許這個(gè)跨域請(qǐng)求,響應(yīng)報(bào)文中自動(dòng)設(shè)置Access-Control-Allow-Origin頭倦微。這個(gè)頭的值匹配請(qǐng)求報(bào)文中Origin頭的值,或者是通配符“*”,這意味著任何起源是被允許的妻味。
如果響應(yīng)不包括Access-Control-Allow-Origin頭,這是AJAX請(qǐng)求失敗。具體來(lái)說(shuō)是瀏覽器不允許請(qǐng)求欣福。即使服務(wù)器返回一個(gè)成功的響應(yīng),瀏覽器響應(yīng)的結(jié)果不可用于客戶(hù)端應(yīng)用程序责球。
CORS 預(yù)檢請(qǐng)求preflight request
對(duì)于一些CORS請(qǐng)求,瀏覽器會(huì)發(fā)送一個(gè)額外的請(qǐng)求,稱(chēng)為預(yù)檢請(qǐng)求“preflight request”,在發(fā)送的實(shí)際請(qǐng)求的資源之前。
瀏覽器可以跳過(guò)preflight request如果下列條件屬實(shí):
- 1 請(qǐng)求的方法是GET, HEAD, or POST,等
- 2 應(yīng)用程序不設(shè)置任何請(qǐng)求頭除了Accept, Accept-Language, Content-Language, Content-Type, or Last-Event-ID等
- 3 content - type報(bào)頭(如果設(shè)置)是下列之一:
application/x-www-form-urlencoded
multipart/form-data
text/plain
這個(gè)請(qǐng)求頭的規(guī)范適用于當(dāng)應(yīng)用程序調(diào)用setRequestHeade XMLHttpRequest對(duì)象時(shí)發(fā)起的請(qǐng)求頭。 規(guī)范并不適用于瀏覽器的請(qǐng)求頭可以設(shè)置,如用戶(hù)代理,主機(jī),或內(nèi)容長(zhǎng)度雏逾。
下面是preflight request的一個(gè)例子
pre-flight請(qǐng)求使用HTTP OPTIONS方法嘉裤。它包括兩個(gè)特殊的請(qǐng)求頭:
Access-Control-Request-Method:HTTP方法將被用于實(shí)際的請(qǐng)求。
Access-Control-Request-Headers:應(yīng)用程序設(shè)置的實(shí)際的請(qǐng)求頭的列表栖博。(同樣,這并不包括瀏覽器設(shè)置的請(qǐng)求頭屑宠。)
這里有一個(gè)響應(yīng)報(bào)文例子,假設(shè)服務(wù)器允許請(qǐng)求:
響應(yīng)包含一個(gè)Access-Control-Allow-Methods列出允許的方法、和可選一個(gè)Access-Control-Allow-Headers頭列表允許的頭笛匙。如果preflight請(qǐng)求成功,瀏覽器發(fā)送實(shí)際的請(qǐng)求,如前所述。
[EnableCors]設(shè)置
您可以啟用 CORS在每一個(gè) action,controller,或Web API全局控制器中犀变。
- 1 action設(shè)置
在action上允許跨域妹孙,設(shè)置[EnableCors]屬性的action方法。下面的例子使GetItemmethod 單獨(dú)允許跨域
public class ItemsController : ApiController
{
public HttpResponseMessage GetAll() { ... }
[EnableCors(origins: "http://www.example.com", headers: "*", methods: "*")]
public HttpResponseMessage GetItem(int id) { ... }
public HttpResponseMessage Post() { ... }
public HttpResponseMessage PutItem(int id) { ... }
}
- 2 controller設(shè)置
如果您設(shè)置EnableCors在控制器,它適用于該控制器上的所有的action获枝。如果想對(duì)某一個(gè)action禁用跨域蠢正,請(qǐng)使用[DisableCors]特性。下面的例子除了PutItem action 其他action都支持跨域省店。
[EnableCors(origins: "http://www.example.com", headers: "*", methods: "*")]
public class ItemsController : ApiController
{
public HttpResponseMessage GetAll() { ... }
public HttpResponseMessage GetItem(int id) { ... }
public HttpResponseMessage Post() { ... }
[DisableCors]
public HttpResponseMessage PutItem(int id) { ... }
}
- 3 全局設(shè)置
在應(yīng)用程序中為所有Web API 控制器允許跨域,將一個(gè)EnableCorsAttribute實(shí)例傳遞給EnableCors方法:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
var cors = new EnableCorsAttribute("www.example.com", "*", "*");
config.EnableCors(cors);
// ...
}
}
如果你在多個(gè)范圍內(nèi)多個(gè)設(shè)置[EnableCors]嚣崭、優(yōu)先順序是:
1Action
2Controller
3Global
[EnableCors]參數(shù)origin介紹
[EnableCors]的origins 參數(shù)指定了哪一個(gè)請(qǐng)求起源是允許訪問(wèn)的。允許的值之間是一個(gè)以逗號(hào)分隔的懦傍。
[EnableCors(origins: "http://www.contoso.com,http://www.example.com",
headers: "*", methods: "*")]
[EnableCors]參數(shù)methods介紹
[EnableCors]特性的methods雹舀,指定了哪一個(gè)HTTP方法可以訪問(wèn)資源。為了使所有方法都可以訪問(wèn)粗俱,使用通配符“ * ”说榆。下面是一個(gè)只允許GET和POST方法的請(qǐng)求示例:
[EnableCors(origins: "http://www.example.com", headers: "*", methods: "get,post")]
public class TestController : ApiController
{
public HttpResponseMessage Get() { ... }
public HttpResponseMessage Post() { ... }
public HttpResponseMessage Put() { ... }
}
[EnableCors]參數(shù)headers介紹
[EnableCors]特性的headers,指定了哪一個(gè)HTTP請(qǐng)求頭可以訪問(wèn)資源寸认。為了使任何請(qǐng)求頭都可以訪問(wèn)签财,使用通配符“ * ”,多個(gè)允許的headers之間使用一個(gè)逗號(hào)來(lái)分隔偏塞。
[EnableCors(origins: "http://example.com",
headers: "accept,content-type,origin,x-my-header", methods: "*")]
設(shè)置允許響應(yīng)標(biāo)頭
默認(rèn)情況下,瀏覽器不公開(kāi)所有的應(yīng)用程序響應(yīng)標(biāo)頭唱蒸。可用的響應(yīng)頭默認(rèn)情況下是:
Cache-Control
Content-Language
Content-Type
Expires
Last-Modified
Pragma
CORS 規(guī)定了調(diào)用這些簡(jiǎn)單的響應(yīng)頭灸叼。于應(yīng)用程序中使用其他頭文件,請(qǐng)?jiān)O(shè)置[EnableCors]的exposedHeaders參數(shù)
在接下來(lái)的例子中,控制器的Get方法設(shè)置一個(gè)自定義標(biāo)頭命名為“X-Custom-Header”神汹。默認(rèn)情況下,瀏覽器不會(huì)在跨源的請(qǐng)求中暴露這個(gè)自定義標(biāo)頭。為了使自定義標(biāo)頭有效,使 exposedHeaders 參數(shù)的值為X-Custom-Header”古今。
[EnableCors(origins: "*", headers: "*", methods: "*", exposedHeaders: "X-Custom-Header")]
public class TestController : ApiController
{
public HttpResponseMessage Get()
{
var resp = new HttpResponseMessage()
{
Content = new StringContent("GET: Test message")
};
resp.Headers.Add("X-Custom-Header", "hello");
return resp;
}
}
在跨域請(qǐng)求中通過(guò)證書(shū)請(qǐng)求
跨域請(qǐng)求中使用證書(shū)需要特殊處理慎冤。默認(rèn)情況下,瀏覽器不發(fā)送任何證書(shū)憑證與跨源的要求。憑證不但包括cookies還包括HTTP身份驗(yàn)證方案沧卢。為了在跨源請(qǐng)求發(fā)送憑證,客戶(hù)端必須設(shè)置XMLHttpRequest.withCredentials為true蚁堤。
- c#
var xhr = new XMLHttpRequest();
xhr.open('get', 'http://www.example.com/api/test');
xhr.withCredentials = true;
- JS
$.ajax({
type: 'get',
url: 'http://www.example.com/api/test',
xhrFields: {
withCredentials: true
}
此外,服務(wù)器必須允許憑據(jù)。在Web API,(EnableCors)特性的允許跨源憑證SupportsCredentials參數(shù)設(shè)置為true
[EnableCors(origins: "http://myclient.azurewebsites.net", headers: "*",
methods: "*", SupportsCredentials = true)]
如果這個(gè)屬性是true,HTTP響應(yīng)將包含Access-Control-Allow-Credentials頭。這個(gè)頭告訴瀏覽器跨源請(qǐng)求的服務(wù)器允許憑據(jù)披诗。
如果瀏覽器發(fā)送證書(shū),但是響應(yīng)不包括一個(gè)有效的Access-Control-Allow-Credentials頭,瀏覽器不會(huì)公開(kāi)響應(yīng)應(yīng)用程序,并且AJAX請(qǐng)求失敗撬即。
務(wù)必小心將SupportsCredentials設(shè)置為true,因?yàn)檫@意味在一個(gè)網(wǎng)站在另一個(gè)域可以發(fā)送一個(gè)登錄的用戶(hù)的憑證代表用戶(hù)的Web API,。CORS還規(guī)定,設(shè)置“*”的Origin是無(wú)效的呈队,在SupportsCredentials是true的情況下剥槐。
自定義[EnableCors]特性
[EnableCors]特性實(shí)現(xiàn)了ICorsPolicyProvider接口。您可以提供自己的實(shí)現(xiàn)通過(guò)創(chuàng)建一個(gè)類(lèi),它來(lái)繼承Attribute和實(shí)現(xiàn)了ICorsProlicyProvider接口宪摧。
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false)]
public class MyCorsPolicyAttribute : Attribute, ICorsPolicyProvider
{
private CorsPolicy _policy;
public MyCorsPolicyAttribute()
{
// Create a CORS policy.
_policy = new CorsPolicy
{
AllowAnyMethod = true,
AllowAnyHeader = true
};
// Add allowed origins.
_policy.Origins.Add("http://myclient.azurewebsites.net");
_policy.Origins.Add("http://www.contoso.com");
}
public Task<CorsPolicy> GetCorsPolicyAsync(HttpRequestMessage request)
{
return Task.FromResult(_policy);
}
}
現(xiàn)在你可以在你想要允許跨域的任何地方使用你剛才自定義的[EnableCors].特性
[MyCorsPolicy]
public class TestController : ApiController
{
.. //
}