跨域资源共享(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 ...
随机推荐
- ecplise + hadoop 调试环境搭建
1.需要安装包 1.1 hadoop源码包(hadoop-2.5.2-src.tar.gz) 1.2 hadoop 2X插件(hadoop2x-eclipse-plugin-master.zip) 1 ...
- 命名规范(数据库,c#)
Ⅰ. Naming Conventions 1. Table Naming Rule 1a ( Prefix) 新加的Table要加上適當的前缀 e.g. mUsr, eTxn, tmpRolle ...
- Effective C++ 笔记1
条款1:视C++为一个语言联邦 1.C.Object-Oriented C++.Template C++ .STL 组成了C++,高效编程取决你使用C++的哪一部分 条款2:尽量用const ,enu ...
- 动态生成一个设定好特殊样式的Tlabel,快速生成代码
动态生成一个设定好特殊样式的Tlabel,快速生成代码: 1.自己先在可视化界面设定一个Label,像这样: 2.选择label,快捷键ctrl+C 复制,粘贴带代码编辑器去,会生成一段这样的窗体代码 ...
- DFS序+线段树+bitset CF 620E New Year Tree(圣诞树)
题目链接 题意: 一棵以1为根的树,树上每个节点有颜色标记(<=60),有两种操作: 1. 可以把某个节点的子树的节点(包括本身)都改成某种颜色 2. 查询某个节点的子树上(包括本身)有多少个不 ...
- mac apache 2.4的配置
开启vhost 文件 命令行输入 vim /etc/apache2/httpd.conf 找到把前边的# include /private/etc/apache2/extra/httpd-vhost. ...
- C#对象序列化与反序列化zz
C#对象序列化与反序列化(转载自:http://www.cnblogs.com/LiZhiW/p/3622365.html) 1. 对象序列化的介绍........................ ...
- 采用Lambda表达式快速实现实体模型对象转换到DTO
在项目中,采用code first时建立的模型对象不能直接用于数据传输,需要从新根据需求建立Dto对象 为什么需要建立Dto对象呢? DTO即数据传输对象.之前不明白有些框架中为什么要专门定义DTO来 ...
- Python调用HTTP接口并传递cookie
#get接口调用 import urllib import urllib2 get_url = "http://10.10.3.63/test?id=123&name=nba&quo ...
- 【历史】JavaScript和Java没啥关系!————JavaScript简史
文章的开始先上张图: 图片拍摄自北京图书大厦,代表着现在国内应该是绝大部分书店的现状--Javascript书籍放在Java类当中.甚至很多业内人也一直认为Javascript是Java语言在浏览器内 ...