跨域资源共享(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
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号 蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
相关文章
|
5天前
|
开发框架 监控 .NET
Visual Basic的Web服务和REST API开发指南
【4月更文挑战第27天】本文探讨了使用Visual Basic(VB.NET)构建Web服务和RESTful API的方法。首先介绍了Web服务的基础和REST API的概念,然后阐述了.NET Framework与.NET Core/.NET 5+对VB.NET的支持,以及ASP.NET Core在Web开发中的作用。接着,详细讲解了创建RESTful API的步骤,包括控制器与路由设置、模型绑定与验证,以及返回响应。此外,还讨论了安全措施、测试方法、部署选项和监控策略。最后强调,VB.NET开发者可以通过ASP.NET Core涉足现代Web服务开发,拓宽技术领域。
|
5天前
|
API 网络安全 数据安全/隐私保护
.NET邮箱API发送邮件的方法有哪些
本文介绍了.NET开发中使用邮箱API发送邮件的方法,包括SmtpClient类发送邮件、MailMessage类创建邮件消息、设置SmtpClient属性、同步/异步发送、错误处理、发送HTML格式邮件、带附件邮件以及多人邮件。AokSend提供高触达发信服务,适用于大规模验证码发送场景。了解这些技巧有助于开发者实现高效、可靠的邮件功能。
|
3天前
|
存储 安全 前端开发
第五章 跨域资源共享(CORS):现代Web开发中的关键机制
第五章 跨域资源共享(CORS):现代Web开发中的关键机制
|
3天前
|
移动开发 JSON 前端开发
跨域资源共享(CORS):详解跨域请求的限制与解决方法
跨域资源共享(CORS):详解跨域请求的限制与解决方法
|
5天前
|
弹性计算 JSON Shell
基于Web API的自动化信息收集和整理
【4月更文挑战第30天】
20 0
|
2天前
|
缓存 监控 API
利用Python构建高性能的Web API后端服务
随着微服务架构的普及和RESTful API的广泛应用,构建高性能、可扩展的Web API后端服务变得尤为重要。本文将探讨如何利用Python这一强大且灵活的语言,结合现代Web框架和工具,构建高效、可靠的Web API后端服务。我们将分析Python在Web开发中的优势,介绍常用的Web框架,并通过实际案例展示如何设计并实现高性能的API服务。
|
3天前
|
前端开发 安全 JavaScript
跨域资源共享(CORS)
跨域资源共享(CORS)
8 0
|
5天前
|
JSON 安全 API
【专栏】四种REST API身份验证方法:基本认证、OAuth、JSON Web Token(JWT)和API密钥
【4月更文挑战第28天】本文探讨了四种REST API身份验证方法:基本认证、OAuth、JSON Web Token(JWT)和API密钥。基本认证简单但不安全;OAuth适用于授权第三方应用;JWT提供安全的身份验证信息传递;API密钥适合内部使用。选择方法时需平衡安全性、用户体验和开发复杂性。
|
5天前
|
缓存 前端开发 API
toapi,一个强大的 Python Web API库!
toapi,一个强大的 Python Web API库!
29 5
|
5天前
|
JavaScript 前端开发 安全
JavaScript中跨域资源共享(CORS):原理和解决方案
【4月更文挑战第22天】本文介绍了JavaScript中跨域资源共享(CORS)的原理和解决方案。CORS借助HTTP头部字段允许跨域请求,核心是Access-Control-Allow-Origin响应头。解决方案包括:服务器端设置响应头(如使用Express.js的cors中间件)、使用代理服务器或JSONP。现代Web开发推荐使用CORS,因为它更安全、灵活,而JSONP已逐渐被淘汰。理解并正确实施CORS能提升Web应用性能和安全性。

热门文章

最新文章