ASP.NET MVC RequireHttps solo in produzione


121

Desidero utilizzare RequireHttpsAttribute per impedire l' invio di richieste HTTP non protette 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 pubblicata nell'ambiente di produzione, ma non quando viene eseguita sulla mia workstation di sviluppo su ASP.NET Development Server?


3
Prova con il tuo IIS locale e con IIS Express. Vedi il mio blog SSL blogs.msdn.com/b/rickandy/archive/2011/04/22/… e blogs.msdn.com/b/rickandy/archive/2012/03/23/…
RickAndMSFT

Risposte:


129

Questo non aiuta se esegui build di rilascio sulla tua workstation di sviluppo, ma la compilazione condizionale potrebbe fare il lavoro ...

#if !DEBUG
[RequireHttps] //apply to all actions in controller
#endif
public class SomeController 
{
    //... or ...
#if !DEBUG
    [RequireHttps] //apply to this action only
#endif
    public ActionResult SomeAction()
    {
    }

}

Aggiornare

In Visual Basic, gli attributi fanno tecnicamente parte della stessa linea della definizione a cui si applicano. Non puoi inserire istruzioni di compilazione condizionale all'interno di una riga, quindi sei costretto a scrivere la dichiarazione di funzione due volte: una con l'attributo e una senza. Funziona, però, se non ti dispiace la bruttezza.

#If Not Debug Then
    <RequireHttps()> _
    Function SomeAction() As ActionResult
#Else
    Function SomeAction() As ActionResult
#End If
        ...
    End Function

Aggiorna 2

Diverse persone hanno menzionato derivante da RequireHttpsAttributesenza fornire un esempio, quindi eccone uno per te. Penso che questo approccio sarebbe molto più pulito rispetto all'approccio della compilazione condizionale, e sarebbe la mia preferenza nella tua posizione.

DISCLAIMER: Non ho testato questo codice, nemmeno un po ', e il mio VB è abbastanza arrugginito. Tutto quello che so è che si compila. L'ho scritto basandomi sui suggerimenti di spot, queen3 e Lance Fisher. Se non funziona, dovrebbe almeno trasmettere l'idea generale e darti un punto di partenza.

Public Class RemoteRequireHttpsAttribute
    Inherits System.Web.Mvc.RequireHttpsAttribute

    Public Overrides Sub OnAuthorization(ByVal filterContext As  _
                                         System.Web.Mvc.AuthorizationContext)
        If IsNothing(filterContext) Then
            Throw New ArgumentNullException("filterContext")
        End If

        If Not IsNothing(filterContext.HttpContext) AndAlso _
            filterContext.HttpContext.Request.IsLocal Then
            Return
        End If

        MyBase.OnAuthorization(filterContext)
    End Sub

End Class

Fondamentalmente, il nuovo attributo si chiude invece di eseguire il codice di autorizzazione SSL predefinito, se la richiesta corrente è locale (ovvero, stai accedendo al sito tramite localhost). Puoi usarlo in questo modo:

<RemoteRequireHttps()> _
Public Class SomeController

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

End Class

Molto più pulito! A condizione che il mio codice non testato funzioni effettivamente.


Grazie per, um, aver modificato il mio post per me, Zack. La tua domanda era in C #, quindi ho pubblicato una risposta in C #. Non sapevo che VB fosse rilevante. Qualcuno sa se esiste un modo per utilizzare la compilazione condizionale per controllare gli attributi in VB, o semplicemente non è possibile?
Joel Mueller

Sì, funziona per C # e funziona anche per VB, ma devi fare una duplicazione piuttosto brutta della definizione di funzione / classe. Vedi la mia risposta aggiornata sopra.
Joel Mueller

Scusate. Gli esempi di codice VB stanno diventando sempre più difficili da trovare. Non pensavo che avrebbe avuto importanza. Ho aggiornato la domanda originale. La compilazione condizionale attorno agli attributi funziona sicuramente in C #? Non ho provato. Sembra una soluzione perfetta ed elegante.
Zack Peterson,

Il tuo codice RemoteRequireHttpsAttribute funziona perfettamente. È molto più elegante per VB rispetto alla compilazione condizionale. Grazie ancora Joel.
Zack Peterson,

2
Grazie, questo era esattamente quello di cui avevo bisogno. Saluti!
davecoulter

65

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

ok durante la lettura questo e questo come misura di sicurezza dovremmo aggiungere filters.Add(new MyRequireHttpsAttribute ());a FilterConfig?
shaijut

Sulla base di questa risposta ho creato una soluzione per MVC 6 utilizzando il filtro in Startup.cs o lo stile di attributo su Controller.
Nick Niebling

26

Derivare da RequireHttps è un buon approccio.

Per eliminare completamente il problema, puoi utilizzare IIS sul tuo computer locale anche con un certificato autofirmato. IIS è più veloce del server web integrato e hai il vantaggio che il tuo ambiente di sviluppo è più simile alla produzione.

Scott Hanselman ha una grande risorsa su alcuni modi per implementare HTTPS locale con VS2010 e IIS Express.


ya - finché non provi a eseguire il port forwarding con un dispositivo Verizon wifi Mifi e scopri che la porta 443 non è disponibile per l'inoltro !!! # * & # * & $
Simon_Weaver

Quello che non mi piace dell'utilizzo di IIS sulla macchina locale con un certificato autofirmato è che devo eseguire un passaggio aggiuntivo della distribuzione per testare le modifiche. Penso che se stai testando qualcosa relativo alla sicurezza di quanto abbia senso, ma se stai solo controllando qualche altra modifica minore, è un problema doverlo distribuire solo per aggirare l'incapacità di Cassini di supportare HTTPS.
davecoulter

1
@davecoulter - Usa IIS express sulle versioni client di Windows, non è necessario cassini e funzionerà esattamente come IIS, inclusa la capacità ssl.
Erik Funkenbusch

@Mystere Man - sì, l'ho scoperto dopo quel commento. Grazie per il suggerimento :)
davecoulter

ulteriori informazioni o collegamenti dovrebbero essere aggiunti su come fare queste cose.
stephenbayer

12

Sfruttando il sistema di filtri MVC e Global.asax.cs, presumo che potresti farlo ...

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

Preferisco questa risposta in quanto implica un controllo per durata dell'applicazione piuttosto che implementare un nuovo filtro che verrà eseguito \ chiamato con ogni singola richiesta.
Abdulhameed

10

Poiché è stato il server di sviluppo ASP.Net a causare il tuo problema in primo luogo, vale la pena notare che Microsoft ora ha IIS Express , che viene fornito con Visual Studio (da VS2010 SP1). Questa è una versione ridotta di IIS facile da usare come il server di sviluppo, ma supporta il set completo di funzionalità di IIS 7.5 incluso SSL.

Scott Hanselman ha un post dettagliato su come lavorare con SSL in IIS Express .


9

Che ne dici di ereditare l'attributo RequireHttps in un attributo personalizzato. Quindi, all'interno del tuo attributo personalizzato, controlla la proprietà IsLocal della richiesta corrente per vedere se la richiesta proviene dalla macchina locale. In caso affermativo, non applicare la funzionalità di base. Altrimenti, chiama l'operazione di base.


4

Questo ha funzionato per me, MVC 6 (ASP.NET Core 1.0) . Il codice controlla se il debug è in fase di sviluppo e, in caso contrario, ssl non è richiesto. Tutte le modifiche sono in Startup.cs .

Inserisci:

private IHostingEnvironment CurrentEnvironment { get; set; }

Inserisci:

public Startup(IHostingEnvironment env)
{
    CurrentEnvironment = env;
}

Modificare:

public void ConfigureServices(IServiceCollection services)
{
    // additional services...

    services.AddMvc(options =>
    {
        if (!CurrentEnvironment.IsDevelopment())
        {
            options.Filters.Add(typeof(RequireHttpsAttribute));
        }
    });
}

3

Se puoi derivare e sovrascrivere, fallo. Se non puoi - MVC viene fornito con i sorgenti, prendi i sorgenti e crea il tuo attributo [ForceHttps] che controlli IsLocal.


3

Per MVC 3 ho aggiunto il mio FilterProvider (basato sul codice trovato qui: filtri globali e condizionali che, tra le altre cose (visualizzazione delle informazioni di debug per gli utenti locali, ecc.) Decoreranno tutte le azioni con RequireHttpsAttributequando HttpContext.Request.IsLocal == false.


Oppure puoi aggiungerlo in modo condizionale alla raccolta di filtri globali quando la richiesta è locale. Tieni presente che ti consigliamo di verificarlo in un blocco try / catch se l'app è impostata per l'avvio immediato poiché la richiesta potrebbe non essere disponibile.
tvanfosson

3

Dopo aver cercato aroud, sono stato in grado di risolvere questo problema con IIS Express e un override del metodo OnAuthorization della classe Controller (Rif # 1). Sono andato anche con il percorso consigliato da Hanselman (Rif # 2). Tuttavia, non ero completamente soddisfatto di queste due soluzioni per due motivi: 1. L'autorizzazione On di Ref # 1 funziona solo a livello di azione, non a livello di classe controller 2. Ref # 2 richiede molte impostazioni (Win7 SDK per makecert ), comandi netsh e, per poter utilizzare la porta 80 e la porta 443, devo avviare VS2010 come amministratore, cosa che non mi piace.

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

  1. Voglio essere in grado di utilizzare l'attributo RequireHttps a livello di classe o azione Controller

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

  3. Non voglio dover eseguire Visual Studio come amministratore

  4. Voglio essere in grado di utilizzare qualsiasi porta HTTP e HTTPS assegnata da IIS Express (vedi nota n. 1)

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

  6. Voglio che sviluppo, test e produzione abbiano la stessa identica base di codice e lo stesso binario e il più possibile indipendenti dalla configurazione aggiuntiva (ad esempio utilizzando netsh, snap-in mmc cert, ecc.)

Ora, con lo sfondo e la spiegazione fuori mano, spero che questo codice aiuti qualcuno e faccia risparmiare un po 'di tempo. Fondamentalmente, crea una classe BaseController che eredita da Controller e deriva le classi del controller da questa classe base. Dato che hai letto fin qui, presumo che tu sappia come farlo. Quindi, buona programmazione!

Nota # 1: ciò si ottiene utilizzando un'utile funzione 'getConfig' (vedi codice)

Rif n.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 stackoverflow.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, aggiungi quanto segue per cancellare HttpPort e HttpsPort (per usare i valori predefiniti 80 e 443).

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

3

Una soluzione che puoi utilizzare sia in produzione che su workstation di sviluppo. Si basa sulla tua opzione dalle impostazioni dell'applicazione in web.config

<appSettings>
     <!--Use SSL port 44300 in IIS Express on development workstation-->
     <add key="UseSSL" value="44300" />
</appSettings>

Se non vuoi usare SSL rimuovi la chiave. Se si utilizza la porta SSL standard 443, rimuovere il valore o specificare 443.

Quindi usa l'implementazione personalizzata di RequireHttpsAttribute che si prende cura della tua condizione. In realtà è derivato da RequireHttps e utilizza la stessa implementazione del metodo di base ad eccezione dell'aggiunta di condizioni.

public class RequireHttpsConditional : RequireHttpsAttribute
{
    protected override void HandleNonHttpsRequest(AuthorizationContext filterContext)
    {
        var useSslConfig = ConfigurationManager.AppSettings["UseSSL"];
        if (useSslConfig != null)
        {
            if (!string.Equals(filterContext.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
            {
                throw new InvalidOperationException("The requested resource can only be accessed via SSL.");
            }

            var request = filterContext.HttpContext.Request;
            string url = null;
            int sslPort;

            if (Int32.TryParse(useSslConfig, out sslPort) && sslPort > 0)
            {
                url = "https://" + request.Url.Host + request.RawUrl;

                if (sslPort != 443)
                {
                    var builder = new UriBuilder(url) {Port = sslPort};
                    url = builder.Uri.ToString();
                }
            }

            if (sslPort != request.Url.Port)
            {
                filterContext.Result = new RedirectResult(url);
            }
        }
    }
}

Non dimenticare di decorare il metodo LogOn in AccountController

[RequireHttpsConditional]
[HttpPost]
public ActionResult LogOn(LogOnModel model, string returnUrl)

e qualcosa di simile nella tua vista LogOn per postare il modulo su https.

<% using (Html.BeginFormSecure("LogOn", "Account", new { ReturnUrl = Request.QueryString["ReturnUrl"] }, Request.IsSecureConnection, Request.Url)) { %>

ricevo questo errore: XMLHttpRequest non può caricare m.XXX.com/Auth/SignIn . Nessuna intestazione "Access-Control-Allow-Origin" è presente sulla risorsa richiesta. L'accesso a Origin " m.XXX.com " non è pertanto consentito.
Ranjith Kumar Nagiri

2

Come menzionato da Joel, puoi modificare la compilazione usando la #if !DEBUGdirettiva.

Ho appena scoperto che puoi modificare il valore del simbolo DEBUG nell'elemento di compilazione del file web.config. Spero che aiuti.


1

MVC 6 (ASP.NET Core 1.0):

La soluzione corretta sarebbe usare env.IsProduction () o env.IsDevelopment (). Leggi di più sul motivo alla base di questa risposta su come richiedere https solo in produzione .

Risposta condensata di seguito (vedere il collegamento sopra per saperne di più sulle decisioni di progettazione) per 2 stili diversi:

  1. Startup.cs: registra il filtro
  2. BaseController: stile dell'attributo

Startup.cs (filtro di registro):

public void ConfigureServices(IServiceCollection services)
{
    // TODO: Register other services

    services.AddMvc(options =>
    {
        options.Filters.Add(typeof(RequireHttpsInProductionAttribute));
    });
}

BaseController.cs (stile attributo):

[RequireHttpsInProductionAttribute]
public class BaseController : Controller
{
    // Maybe you have other shared controller logic..
}

public class HomeController : BaseController
{
    // Add endpoints (GET / POST) for Home controller
}

RequireHttpsInProductionAttribute : entrambi i precedenti utilizzano l'attributo personalizzato che eredita da RequireHttpsAttribute :

public class RequireHttpsInProductionAttribute : RequireHttpsAttribute
{
    private bool IsProduction { get; }

    public RequireHttpsInProductionAttribute(IHostingEnvironment environment)
    {
        if (environment == null)
            throw new ArgumentNullException(nameof(environment));
        this.IsProduction = environment.IsProduction(); 
    }
    public override void OnAuthorization(AuthorizationContext filterContext)
    {
        if (this.IsProduction)
            base.OnAuthorization(filterContext);
    }
    protected override void HandleNonHttpsRequest(AuthorizationContext filterContext)
    {
        if(this.IsProduction)
            base.HandleNonHttpsRequest(filterContext);
    }
}

1

Questo è stato il modo più pulito per me. Nel mio App_Start\FilterConfig.csfile. Tuttavia, non è più possibile eseguire build di rilascio.

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

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

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

Questa è una soluzione semplice che funziona alla grande in MVC 5 :)
MWD

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.