在《通过扩展让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中是如何实现的?的更多相关文章

  1. 在ASP.NET Web API中实现CORS(跨域资源共享)

    默认情况下,是不允许网页从不同的域访问服务器资源的,访问遵循"同源"策略的原则. 会遇到如下的报错: XMLHttpRequest cannot load http://local ...

  2. JavaScript跨域调用、JSONP、CORS与ASP.NET Web API[共8篇]

    [第1篇] 同源策略与JSONP 浏览器是访问Internet的工具,也是客户端应用的宿主,它为客户端应用提供一个寄宿和运行的环境.而这里所说的应用,基本是指在浏览器中执行的客户端JavaScript ...

  3. 跨域资源共享(CORS)问题解决方案

    CORS:Cross-Origin Resource Sharing(跨域资源共享) CORS被浏览器支持的版本情况如下:Chrome 3+.IE 8+.Firefox 3.5+.Opera 12+. ...

  4. 跨域资源共享CORS与JSONP

    同源策略限制: 同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能,如果没有同源策略,攻击者可以通过JavaScript获取你的邮件以及其他敏感信息,比如说 ...

  5. 网络编程-跨域资源共享 CORS

    目录 1.什么是同源策略? 2.跨域资源共享 CORS 3.预检请求 4.CORS相关字段 5.Golang实现跨域 6.参考资料 1.什么是同源策略? 如果两个 URL 的 protocol.por ...

  6. 跨域解决方案 - 跨域资源共享cors

    目录 1. cors 介绍 2. 原理 3. cors 解决跨域 4. 自定义HTTP 头部字段解决跨域 5. 代码演示 5. 参考链接 1. cors 介绍 cors 说的是一个机制,其实相当于一个 ...

  7. VUE SpringCloud 跨域资源共享 CORS 详解

    VUE  SpringCloud 跨域资源共享 CORS 详解 作者:  张艳涛 日期: 2020年7月28日 本篇文章主要参考:阮一峰的网络日志 » 首页 » 档案 --跨域资源共享 CORS 详解 ...

  8. 【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 ...

  9. ASP.NET Web API中的JSON和XML序列化

    ASP.NET Web API中的JSON和XML序列化 前言 阅读本文之前,您也可以到Asp.Net Web API 2 系列导航进行查看 http://www.cnblogs.com/aehyok ...

随机推荐

  1. react native 中webview内的点击事件传到外部原生调用

    先说一下我使用webview的时候遇到的一个功能需求 是这样的,上图中的这个页面是用h5做的,但是由于点击"我的优惠劵"是需要跳转到我原生的页面,也就是说我需要获得这个h5提供的点 ...

  2. trigger() --工作中问题nav样式

    自动执行某元素的某个事件 $("#div").trigger("click");  //让系统自动执行单击事件 适用于nav样式中,下面横线绝对定位于nav.o ...

  3. Android 图片圆角的简单方法

    package com.jereh.helloworld.activity.ui; import android.content.Context; import android.graphics.Ca ...

  4. 单独编译使用WebRTC的音频处理模块

    块,每块个点,(12*64=768采样)即AEC-PC仅能处理48ms的单声道16kHz延迟的数据,而 - 加载编译好的NS模块动态库 接下来只需要按照 此文 的描述在 android 的JAVA代码 ...

  5. js的一些笔记

    var txt = "Hello World!";document.write("<p>字体变大: " + txt.big() + "&l ...

  6. 德国W家HIPP 奶粉有货播报:2014.7.8 HIPP 奶粉 1+ 4盒装有货啦!

    德国W家HIPP 奶粉有货播报:2014.7.8 HIPP 奶粉 1+ 4盒装有货啦!

  7. PHP用户注册与登录【1】

    需求分析 主要功能分为 用户注册.用户登录.用户退出.用户中心 四个部分. 用户注册 用户注册主要功能有: 注册信息表单填写界面 javascript 脚本初步检测用户输入的注册信息. 注册处理模块检 ...

  8. A library of generic data structures

    A library of generic data structures including a list, array, hashtable, deque etc.. https://github. ...

  9. touches 事件捕获不到

    在UIView上加载了一个UIScrollView(全屏),touches 事件捕获不到了 原因:UIView的touch事件被UIScrollView捕获了,无法传递下去 解决方法:写一个UIScr ...

  10. MySQL慢查询日志总结

    慢查询日志概念 MySQL的慢查询日志是MySQL提供的一种日志记录,它用来记录在MySQL中响应时间超过阀值的语句,具体指运行时间超过long_query_time值的SQL,则会被记录到慢查询日志 ...