Preventing Web API from executing AT ALL if the En

2020-02-14 02:49发布

I am using Microsofts EnableCors Attribute for my Web API calls. The client-side behavior runs as I would expect: e.g. the call returns as failed when the Origin is invalid.

However, when I put a break-point inside the method & call from an invalid Origin...the method still executes top-to-bottom (even though the client gets a failed result). If the Origin is invalid, I dont want it to execute AT ALL.

MY QUESTION IS:
How can I prevent the Web API method from executing AT ALL if the EnableCors Origin is invalid?

Help me Obi-Wan Kenobi...you're my only hope.

MY CODE LOOKS LIKE:

[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));
}

MY CONFIG LOOKS LIKE:

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
}

1条回答
forever°为你锁心
2楼-- · 2020-02-14 03:25

As it turns out...

CORS headers are not expected to PREVENT calls to a controller: MVC or Web API. It merely prevents the RESULTS from being returned to the browser. The method it will get executed no matter what...so you must prevent execution by other means.

WHAT OTHER MEANS?
You can probably do it using an AuthorizeAttribute. But I wanted to do it at the ACTION level, so I chose an ActionFilterAttribute - and performed checks in in OnActionExecuting

NOTES ON USING: The ActionFilterAttribute

  • Go ahead and open CORS to all & then restrict by action (so everyone can PING)

  • Assumes all calls come from a valid REFERRER

This means things like SQL CLR calls from a database (which is what I am doing) won't work because the REFERRER is null (because of this, I will be posting a better solution later).

  • The ICorsPolicyProvider is useless - I am removing it (but included it here)

All the examples I saw included it, but I have yet to find a scenario where it gets called. My constructor already creates the CorsPolicy & the policy is available throughout the calls lifetime...so the ICorsPolicyProvider method seems pretty useless (at the moment).

  • The TraceHandler implementation is my own - go ahead & use your own instead

  • The Access-Control-Allow-Origin header had to be added to ensure expected return-message behavior for certain clients

HERE IS THE CODE: The 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
    }
}

HERE IS USAGE OF THE CODE: The 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
    }
}

APPLICATION SETTINGS: The ActionFilterAttribute

  <appSettings>
    <add key="EnableCors.Origins" value="" />
  </appSettings>
查看更多
登录 后发表回答