[asp.net-mvc] ASP.NET MVC RequireHttps solo in produzione



Answers

Se qualcuno ha bisogno della versione C #:

using System;
using System.Web.Mvc;

namespace My.Utils
{
    public class MyRequireHttpsAttribute : RequireHttpsAttribute
    {
        public override void OnAuthorization(AuthorizationContext filterContext)
        {
            if (filterContext == null)
            {
                throw new ArgumentNullException("filterContext");
            }

            if (filterContext.HttpContext != null && filterContext.HttpContext.Request.IsLocal)
            {
                return;
            }

            base.OnAuthorization(filterContext);
        }
    }
}
Question

Voglio utilizzare RequireHttpsAttribute per impedire che richieste HTTP non protette vengano inviate a un metodo di azione.

C #

[RequireHttps] //apply to all actions in controller
public class SomeController 
{
    [RequireHttps] //apply to this action only
    public ActionResult SomeAction()
    {
        ...
    }
}

VB

<RequireHttps()> _
Public Class SomeController

    <RequireHttps()> _
    Public Function SomeAction() As ActionResult
        ...
    End Function

End Class

Sfortunatamente, ASP.NET Development Server non supporta HTTPS.

Come posso fare in modo che la mia applicazione ASP.NET MVC utilizzi RequireHttps quando viene pubblicata nell'ambiente di produzione, ma non quando viene eseguita sulla mia workstation di sviluppo sul server di sviluppo ASP.NET?




Sfruttando il sistema di filtri MVC e Global.asax.cs, suppongo che tu possa fare questo ...

    protected void Application_Start()
    {
      RegisterGlobalFilters(GlobalFilters.Filters);
    }

    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
      filters.Add(new HandleErrorAttribute());
      if(Config.IsProduction) //Some flag that you can tell if you are in your production environment.
      {
        filters.Add(new RequireHttpsAttribute());
      }
    }



Dopo una ricerca aroud, sono stato in grado di risolvere questo problema con IIS Express e un override del metodo OnAuthorization della classe Controller (Ref # 1). Ho anche seguito il percorso consigliato da Hanselman (Rif. 2). Tuttavia, non sono stato completamente soddisfatto di queste due soluzioni per due motivi: 1. L'OnAutorizzazione di Ref # 1 funziona solo a livello di azione, non a livello di classe di controller 2. Ref # 2 richiede molte impostazioni (WinK SDK per makecert ), i comandi netsh e, per poter utilizzare la porta 80 e la porta 443, ho bisogno di lanciare VS2010 come amministratore, che disapprovo.

Quindi, ho trovato questa soluzione che si concentra sulla semplicità con le seguenti condizioni:

  1. Voglio essere in grado di utilizzare la funzione RequireHttps alla classe Controller o al livello di azione

  2. Voglio che MVC utilizzi HTTPS quando è presente l'attributo RequireHttps e utilizzi HTTP se è assente

  3. Non voglio dover eseguire Visual Studio come amministratore

  4. Voglio poter utilizzare qualsiasi porta HTTP e HTTPS assegnata da IIS Express (vedere la nota n. 1)

  5. Posso riutilizzare il cert SSL autofirmato di IIS Express, e non mi interessa se vedo il prompt SSL non valido

  6. Voglio che lo sviluppo, il test e la produzione abbiano lo stesso identico codice base e lo stesso binario e indipendente dall'impostazione aggiuntiva (ad esempio, utilizzando netsh, ccr snap-in mmc, ecc.) Possibile

Ora, con lo sfondo e le spiegazioni fuori mano, spero che questo codice aiuti qualcuno e risparmi un po 'di tempo. Fondamentalmente, creare una classe BaseController che eredita da Controller e derivare le classi controller da questa classe base. Visto che hai letto fino a qui, suppongo che tu sappia come fare questi. Quindi, felice codifica!

Nota 1: questo è ottenuto mediante l'uso di una funzione utile 'getConfig' (vedi codice)

Rif. 1: http://puredotnetcoder.blogspot.com/2011/09/requirehttps-attribute-in-mvc3.html

Rif. 2: http://www.hanselman.com/blog/WorkingWithSSLAtDevelopmentTimeIsEasierWithIISExpress.aspx

========== Codice in BaseController ===================

     #region Override to reroute to non-SSL port if controller action does not have RequireHttps attribute to save on CPU 
    // By L. Keng, 2012/08/27
    // Note that this code works with RequireHttps at the controller class or action level.
    // Credit: Various .com posts and http://puredotnetcoder.blogspot.com/2011/09/requirehttps-attribute-in-mvc3.html
    protected override void OnAuthorization(AuthorizationContext filterContext)
    {
        // if the controller class or the action has RequireHttps attribute
        var requireHttps = (filterContext.ActionDescriptor.ControllerDescriptor.GetCustomAttributes(typeof(RequireHttpsAttribute), true).Count() > 0 
                            || filterContext.ActionDescriptor.GetCustomAttributes(typeof(RequireHttpsAttribute), true).Count() > 0);
        if (Request.IsSecureConnection)
        {
            // If request has a secure connection but we don't need SSL, and we are not on a child action   
            if (!requireHttps && !filterContext.IsChildAction)
            {
                var uriBuilder = new UriBuilder(Request.Url)
                {
                    Scheme = "http",
                    Port = int.Parse(getConfig("HttpPort", "80")) // grab from config; default to port 80
                };
                filterContext.Result = this.Redirect(uriBuilder.Uri.AbsoluteUri);
            }
        }
        else
        {
            // If request does not have a secure connection but we need SSL, and we are not on a child action   
            if (requireHttps && !filterContext.IsChildAction)
            {
                var uriBuilder = new UriBuilder(Request.Url)
                {
                    Scheme = "https",
                    Port = int.Parse(getConfig("HttpsPort", "443")) // grab from config; default to port 443
                };
                filterContext.Result = this.Redirect(uriBuilder.Uri.AbsoluteUri);
            }
        }
        base.OnAuthorization(filterContext);
    }
    #endregion

    // a useful helper function to get appSettings value; allow caller to specify a default value if one cannot be found
    internal static string getConfig(string name, string defaultValue = null)
    {
        var val = System.Configuration.ConfigurationManager.AppSettings[name];
        return (val == null ? defaultValue : val);
    }

============== codice finale ================

In Web.Release.Config, aggiungere quanto segue per cancellare HttpPort e HttpsPort (per utilizzare 80 e 443 predefiniti).

<appSettings>
<add key="HttpPort" value="" xdt:Transform="SetAttributes" xdt:Locator="Match(key)"/>
<add key="HttpsPort" value="" xdt:Transform="SetAttributes" xdt:Locator="Match(key)"/>
</appSettings>



Se riesci a derivare e scavalcare, fallo. Se non puoi - MVC viene fornito con fonti, prendi semplicemente le fonti e crea il tuo attributo [ForceHttps] che controlla IsLocal.




Questo era il modo più pulito per me. Nel mio file App_Start\FilterConfig.cs . Non posso più eseguire build di rilascio però.

... 
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
        if (!Web.HttpContext.Current.IsDebuggingEnabled) {
            filters.Add(new RequireHttpsAttribute());   
        }
        ...
}

In alternativa, è possibile impostarlo per richiedere solo https quando la pagina di errore personalizzata è attiva.

... 
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
        if (Web.HttpContext.Current.IsCustomErrorEnabled) {
            filters.Add(new RequireHttpsAttribute());   
        }
        ...
}



Come menzionato da Joel, è possibile modificare la compilazione usando la direttiva #if !DEBUG .

Ho appena scoperto che è possibile modificare il valore del simbolo DEBUG nell'elemento di compilazione del file web.config. Spero possa aiutare.




Che ne dici di ereditare l'attributo RequireHttps in un attributo personalizzato. Quindi, all'interno dell'attributo personalizzato, controllare la proprietà IsLocal della richiesta corrente per verificare se la richiesta proviene dal computer locale. Se lo è, non applicare la funzionalità di base. Altrimenti, chiama l'operazione di base.






Related