如果 EnableCors Origin 无效,则完全阻止 Web API 执行

Posted

技术标签:

【中文标题】如果 EnableCors Origin 无效,则完全阻止 Web API 执行【英文标题】:Preventing Web API from executing AT ALL if the EnableCors Origin is invalid 【发布时间】:2016-08-02 01:13:10 【问题描述】:

我正在为我的Web API 调用使用Microsofts EnableCors 属性。客户端行为按我的预期运行:例如当 Origin 无效时调用返回失败。

但是,当我在方法中设置断点并从无效的 Origin 调用时...该方法仍然从上到下执行(即使客户端得到失败的结果)。如果 Origin 无效,我根本不希望它执行。

我的问题是: 如果 EnableCors Origin 无效,如何阻止 Web API 方法执行?

帮助我欧比旺·克诺比……你是我唯一的希望。

我的代码看起来像:

[HttpPost]
[EnableCors(origins: "www.zippitydoodah.com", headers: "*", methods: "*")]
public HttpResponseMessage Enqueue(HttpRequestMessage request)

    // NONE OF THIS SHOULD RUN: If the Origin is bad...but (oddly) it is
    TraceHandler.TraceIn(TraceLevel.Info);

    string claimId = string.Empty;
    ClaimMessage claimMessage = null;

    try 
    
        claimId = GetClaimId(request);
        claimMessage = CreateClaimMessage(claimId, segmentClaimFullName);

        Enqueue(claimMessage);

        TraceHandler.TraceAppend(FORMAT_ENQUEUED_SUCCESS, claimId);
    
    catch (Exception ex)
    
        TraceHandler.TraceError(ex);
        TraceHandler.TraceOut();

        EnqueueToPoison(ex, claimMessage);
        return Request.CreateResponse(HttpStatusCode.InternalServerError, GetHttpError());
    

    TraceHandler.TraceOut();
    return Request.CreateResponse(HttpStatusCode.OK, string.Format(FORMAT_ENQUEUED_SUCCESS, claimId));

我的配置看起来像:

public static class WebApiConfig

    #region <Methods>

    public static void Register(HttpConfiguration config)
    
        // ENABLE CORS
        config.EnableCors();

        // CREATE ROUTES
        config.Routes.MapHttpRoute(
            name: "DefaultRpcApiActions",
            routeTemplate: "api/controller/actions/action/id",
            defaults: new  id = RouteParameter.Optional );

        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/controller/id",
            defaults: new  id = RouteParameter.Optional );
    

    #endregion

【问题讨论】:

this 有帮助吗? +1 到目前为止,我通过令牌(又名“身份验证标头”)处理“一切”(包括非浏览器请求,因为它们发生)。 @EdSF 帮我理解这意味着什么? 它与任何其他“身份验证”没有什么不同 - 例如Basic, "shared secret"/tokens 等等。所以验证请求在任何其他执行之前(验证检查将发生,但没有其他),无论是来自浏览器/XHR 还是其他一些非-浏览器客户端(curlHttpClientWebClient 等执行 POSTGET 等)。本质上,CORS 处理 javascript/XHR,但不处理直接请求(例如 Form Post 等),因此如果您想停止执行,则需要更进一步。这是detail that will be helpful。 Hth 【参考方案1】:

事实证明...

CORS 标头不应阻止对控制器的调用:MVC 或 Web API。它只是防止将结果返回给浏览器。无论如何都会被执行的方法……所以你必须通过其他方式阻止执行。

还有什么意思? 您可以使用AuthorizeAttribute 来完成。但我想在 ACTION 级别进行,所以我选择了ActionFilterAttribute - 并在OnActionExecuting 中进行了签入

使用注意事项: ActionFilterAttribute

继续向所有人开放 CORS,然后通过操作进行限制(这样每个人都可以 PING)

假设所有呼叫都来自有效的REFERRER

这意味着像 SQL CLR 来自数据库的调用(这是我正在做的)将不起作用,因为 REFERRER 为空(因此,我将发布一个以后有更好的解决方案。

ICorsPolicyProvider 没用 - 我正在删除它(但包含在此处)

我看到的所有示例都包含它,但我还没有找到调用它的场景。我的构造函数已经创建了 CorsPolicy 并且该策略在整个调用生命周期中都可用...所以 ICorsPolicyProvider 方法似乎毫无用处(目前)。

TraceHandler 实现是我自己的 - 继续使用您自己的实现

必须添加 Access-Control-Allow-Origin 标头以确保某些客户端的预期返回消息行为

代码如下: ActionFilterAttribute

namespace My.Application.Security

    using My.Application.Diagnostics;
    using System;
    using System.Configuration;
    using System.Diagnostics;
    using System.Net;
    using System.Net.Http;
    using System.Threading;
    using System.Threading.Tasks;
    using System.Web.Cors;
    using System.Web.Http.Controllers;
    using System.Web.Http.Cors;
    using System.Web.Http.Filters;

    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
    public class EnableWebApiCorsFromAppSettingsAttribute : ActionFilterAttribute, ICorsPolicyProvider
    
        #region <Fields & Constants>

        private const string EXCEPTION_CONTEXT_NULL = "Access Denied: HttpActionContext cannot be null.";
        private const string EXCEPTION_REFERRER_NULL = "Access Denied: Referrer cannot be null.";
        private const string FORMAT_INVALID_REFERRER = "Access Denied: '0' is not a valid referrer.";
        private const string FORMAT_REFERRER = "Referrer: '0' was processed for this request.";
        private const string FORMAT_REFERRER_FOUND = "Referrer IsFound: 0.";

        private readonly CorsPolicy policy;

        #endregion

        #region <Constructors>

        public EnableWebApiCorsFromAppSettingsAttribute(string appSettingKey, bool allowAnyHeader = true, bool allowAnyMethod = true, bool supportsCredentials = true)
        
            policy = new CorsPolicy();
            policy.AllowAnyOrigin = false;
            policy.AllowAnyHeader = allowAnyHeader;
            policy.AllowAnyMethod = allowAnyMethod;
            policy.SupportsCredentials = supportsCredentials;

            SetValidOrigins(appSettingKey);

            if (policy.Origins.Count == 0)
                policy.AllowAnyOrigin = true;
        

        #endregion

        #region <Methods>

        #region public

        public override void OnActionExecuting(HttpActionContext actionContext)
        
            TraceHandler.TraceIn(TraceLevel.Info);

            if (actionContext == null)
                throw new ArgumentNullException("HttpActionContext");

            if (actionContext.Request.Headers.Referrer == null)
                actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.Forbidden, EXCEPTION_REFERRER_NULL);

            var referrer = actionContext.Request.Headers.Referrer.ToString();

            TraceHandler.TraceAppend(string.Format(FORMAT_REFERRER, referrer));

            // If no Origins Are Set - Do Nothing
            if (policy.Origins.Count > 0)
            
                var isFound = policy.Origins.Contains(referrer);

                TraceHandler.TraceAppend(string.Format(FORMAT_REFERRER_FOUND, isFound));

                if (!isFound)
                
                    TraceHandler.TraceAppend("IsFound was FALSE");
                    actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.Forbidden, string.Format(FORMAT_INVALID_REFERRER, referrer));
                
            

            TraceHandler.TraceOut();
            base.OnActionExecuting(actionContext);
        

        public Task<CorsPolicy> GetCorsPolicyAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        
            if (cancellationToken.CanBeCanceled && cancellationToken.IsCancellationRequested)
                return Task.FromResult<CorsPolicy>(null);

            return Task.FromResult(policy);
        

        #endregion

        #region private

        private void SetValidOrigins(string appSettingKey)
        
            // APP SETTING KEY: <add key="EnableCors.Origins" value="http://www.zippitydoodah.com" />
            var origins = string.Empty;
            if (!string.IsNullOrEmpty(appSettingKey))
            
                origins = ConfigurationManager.AppSettings[appSettingKey];
                if (!string.IsNullOrEmpty(origins))
                
                    foreach (string origin in origins.Split(",;|".ToCharArray(), StringSplitOptions.RemoveEmptyEntries))
                        policy.Origins.Add(origin);
                
            
        

        #endregion

        #endregion
    

代码用法如下: ActionFilterAttribute

namespace My.Application.Web.Controllers

    using Security;
    using My.Application.Diagnostics;
    using My.Application.Framework.Configuration;
    using My.Application.Models;
    using My.Application.Process;
    using System;
    using System.Configuration;
    using System.Diagnostics;
    using System.IO;
    using System.Linq;
    using System.Messaging;
    using System.Net;
    using System.Net.Http;
    using System.Web.Http;
    using System.Web.Http.Cors;
    using System.Xml.Linq;
    using System.Xml.Serialization;

    [EnableCors(origins: "*", headers: "*", methods: "*")]
    public class OutboundEventController : ApiControllerBase
    
        #region <Actions>

        [HttpGet]
        public HttpResponseMessage Ping()
        
            TraceHandler.TraceIn(TraceLevel.Info);

            if (Request.Headers.Referrer == null)
                TraceHandler.TraceAppend(MESSAGE_REFERRER_NULL);

            if (Request.Headers.Referrer != null)
                TraceHandler.TraceAppend(string.Format(FORMAT_REFERRER, Request.Headers.Referrer));

            TraceHandler.TraceOut();
            return Request.CreateResponse(HttpStatusCode.OK, "Ping back at cha...");
        

        [HttpPost]
        [EnableWebApiCorsFromAppSettings("EnableCors.Origins")]
        public HttpResponseMessage Enqueue(HttpRequestMessage request)
        
            TraceHandler.TraceIn(TraceLevel.Info);

            if (Request.Headers.Referrer == null)
                TraceHandler.TraceAppend(MESSAGE_REFERRER_NULL);

            if (Request.Headers.Referrer != null)
                TraceHandler.TraceAppend(string.Format(FORMAT_REFERRER, Request.Headers.Referrer));

            try 
            
                // Do Amazing Stuff Here...

                TraceHandler.TraceAppend(FORMAT_ENQUEUED_SUCCESS, claimId);
            
            catch (Exception ex)
            
                TraceHandler.TraceError(ex);
                TraceHandler.TraceOut();

                EnqueueToPoison(ex, claimMessage);
                return Request.CreateResponse(HttpStatusCode.InternalServerError, GetHttpError());
            

            TraceHandler.TraceOut();

            // FORCE: Correct Header
            var response = Request.CreateResponse(HttpStatusCode.OK, string.Format(FORMAT_ENQUEUED_SUCCESS, claimId));
            response.Headers.Add("Access-Control-Allow-Origin", "*");
            return response;
        

        #endregion

        private string GetClaimId(HttpRequestMessage request)
        
            var stream = request.Content.ReadAsStreamAsync().Result;
            var xdoc = XDocument.Load(stream);
            var result = GetElementValue(xdoc, "ClaimId");

            return result;
        

        private ClaimMessage CreateClaimMessage(string claimId, string process)
        
            ClaimMessage message = new ClaimMessage();

            message.ClaimID = claimId;
            message.Process = process;

            return message;
        

        private void Enqueue(ClaimMessage claimMessage)
        
            var queueName = ConfigurationManager.AppSettings[Settings.Messaging.Queue.Name].ToString();
            var queue = new MessageQueue(queueName);
            queue.DefaultPropertiesToSend.Recoverable = true;

            TraceHandler.TraceAppend(FORMAT_QUEUE_NAME, queueName);

            MessageQueueTransaction transaction;
            transaction = new MessageQueueTransaction();
            transaction.Begin();

            var message = new System.Messaging.Message();
            message.Formatter = new XmlMessageFormatter(new Type[]  typeof(ClaimMessage) );

            message.Label = "ClaimID " + claimMessage.ClaimID;

            message.Body = claimMessage;
            queue.Send(message, transaction);

            transaction.Commit();
            queue.Close();
        

        private void EnqueueToPoison(Exception exception, ClaimMessage claimdata)
        
            TraceHandler.TraceIn(TraceLevel.Info);

            var poison = ToPoisonMessage(exception, claimdata);
            var message = new System.Messaging.Message();

            try
            
                var poisonQueueName = ConfigurationManager.AppSettings[Settings.Messaging.PoisonQueue.Name].ToString();

                TraceHandler.TraceAppend(FORMAT_QUEUE_NAME, poisonQueueName);

                if (MessageQueue.Exists(poisonQueueName))
                
                    var queue = new MessageQueue(poisonQueueName);
                    queue.DefaultPropertiesToSend.Recoverable = true;

                    var transaction = new MessageQueueTransaction();
                    transaction.Begin();

                    message.Formatter = new XmlMessageFormatter(new Type[]  typeof(PoisonClaimMessage) );
                    message.Label = "Poison ClaimID " + poison.ClaimID;

                    var xmlSerializer = new XmlSerializer(poison.GetType());
                    xmlSerializer.Serialize(message.BodyStream, poison);

                    queue.Send(message, transaction);

                    TraceHandler.TraceAppend(FORMAT_ENQUEUED_POISON_SUCCESS, poison.ClaimID);

                    transaction.Commit();
                    queue.Close();
                
            
            catch(Exception ex)
            
                // An error occurred while enqueuing to POISON
                var poisonXml = ToString(poison);

                TraceHandler.TraceError(ex);
                TraceHandler.TraceAppend(poisonXml);
            
            finally
            
                TraceHandler.TraceOut();
            
        


        #endregion
    

应用设置: ActionFilterAttribute

  <appSettings>
    <add key="EnableCors.Origins" value="" />
  </appSettings>

【讨论】:

以上是关于如果 EnableCors Origin 无效,则完全阻止 Web API 执行的主要内容,如果未能解决你的问题,请参考以下文章

对无效CORS请求的预期响应是什么?

teechart曲线颜色设置无效

经实测解决Access-Control-Allow-Origin多域名跨域问题

Web API 2 启用 CORS 引发 Access-Control-Allow-Origin 错误

如果 CORS 标头“Access-Control-Allow-Origin”为“*”,则不支持凭据

Angular JS Cors 请求的资源上不存在“Access-Control-Allow-Origin”标头错误