跨域资源共享(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授权检验

16082911-5b051ccbf693430c94438a5f5e54178

实现在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(,,)] 
   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


作者:蒋金楠
微信公众账号:大内老A
微博: www.weibo.com/artech
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号 蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
相关文章
|
10天前
|
开发框架 缓存 前端开发
利用Visual Basic构建高效的ASP.NET Web应用
【4月更文挑战第27天】本文探讨使用Visual Basic与ASP.NET创建高效Web应用的策略,包括了解两者基础、项目规划、MVC架构、数据访问与缓存、代码优化、异步编程、安全性、测试及部署维护。通过这些步骤,开发者能构建出快速、可靠且安全的Web应用,适应不断进步的技术环境。
|
3天前
|
Java Spring
快速解决Spring Boot跨域困扰:使用CORS实现无缝跨域支持
这是一个简单的配置示例,用于在Spring Boot应用程序中实现CORS支持。根据你的项目需求,你可能需要更详细的配置来限制允许的来源、方法和标头。
15 3
|
8天前
|
缓存 前端开发 安全
Python web框架fastapi中间件的使用,CORS跨域详解
Python web框架fastapi中间件的使用,CORS跨域详解
|
15天前
|
JavaScript 前端开发 安全
JavaScript中跨域资源共享(CORS):原理和解决方案
【4月更文挑战第22天】本文介绍了JavaScript中跨域资源共享(CORS)的原理和解决方案。CORS借助HTTP头部字段允许跨域请求,核心是Access-Control-Allow-Origin响应头。解决方案包括:服务器端设置响应头(如使用Express.js的cors中间件)、使用代理服务器或JSONP。现代Web开发推荐使用CORS,因为它更安全、灵活,而JSONP已逐渐被淘汰。理解并正确实施CORS能提升Web应用性能和安全性。
|
2月前
|
缓存 安全 数据安全/隐私保护
在智能媒体服务中,跨域问题可以通过设置CORS(跨源资源共享)规则来解决
在智能媒体服务中,跨域问题可以通过设置CORS(跨源资源共享)规则来解决
19 4
|
2月前
|
JavaScript 安全 前端开发
js开发:请解释什么是跨域请求(CORS),以及如何解决跨域问题。
CORS是一种W3C标准,用于解决浏览器同源策略导致的跨域数据访问限制。它通过服务器在HTTP响应头添加标志允许特定源进行跨域请求。简单请求无需预检,而预检请求(OPTIONS)用于询问服务器是否接受非简单请求。服务器端配置响应头如`Access-Control-Allow-Origin`等实现CORS策略,客户端JavaScript则正常发起请求。若配置不当,浏览器将阻止跨域访问,保障安全。
24 2
|
2月前
|
前端开发 安全 开发者
前端开发中的跨域资源共享(CORS)问题及解决方案探讨
在前端开发中,跨域资源共享(CORS)是一个常见且重要的问题。本文将深入探讨CORS的原理、影响以及解决方案,帮助开发者更好地应对跨域请求问题。
|
8天前
|
存储 缓存 运维
DataWorks操作报错合集之DataWorks根据api,调用查询文件列表接口报错如何解决
DataWorks是阿里云提供的一站式大数据开发与治理平台,支持数据集成、数据开发、数据服务、数据质量管理、数据安全管理等全流程数据处理。在使用DataWorks过程中,可能会遇到各种操作报错。以下是一些常见的报错情况及其可能的原因和解决方法。
20 1
|
8天前
|
SQL 数据管理 API
数据管理DMS产品使用合集之阿里云DMS提供API接口来进行数据导出功能吗
阿里云数据管理DMS提供了全面的数据管理、数据库运维、数据安全、数据迁移与同步等功能,助力企业高效、安全地进行数据库管理和运维工作。以下是DMS产品使用合集的详细介绍。
|
9天前
|
运维 Serverless API
Serverless 应用引擎产品使用之在阿里函数计算中开启函数计算 API 接口如何解决
阿里云Serverless 应用引擎(SAE)提供了完整的微服务应用生命周期管理能力,包括应用部署、服务治理、开发运维、资源管理等功能,并通过扩展功能支持多环境管理、API Gateway、事件驱动等高级应用场景,帮助企业快速构建、部署、运维和扩展微服务架构,实现Serverless化的应用部署与运维模式。以下是对SAE产品使用合集的概述,包括应用管理、服务治理、开发运维、资源管理等方面。
107 6