跨域资源共享(CORS)在ASP.NET Web API中是如何实现的?
在《通过扩展让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
跨域资源共享(CORS)在ASP.NET Web API中是如何实现的?的更多相关文章
- 在ASP.NET Web API中实现CORS(跨域资源共享)
默认情况下,是不允许网页从不同的域访问服务器资源的,访问遵循"同源"策略的原则. 会遇到如下的报错: XMLHttpRequest cannot load http://local ...
- JavaScript跨域调用、JSONP、CORS与ASP.NET Web API[共8篇]
[第1篇] 同源策略与JSONP 浏览器是访问Internet的工具,也是客户端应用的宿主,它为客户端应用提供一个寄宿和运行的环境.而这里所说的应用,基本是指在浏览器中执行的客户端JavaScript ...
- 跨域资源共享(CORS)问题解决方案
CORS:Cross-Origin Resource Sharing(跨域资源共享) CORS被浏览器支持的版本情况如下:Chrome 3+.IE 8+.Firefox 3.5+.Opera 12+. ...
- 跨域资源共享CORS与JSONP
同源策略限制: 同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能,如果没有同源策略,攻击者可以通过JavaScript获取你的邮件以及其他敏感信息,比如说 ...
- 网络编程-跨域资源共享 CORS
目录 1.什么是同源策略? 2.跨域资源共享 CORS 3.预检请求 4.CORS相关字段 5.Golang实现跨域 6.参考资料 1.什么是同源策略? 如果两个 URL 的 protocol.por ...
- 跨域解决方案 - 跨域资源共享cors
目录 1. cors 介绍 2. 原理 3. cors 解决跨域 4. 自定义HTTP 头部字段解决跨域 5. 代码演示 5. 参考链接 1. cors 介绍 cors 说的是一个机制,其实相当于一个 ...
- VUE SpringCloud 跨域资源共享 CORS 详解
VUE SpringCloud 跨域资源共享 CORS 详解 作者: 张艳涛 日期: 2020年7月28日 本篇文章主要参考:阮一峰的网络日志 » 首页 » 档案 --跨域资源共享 CORS 详解 ...
- 【ASP.NET Web API教程】5.5 ASP.NET Web API中的HTTP Cookie
原文:[ASP.NET Web API教程]5.5 ASP.NET Web API中的HTTP Cookie 5.5 HTTP Cookies in ASP.NET Web API 5.5 ASP.N ...
- ASP.NET Web API中的JSON和XML序列化
ASP.NET Web API中的JSON和XML序列化 前言 阅读本文之前,您也可以到Asp.Net Web API 2 系列导航进行查看 http://www.cnblogs.com/aehyok ...
随机推荐
- 安装zeppelin
安装zeppelin 1.默认安装好spark集群 2.安装zeppelin 1.解压安装包 tar zxvf zeppelin-0.5.5-incubating-bin-all.tgz 2.配置环境 ...
- MIT 6.828 JOS学习笔记18. Lab 3.2 Part B: Page Faults, Breakpoints Exceptions, and System Calls
现在你的操作系统内核已经具备一定的异常处理能力了,在这部分实验中,我们将会进一步完善它,使它能够处理不同类型的中断/异常. Handling Page Fault 缺页中断是一个非常重要的中断,因为我 ...
- Tray - a SharedPreferences replacement for Android
一个代替SharedPreferences的开源库, no Editor, no commit() no apply(),因此不存在UI卡顿现象,并且支持多线程,在一个线程中存另一个线程中取数据. h ...
- nodejs复习02
process 这个模块是单线程的,无法完全利用多核CPU 基本信息 //程序目录 process.cwd(); //应用程序当前目录 process.chdir('/home'); //改变应用程序 ...
- Java对象大小计算
这篇说说如何计算Java对象大小的方法.之前在聊聊高并发(四)Java对象的表示模型和运行时内存表示 这篇中已经说了Java对象的内存表示模型是Oop-Klass模型. 普通对象的结构如下,按64位机 ...
- jQuery页面加载初始化的3种方法
jQuery 页面加载初始化的方法有3种 ,页面在加载的时候都会执行脚本,应该没什么区别,主要看习惯吧,本人觉得第二种方法最好,比较简洁. 第一种: $(document).ready(functio ...
- 图论 - Travel
Travel The country frog lives in has nn towns which are conveniently numbered by 1,2,…,n. Among n(n− ...
- 2016 windows安装phing:安装成功
21:39 2016/7/212016 windows安装phing:安装成功注意:出现错误时就去更新pear:参见:http://www.cnblogs.com/pinnasky/archive/2 ...
- App Framework $.ui.loadContent 参数解释
在使用 app Framework 的 $.ui.loadContent(target,newTab,goBack,transition);时 对 newTab goback两个参数一直不得其解.通过 ...
- Android 无标题 全屏设置
标题栏和状态栏 Android程序默认情况下是包含状态栏和标题栏的. 在Eclipse中新建一个Android程序,运行后显示如下: 图中标出了状态栏(显示时间.电池电量.网络等)和标题栏(显示应用的 ...