ASP.NET Web API自身对CORS的支持: EnableCorsAttribute特性背后的故事

简介:

从编程的角度来讲,ASP.NET Web API针对CORS的实现仅仅涉及到HttpConfiguration的扩展方法EnableCors和EnableCorsAttribute特性。但是整个CORS体系不限于此,在它们背后隐藏着一系列的类型,我们将会利用本章余下的内容对此作全面讲述,今天我们就来讨论一下用于定义CORS授权策略的EnableCorsAttribute特性背后的故事。

目录
一、CorsPolicy
二、CorsPolicyProvider
三、CorsPolicyProviderFactory
四、CorsPolicyProviderFactory的注册
五、总结

一、CorsPolicy

通过将EnableCorsAttribute特性应用到HttpController类型或者定义其中的某个Action方法上,我们可以为提供的资源定义相应的授权策略。ASP.NET Web API最终会利用这些策略对请求(包括预检请求)进行解析并生成相应的CORS响应报头。在ASP.NET Web API的应用编程接口中,CORS授权策略通过CorsPolicy类型表示。

通过《W3C的CORS规范》的介绍,我们知道针对跨域资源的授权策略不仅仅要求请求的源站点值得信任,还涉及到对请求采用的HTTP方法、携带的自定义报头和用户凭证的要求,以及针对自定义响应报头的授权等。除此之外,为了避免频繁浏览器频繁地发送预检请求,它可以将响应的结果进行缓存,而这又涉及到对缓存过期时间的控制。总得来说,这些授权策略体现在如下6个CORS响应报头上。

  • Access-Control-Allow-Origin
  • Access-Control-Expose-Headers
  • Access-Control-Allow-Methods
  • Access-Control-Allow-Headers
  • Access-Control-Max-Age
  • Access-Control-Allow-Credentials

在ASP.NET Web API的应用编程接口中,围绕着这6个CORS响应报头的授权策略通过类型System.Web.Cors.CorsPolicy来表示。CorsPolicy具有如下6个属性正好与上面这6个CORS响应报头一一对应。

   1: public class CorsPolicy
   2: {
   3:     //其他成员
   4:     public IList<string>     Origins { get; }
   5:     public IList<string>     ExposedHeaders { get; }
   6:     public IList<string>     Headers { get; }
   7:     public IList<string>     Methods { get;; }
   8:     public long?             PreflightMaxAge { get; set; }
   9:     public bool              SupportsCredentials { get; set; }
  10: }

除了上述这6个属性之外,CorsPolicy还具有如下3个布尔类型的属性(AllowAnyOrigin、AllowAnyHeader和AllowAnyMethod),它们分别表示是否支持所有的源站点、自定义请求报头和HTTP方法。

   1: public class CorsPolicy
   2: {
   3:     //其他成员
   4:     public bool AllowAnyOrigin { get; set; }
   5:     public bool AllowAnyHeader { get; set; }
   6:     public bool AllowAnyMethod { get; set; }
   7: }

 

二、CorsPolicyProvider

作为跨域资源请求进行授权检查的依据,同时用于生成相应的CORS报头的CorsPolicy对象通过另一个名为CorsPolicyProvider的对象来提供,所有的CorsPolicyProvider类型均实现了的接口System.Web.Http.Cors.ICorsPolicyProvider。如下面的代码片断所示,该接口具有的唯一方法GetCorsPolicyAsync会根据代表但前请求的HttpRequestMessage对象得到表示CORS授权策略的CorsPolicy对象。

   1: public interface ICorsPolicyProvider
   2: {
   3:     Task<CorsPolicy> GetCorsPolicyAsync(HttpRequestMessage request, CancellationToken cancellationToken);
   4: }

实际上我们通过应用在目标HttpController类型或者定义其中的Action方法上用于定义CORS授权策略的System.Web.Http.Cors.EnableCorsAttribute就是ICorsPolicyProvider接口的实现者之一。如下面的代码片断所示,EnableCorsAttribute同样具有6个针对CORS响应报头的属性。在实现的GetCorsPolicyAsync方法中,它就是通过这6个属性对返回的CorsPolicy对象进行初始化。

   1: [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple=false)]
   2: public sealed class EnableCorsAttribute : Attribute, ICorsPolicyProvider
   3: {    
   4:     public EnableCorsAttribute(string origins, string headers, string methods);
   5:     public EnableCorsAttribute(string origins, string headers, string methods,string exposedHeaders);
   6:  
   7:     public Task<CorsPolicy> GetCorsPolicyAsync(HttpRequestMessage request, CancellationToken cancellationToken);
   8:     
   9:     public IList<string> Origins { get; }
  10:     public IList<string> ExposedHeaders { get; }
  11:     public IList<string> Headers { get; }
  12:     public IList<string> Methods { get; }
  13:     public long          PreflightMaxAge { get; set; }
  14:     public bool          SupportsCredentials { get; set; }
  15: }

授权的源站点和允许的自定义请求报头和HTTP方法,以及暴露给客户端JavaScript程序的自定义响应报头均可以直接通过构造函数参数来指定。对于这4个参数,我们可以指定某个单一的值(比如origin="http://www.artech.com"),也可以指定一个通过逗号分割的列表(比如origin="http://www.artech.com, http://www.jinnan.me")。除了exposedHeaders之外,我们还可以指定“*”作为其参数值,意味着不对此作任何限制,它们会控制生成CorsPolicy对象的3个对应布尔类型属性值(AllowAnyOrigin、AllowAnyHeader和AllowAnyMethod)。

除了EnableCorsAttribute特性之外,在“System.Web.Http.Cors”命名空间下还定义着另一个与之相对的特性DisableCorsAttribute。顾名思义,如果DisableCorsAttribute特性被应用到某个HttpController类型或者定义其中的某个Action方法上,意味着目标HttpController或者Action不支持跨域资源共享。如下面的代码片断所示,在实现的GetCorsPolicyAsync方法中,并没有一个具体的CorsPolicy返回。

   1: [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple=false)]
   2: public sealed class DisableCorsAttribute : Attribute, ICorsPolicyProvider
   3: {
   4:     public Task<CorsPolicy> GetCorsPolicyAsync(HttpRequestMessage request, CancellationToken cancellationToken)
   5:     {
   6:         return Task.FromResult<CorsPolicy>(null);
   7:     }
   8: }

由于应用在Action方法上的CorsPolicyProvider特性比应用在HttpController类型上的特性具有更好的选择优先级,所以对于一个定义了众多Action方法的HttpController类型来说,如果绝大部分Action方法均需要提供跨域资源共享的支持并具有相同的资源授权策略,可以直接在HttpController类型上应用EnableCorsAttribute特性并作相应的设置。对于不需要支持跨域资源共享的Action来说,直接在对应的方法上应用DisableCorsAttribute特性即可。如果某个Action具有特殊的授权需求,可以通过应用的EnableCorsAttribute特性作针对性设置。反之亦然。

三、CorsPolicyProviderFactory

CorsPolicyProvider用于提供用于描述CORS授权策略的CorsPolicy对象,其自身又通过对应的CorsPolicyProviderFactory来创建,所有的CorsPolicyProviderFactory类型均实现了接口System.Web.Http.Cors.ICorsPolicyProviderFactory。如下面的代码片断所示,该接口具有的唯一方法GetCorsPolicyProvider会根据代表当前请求的HttpRequestMessage对象来提供对应的CorsPolicyProvider对象。

   1: public interface ICorsPolicyProviderFactory
   2: {
   3:     ICorsPolicyProvider GetCorsPolicyProvider(HttpRequestMessage request);
   4: }

由于提供的两个具体CorsPolicyProvider类型(EnableCorsAttribute和DisableCorsAttribute)都是特性,所以ASP.NET Web API定义了如下一个AttributeBasedPolicyProviderFactory类型的CorsPolicyProviderFactory以解析特性的方式提供对应的CorsPolicyProvider。

   1: public class AttributeBasedPolicyProviderFactory : ICorsPolicyProviderFactory
   2: {    
   3:     public virtual ICorsPolicyProvider GetCorsPolicyProvider(HttpRequestMessage request);
   4:     public ICorsPolicyProvider DefaultPolicyProvider { get; set; }
   5: }

实现在GetCorsPolicyProvider方法中的CorsPolicyProvider提供机制很简单:它直接利用注册到当前ServicesContainer上的HttpActionSelector根据当前请求获取用于描述目标Action的HttpActionDescriptor对象,然后调用其GetCustomAttributes<T>方法得到应用到对应Action方法上的第一个实现了ICorsPolicyProvider接口的特性。如果这样的特性不存在,则获取描述所在HttpController类型的HttpControllerDescritor对象,采用同样的方式得到应用在目标HttpController类型上的第一个实现了ICorsPolicyProvider接口的特性。

关于针对目标Action的选择问题,有一个核心的核心的细节值得关注:如果当前请求并非真正的跨域资源请求,而仅仅是一个采用“OPTIONS”作为HTTP方法的预检请求(Preflight Request),利用注册的HttpActionSelector根据当前请求是无法将目标Action选择出来的,所以需要将请求的HTTP方法替换成真正跨域资源请求采用的HTTP方法。通过上面针对W3C的CORS规范的介绍我们知道,此HTTP方法可以通过预检请求的“Access-Control-Request-Method”报头获得。实际上在上一个“通过自定义HttpMessageHandler实现CORS”的实例中,我们已经对此作个过演示了。

从上面给出的针对AttributeBasedPolicyProviderFactory的定义可以看出,除了实现的方法GetCorsPolicyProvider方法之外,它还具有一个DefaultPolicyProvider属性。该属性表示默认采用的CorsPolicyProvider,如果没有任何实现ICorsPolicyProvider接口的特性被应用到目标Action方法和它所在的HttpController类型上,该属性将会作为GetCorsPolicyProvider方法的返回值。

四、CorsPolicyProviderFactory的注册

ASP.NET Web API默认使用的CorsPolicyProviderFactory需要注册到当前的HttpConfiguration上。具体来说,所谓注册CorsPolicyProviderFactory实际上就是将它保存到当前HttpConfiguration的Properties属性表示的字典中。CorsPolicyProviderFactory的注册可以通过HttpConfiguration如下所示的扩展方法SetCorsPolicyProviderFactory来完成。

另一扩展方法GetCorsPolicyProviderFactory 则用于获取成功注册的CorsPolicyProviderFactory。如果调用该方法CorsPolicyProviderFactory尚未被注册,一个AttributeBasedPolicyProviderFactory对象会被创建出来并注册到HttpConfiguration上。

   1: public static class CorsHttpConfigurationExtensions
   2: {
   3:     //其他成员
   4:     public static ICorsPolicyProviderFactory GetCorsPolicyProviderFactory(this HttpConfiguration httpConfiguration);
   5:     public static void SetCorsPolicyProviderFactory(this HttpConfiguration httpConfiguration, ICorsPolicyProviderFactory corsPolicyProviderFactory);
   6: }

五、总结

综上所述,CorsPolicy用于描述具体的CORS资源授权策略,它由CorsPolicyProvider来提供,而后者又通过CorsPolicyProviderFactory来创建。如右图所示的UML揭示了CorsPolicy、CorsPolicyProvider和CorsPolicyProviderFactory相关接口和类之间的关系。对于这些类型来说,除了CorsPolicy定义在程序集System.Web.Cors.dll,其余的类型均定义在程序集System.Web.Http.Cors.dll中。

 

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


作者:蒋金楠
微信公众账号:大内老A
微博: www.weibo.com/artech
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号 蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
相关文章
|
11天前
|
存储 JavaScript 前端开发
敲黑板!vue3重点!一文了解Composition API新特性:ref、toRef、toRefs
该文章深入探讨了Vue3中Composition API的关键特性,包括`ref`、`toRef`、`toRefs`的使用方法与场景,以及它们如何帮助开发者更好地管理组件状态和促进逻辑复用。
敲黑板!vue3重点!一文了解Composition API新特性:ref、toRef、toRefs
|
10天前
|
开发框架 监控 前端开发
在 ASP.NET Core Web API 中使用操作筛选器统一处理通用操作
【9月更文挑战第27天】操作筛选器是ASP.NET Core MVC和Web API中的一种过滤器,可在操作方法执行前后运行代码,适用于日志记录、性能监控和验证等场景。通过实现`IActionFilter`接口的`OnActionExecuting`和`OnActionExecuted`方法,可以统一处理日志、验证及异常。创建并注册自定义筛选器类,能提升代码的可维护性和复用性。
|
16天前
|
安全 API 开发者
Web 开发新风尚!Python RESTful API 设计与实现,让你的接口更懂开发者心!
在当前的Web开发中,Python因能构建高效简洁的RESTful API而备受青睐,大大提升了开发效率和用户体验。本文将介绍RESTful API的基本原则及其在Python中的实现方法。以Flask为例,演示了如何通过不同的HTTP方法(如GET、POST、PUT、DELETE)来创建、读取、更新和删除用户信息。此示例还包括了基本的路由设置及操作,为开发者提供了清晰的API交互指南。
66 6
|
14天前
|
Java 程序员 API
Java 8新特性之Lambda表达式与Stream API的探索
【9月更文挑战第24天】本文将深入浅出地介绍Java 8中的重要新特性——Lambda表达式和Stream API,通过实例解析其语法、用法及背后的设计哲学。我们将一探究竟,看看这些新特性如何让Java代码变得更加简洁、易读且富有表现力,同时提升程序的性能和开发效率。
|
15天前
|
存储 JSON API
实战派教程!Python Web开发中RESTful API的设计哲学与实现技巧,一网打尽!
在数字化时代,Web API成为连接前后端及构建复杂应用的关键。RESTful API因简洁直观而广受欢迎。本文通过实战案例,介绍Python Web开发中的RESTful API设计哲学与技巧,包括使用Flask框架构建一个图书管理系统的API,涵盖资源定义、请求响应设计及实现示例。通过准确使用HTTP状态码、版本控制、错误处理及文档化等技巧,帮助你深入理解RESTful API的设计与实现。希望本文能助力你的API设计之旅。
39 3
|
16天前
|
JSON API 数据库
从零到英雄?一篇文章带你搞定Python Web开发中的RESTful API实现!
在Python的Web开发领域中,RESTful API是核心技能之一。本教程将从零开始,通过实战案例教你如何使用Flask框架搭建RESTful API。首先确保已安装Python和Flask,接着通过创建一个简单的用户管理系统,逐步实现用户信息的增删改查(CRUD)操作。我们将定义路由并处理HTTP请求,最终构建出功能完整的Web服务。无论是初学者还是有经验的开发者,都能从中受益,迈出成为Web开发高手的重要一步。
39 4
|
14天前
|
开发框架 JSON 缓存
震撼发布!Python Web开发框架下的RESTful API设计全攻略,让数据交互更自由!
在数字化浪潮推动下,RESTful API成为Web开发中不可或缺的部分。本文详细介绍了在Python环境下如何设计并实现高效、可扩展的RESTful API,涵盖框架选择、资源定义、HTTP方法应用及响应格式设计等内容,并提供了基于Flask的示例代码。此外,还讨论了版本控制、文档化、安全性和性能优化等最佳实践,帮助开发者实现更流畅的数据交互体验。
36 1
|
16天前
|
JSON API 开发者
惊!Python Web开发新纪元,RESTful API设计竟能如此性感撩人?
在这个Python Web开发的新纪元里,RESTful API的设计已经超越了简单的技术实现,成为了一种追求极致用户体验和开发者友好的艺术表达。通过优雅的URL设计、合理的HTTP状态码使用、清晰的错误处理、灵活的版本控制以及严格的安全性措施,我们能够让RESTful API变得更加“性感撩人”,为Web应用注入新的活力与魅力。
34 3
|
18天前
|
JSON API 数据格式
深度剖析!Python Web 开发中 RESTful API 的每一个细节,你不可不知的秘密!
在 Python Web 开发中,RESTful API 是构建强大应用的关键,基于 Representational State Transfer 架构风格,利用 HTTP 卞性能。通过 GET、POST、PUT 和 DELETE 方法分别实现资源的读取、创建、更新和删除操作。示例代码展示了如何使用 Flask 路由处理这些请求,并强调了状态码的正确使用,如 200 表示成功,404 表示未找到资源等。
38 5
|
2月前
|
API C# 开发框架
WPF与Web服务集成大揭秘:手把手教你调用RESTful API,客户端与服务器端优劣对比全解析!
【8月更文挑战第31天】在现代软件开发中,WPF 和 Web 服务各具特色。WPF 以其出色的界面展示能力受到欢迎,而 Web 服务则凭借跨平台和易维护性在互联网应用中占有一席之地。本文探讨了 WPF 如何通过 HttpClient 类调用 RESTful API,并展示了基于 ASP.NET Core 的 Web 服务如何实现同样的功能。通过对比分析,揭示了两者各自的优缺点:WPF 客户端直接处理数据,减轻服务器负担,但需处理网络异常;Web 服务则能利用服务器端功能如缓存和权限验证,但可能增加服务器负载。希望本文能帮助开发者根据具体需求选择合适的技术方案。
72 0