在《通過擴展讓ASP.NET Web API支持W3C的CORS規范》中,我們通過自定義的HttpMessageHandler自行為ASP.NET Web API實現了針對CORS的支持,實際上ASP.NET Web API自身也是這么做的,該自定義HttpMessageHandler就是System.Web.Http.Cors.CorsMessageHandler。
1: public class CorsMessageHandler : DelegatingHandler
2: {
3: public CorsMessageHandler(HttpConfiguration httpConfiguration);
4: protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken);
5:
6: public virtual Task<HttpResponseMessage> HandleCorsPreflightRequestAsync(HttpRequestMessage request, CorsRequestContext corsRequestContext, CancellationToken cancellationToken);
7: public virtual Task<HttpResponseMessage> HandleCorsRequestAsync(HttpRequestMessage request, CorsRequestContext corsRequestContext, CancellationToken cancellationToken);
8: }
CorsMessageHandler的核心功能在于:提取預定義的CORS授權策略并對當前請求實施授權檢驗,并根據授權檢驗的結果為現有的響應(針對簡單跨域資源請求和繼預檢請求之后發送的真正跨域資源請求)或者新創建的響應(針對預檢請求)添加相應的CORS報頭。如上面的代碼片斷所示,CorsMessageHandler定義了HandleCorsPreflightRequestAsync和HandleCorsRequestAsync虛方法,它們分別實現針對預檢請求和非預檢請求的CORS授權檢驗。
在實現的SendAsync方法中,當CorsRequestContext根據表示當前請求的HttpRequestMessage對象創建之后,會根據其IsPreflight屬性選擇調用方法HandleCorsPreflightRequestAsync或者HandleCorsRequestAsync。
CORS授權檢驗
實現在CorsMessageHandler中的具體CORS授權檢驗流程基本上體現在右圖中。它首先根據表示當前請求的HttpRequestMessage對象創建CorsRequestContext對象。然后利用注冊的CorsProviderFactory得到對應的CorsProvider對象,并利用后者得到針對當前請求的資源授權策略,這是一個CorsPolicy對象。
接下來,CorsMessageHandler會獲取注冊的CorsEngine。此前得到的CorsRequestContext和CorsPolicy對象會作為參數調用CorsEngine的EvaluatePolicy方法,CORS資源授權檢驗由此開始。授權檢驗結束之后,CorsMessageHandler會得到表示檢驗結果的CorsResult對象。
對于預檢請求,CorsMessageHandler會直接創建HttpResponseMessage對象予以響應。具體來說,如果預檢請求通過了授權檢驗,一個狀態為“200, OK”的HttpResponseMessage會被創建出來,通過CorsResult得到CORS響應報頭會被添加到這個HttpResponseMessage對象的報頭集合中。如果授權檢驗失敗,創建的HttpResponseMessage具有的狀態為“400, Bad Request”,CorsResult攜帶的錯誤響應會作為響應的主體內容。
對于非預檢請求,它會將當前請求傳遞給消息處理管道的后續部分進行進一步處理,并最終得到表示響應消息的HttpResponseMessage。只有在請求通過授權檢查的情況下,由CorsResult得到的CORS響應報頭才會被添加到此HttpResponseMessage的報頭集合中。
實例演示:創建MyCorsMessageHandler模擬具體采用的授權檢驗
為了讓讀者朋友們對實現在CorsMessageHandler中的具體CORS資源授權流程具有更加深刻的認識,我們現在將這樣的授權檢驗邏輯實現在一個自定義的HttpMessageHandler中。為此我們定義了如下一個MyCorsMessageHandler類型,由于它僅僅用于模擬CorsMessageHandler大體實現邏輯,所以我們會忽略很多細節上(比如異常處理)的代碼。
1: public class MyCorsMessageHandler: DelegatingHandler
2: {
3: protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
4: {
5: //根據當前請求創建CorsRequestContext
6: CorsRequestContext context = request.CreateCorsRequestContext();
7:
8: //針對非預檢請求:將請求傳遞給消息處理管道后續部分繼續處理,并得到響應
9: HttpResponseMessage response = null;
10: if (!context.IsPreflight)
11: {
12: response = await base.SendAsync(request, cancellationToken);
13: }
14:
15: //利用注冊的CorsPolicyProviderFactory得到對應的CorsPolicyProvider
16: //借助于CorsPolicyProvider得到表示CORS資源授權策略的CorsPolicy
17: HttpConfiguration configuration = request.GetConfiguration();
18: CorsPolicy policy = await configuration.GetCorsPolicyProviderFactory().GetCorsPolicyProvider(request).GetCorsPolicyAsync(request,cancellationToken);
19:
20: //獲取注冊的CorsEngine
21: //利用CorsEngine對請求實施CORS資源授權檢驗,并得到表示檢驗結果的CorsResult對象
22: ICorsEngine engine = configuration.GetCorsEngine();
23: CorsResult result = engine.EvaluatePolicy(context, policy);
24:
25: //針對預檢請求
26: //如果請求通過授權檢驗,返回一個狀態為“200, OK”的響應并添加CORS報頭
27: //如果授權檢驗失敗,返回一個狀態為“400, Bad Request”的響應并指定授權失敗原因
28: if (context.IsPreflight)
29: {
30: if (result.IsValid)
31: {
32: response = new HttpResponseMessage(HttpStatusCode.OK);
33: response.AddCorsHeaders(result);
34: }
35: else
36: {
37: response = request.CreateErrorResponse(HttpStatusCode.BadRequest,string.Join(" |", result.ErrorMessages.ToArray()));
38: }
39: }
40: //針對非預檢請求
41: //CORS報頭只有在通過授權檢驗情況下才會被添加到響應報頭集合中
42: else if (result.IsValid)
43: {
44: response.AddCorsHeaders(result);
45: }
46: return response;
47: }
48: }
如上面的代碼片斷所示,我們首選在實現的SendAsync方法中調用自定義的擴展方法CreateCorsRequestContext根據表示當前請求的HttpRequestMessge對象創建出表示針對CORS的跨域資源請求上下文的CorsRequestContext對象。
然后我們根據CorsRequestContext的IsPreflight屬性判斷當前是否是一個預檢請求。對于預檢請求,我們會直接調用基類的同名方法將請求傳遞給消息處理管道的后續環節作進一步處理,并最終得到表示響應的HttpResponse對象。
我們接下來從表示當前請求的HttpRequestMessge對象中直接獲取當前HttpConfiguration對象,并調用擴展方法GetCorsPolicyProviderFactory得到注冊在它上面的CorsPolicyProviderFactory,進而得到由它提供的GetCorsPolicyProvider。通過調用此GetCorsPolicyProvider的方法GetCorsPolicyAsync,我們會得到目標Action方法采用的CORS資源授權策略,這是一個CorsPolicy對象。
在這之后,我們調用HttpConfiguration對象的另一個擴展方法GetCorsEngine得到注冊其上的CorsEngine,并將此前得到的CorsRequestContext和CorsPolicy對象作為參數調用它的方法EvaluatePolicy由此開始針對當前請求的CORS資源授權檢驗,并最終得到表示檢驗結果的CorsResult。
通過CorsResult的IsValid屬性表示當前請求是否通過CORS資源授權檢驗。對于預檢請求,在請求通過授權檢驗的情況下,我們會創建一個狀態為“200, OK”的HttpResponseMessage作為最終的響應,在返回之前我們調用自定義的擴展方法AddCorsHeaders將從CorsResult得到的CORS響應報頭添加到此HttpResponseMessage的報頭集合中。如果請求沒有通過授權檢驗,我們會返回一個狀態為“400, Bad Request”的響應,通過CorsResult的ErrorMessage屬性提取的錯誤消息(表示授權失敗的原因)會作為響應的主體內容。
對于非預檢請求來說,只有在它通過了資源授權檢驗的情況下,我們才會調用擴展方法AddCorsHeaders將從CorsResult得到的CORS報頭添加響應的報頭集合中。換句話說,對于未取得授權的非預檢跨域資源請求,MyCorsMessageHandler沒有對響應作任何的改變。
如下所示的是分別針對HttpRequestMessage和HttpResponseMessage定義的兩個擴展方法,其中CreateCorsRequestContext方法根據HttpRequestMessage創建CorsRequestContext對象,而AddCorsHeaders方法則將從CorsResult中獲取的CORS響應報頭添加到指定的HttpResponseMessage中。
1: public static class CorsExtensions
2: {
3: public static CorsRequestContext CreateCorsRequestContext(this HttpRequestMessage request)
4: {
5: CorsRequestContext context = new CorsRequestContext
6: {
7: RequestUri = request.RequestUri,
8: HttpMethod = request.Method.Method,
9: Host = request.Headers.Host,
10: Origin = request.GetHeader("Origin"),
11: AccessControlRequestMethod = request.GetHeader("Access-Control-Request-Method")
12: };
13:
14: string requestHeaders = request.GetHeader("Access-Control-Request-Headers");
15: if (!string.IsNullOrEmpty(requestHeaders))
16: {
17: Array.ForEach(requestHeaders.Split(','), header => context.AccessControlRequestHeaders.Add(header.Trim()));
18: }
19: return context;
20: }
21:
22: public static void AddCorsHeaders(this HttpResponseMessage response, CorsResult result)
23: {
24: foreach (var item in result.ToResponseHeaders())
25: {
26: response.Headers.TryAddWithoutValidation(item.Key, item.Value);
27: }
28: }
29:
30: private static string GetHeader(this HttpRequestMessage request, string name)
31: {
32: IEnumerable<string> headerValues;
33: if (request.Headers.TryGetValues(name, out headerValues))
34: {
35: return headerValues.FirstOrDefault();
36: }
37: return null;
38: }
39: }
為了驗證我們這個用于模擬CorsMessageHandler的自定義HttpMessageHandler是否能夠真正為ASP.NET Web API提供針對CORS的支持,我們直接將其應用到《同源策略與JSONP》創建的演示實例中。我們通過上面介紹的方式為WebApi應用安裝“Microsoft ASP.NET Web API 2 Cross-Origin Support”這個NuGet包后,將EnableCorsAttribute特性應用到定義在ContactsController上并作如下的設置。
1: [EnableCors("http://localhost:9527","*","*")]
2: public class ContactsController : ApiController
3: {
4: public IHttpActionResult GetAllContacts()
5: {
6: //省略實現
7: }
8: }
在Global.asax中,我們并不調用當前HttpConfiguration的EnableCors方法開啟ASP.NET Web API針對CORS的支持,而是采用如下的方式將創建的CorsMessageHandler對象添加到消息處理管道中。如果現在運行ASP.NET MVC程序,通過調用Web API以跨域Ajax請求得到的聯系人列表依然會顯示在瀏覽器上。
1: public class WebApiApplication : System.Web.HttpApplication
2: {
3: protected void Application_Start()
4: {
5: GlobalConfiguration.Configuration.MessageHandlers.Add(new MyCorsMessageHandler());
6: //其他操作
7: }
8: }
HttpConfiguration的EnableCors方法
通過上面的介紹我們知道針對ASP.NET Web API的CORS編程首先需要做的就是在程序啟動之前調用當前HttpConfiguration的擴展方法EnableCors開啟對CORS的支持,那么該方法中具體實現了怎樣操作呢?由于ASP.NET Web API針對CORS的支持最終是通過CorsMesssageHandler這個自定義的HttpMessageHandler來實現的,所以對于HttpConfiguration的擴展方法EnableCors來說,其核心操作就是對CorsMesssageHandler予以注冊。
1: public static class CorsHttpConfigurationExtensions
2: {
3: public static void EnableCors(this HttpConfiguration httpConfiguration);
4: public static void EnableCors(this HttpConfiguration httpConfiguration, ICorsPolicyProvider defaultPolicyProvider);
5: }
6:
7: public class AttributeBasedPolicyProviderFactory : ICorsPolicyProviderFactory
8: {
9: //其他成員
10: public ICorsPolicyProvider DefaultPolicyProvider { get; set; }
11: }
如上面的代碼片斷所示,HttpConfiguration具有兩個重載的EnableCors方法。其中一個可以指定一個默認的CorsPolicyProvider,如果調用此方法并指定一個具體的CorsPolicyProvider對象,一個AttributeBasedPolicyProviderFactory對象會被創建出來并注冊到HttpConfiguration上。而指定的CorsPolicyProvider實際上會作為AttributeBasedPolicyProviderFactory對象的DefaultPolicyProvider屬性。
CORS系列文章
[1] 同源策略與JSONP
[2] 利用擴展讓ASP.NET Web API支持JSONP
[3] W3C的CORS規范
[4] 利用擴展讓ASP.NET Web API支持CORS
[5] ASP.NET Web API自身對CORS的支持: 從實例開始
[6] ASP.NET Web API自身對CORS的支持: CORS授權策略的定義和提供
[7] ASP.NET Web API自身對CORS的支持: CORS授權檢驗的實施
[8] ASP.NET Web API自身對CORS的支持: CorsMessageHandler
文章列表