ASP.NET MVC RequireHttps solo in produzione

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

  _ Public Class SomeController  _ 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?

Ciò non sarà di aiuto se si eseguono i build Release sulla 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 sono tecnicamente parte della stessa linea della definizione a cui si applicano. Non è ansible inserire istruzioni di compilazione condizionali all’interno di una riga, quindi è necessario scrivere la dichiarazione di funzione due volte, una volta con l’attributo e una volta senza. Funziona, però, se non ti dispiace la bruttezza.

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

Aggiornamento 2

Diverse persone hanno menzionato derivando da RequireHttpsAttribute senza fornire un esempio, quindi eccone uno per te. Penso che questo approccio sarebbe molto più pulito dell’approccio di compilazione condizionale, e sarebbe la mia preferenza nella tua posizione.

NOTA BENE: Non ho testato questo codice, anche 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 il ​​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 interrompe invece di eseguire il codice di authorization SSL predefinito, se la richiesta corrente è locale (ovvero, stai accedendo al sito tramite localhost). Puoi usarlo in questo modo:

  _ Public Class SomeController  _ Public Function SomeAction() As ActionResult ... End Function End Class 

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

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

Derivare da RequireHttps è un buon approccio.

Per risolvere completamente il problema, è ansible utilizzare IIS sul computer locale 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 locali con VS2010 e IIS Express.

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

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

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

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.

Questo ha funzionato per me, MVC 6 (ASP.NET Core 1.0) . Il codice controlla se il debug è in 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)); } }); } 

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.

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

Dopo una ricerca aroud, sono stato in grado di risolvere questo problema con IIS Express e un override del metodo OnAuthorization della class 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 class 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 class 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 class BaseController che eredita da Controller e derivare le classi controller da questa class 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 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, aggiungere quanto segue per cancellare HttpPort e HttpsPort (per utilizzare 80 e 443 predefiniti).

     

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

     

Se non si desidera utilizzare SSL, rimuovere la chiave. Se si utilizza la porta SSL standard 443, rimuovere il valore o specificare 443.

Quindi utilizzare l’implementazione personalizzata di RequireHttpsAttribute che si prende cura delle proprie condizioni. È derivato in realtà da RequireHttps e utilizza la stessa implementazione del metodo base tranne per l’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 di LogOn per poter pubblicare moduli su https.

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

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

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

MVC 6 (ASP.NET Core 1.0):

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

Risposta condensata di seguito (vedi link sopra per ulteriori informazioni sulle decisioni di progettazione) per 2 stili diversi:

  1. Startup.cs – registra il filtro
  2. BaseController – stile attributo

Startup.cs (filtro di registrazione):

 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 : entrambe le versioni precedenti utilizzano l’attributo personalizzato ereditato 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); } } 

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, è ansible impostarlo per richiedere solo https quando la pagina di errore personalizzata è triggers.

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

Si prega di fare riferimento a questo post di Rick Anderson su RickAndMSFT su Azure & MVC Riempimento di Azure Gap

http://blogs.msdn.com/b/rickandy/archive/2011/04/22/better-faster-easier-ssl-testing-for-asp-net-mvc-amp-webforms.aspx