跨域资源共享(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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏LanceToBigData

Java多线程(一)

多线程在面试中经常会被问到,所以也是非常重要的知识。 看到一篇写的很不错的博客:http://www.cnblogs.com/GarfieldEr007/p/5...

23280
来自专栏岑玉海

oozie java api提交作业

今晚试验用java的api来提交代码,由于代码是在我机器上写的,然后提交到我的虚拟机集群当中去,所以中间产生了一个错误。。要想在任意一台机器上向oozie提交...

41270
来自专栏Python中文社区

PyQt的一个UI单元测试框架思路

專 欄 ❈丁果,Python中文社区作者。对django、pyqt、opencv、tornado感兴趣。 GitHub:https://github.com/...

30360
来自专栏Java后端技术

深入聊聊Java多线程

  在没有学习Java多线程以前,总觉得多线程是个很神秘的东西,只有那些大神才能驾驭,新年假期没事就来学习和了解一下Java的多线程,本篇博客我们就来从头说一下...

12040
来自专栏Java3y

Servlet第四篇【request对象常用方法、应用】

什么是HttpServletRequest HttpServletRequest对象代表客户端的请求,当客户端通过HTTP协议访问服务器时,HTTP请求头中的所...

45350
来自专栏JMCui

多线程编程学习三(线程间通信).

一、概要 线程是操作系统中独立的个体,但这些个体如果不经过特殊的处理就不能成为一个整体,线程间的通信就是成为整体的必用方案之一。可以说,使线程进行通信后,系统...

29440
来自专栏FreeBuf

Weevely命令传输分析

前言 Weevely是一款使用python编写的webshell工具,集webshell生成和连接于一身,可以算作是linux下的一款菜刀替代工具(限于php)...

30590
来自专栏zingpLiu

Python 【web框架】之Flask

flask 是Python实现的轻量级web框架。没有表单,orm等,但扩展性很好。很多Python web开发者十分喜欢。本篇介绍flask的简单使用及其扩展...

15520
来自专栏安恒网络空间安全讲武堂

writeup分享 | 近期做的比较好的web

0x01猫头鹰嘤嘤嘤 http://124.128.55.5:30829/index.php 首先分析一下功能,随便上传一张jpg图片上传,跳转到 http...

61280
来自专栏机器学习从入门到成神

关于hibernate中对象的三种状态分析

一、首先Hibernate中对象的状态有三种:瞬态、游离态和持久态,三种状态转化的方法都是通过session来调用,瞬态到持久态的方法有save()、saveO...

24710

扫码关注云+社区

领取腾讯云代金券