ASP.NET MVC RequireHttps solo en producción

Deseo utilizar RequireHttpsAttribute para evitar que las solicitudes HTTP no seguras se envíen a un método de acción.

DO#

[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 

Desafortunadamente, ASP.NET Development Server no es compatible con HTTPS.

¿Cómo puedo hacer que mi aplicación ASP.NET MVC use RequireHttps cuando se publica en el entorno de producción, pero no cuando se ejecuta en mi estación de trabajo de desarrollo en ASP.NET Development Server?

Esto no ayudará si ejecuta comstackciones de Release en su estación de trabajo de desarrollo, pero la comstackción condicional podría hacer el trabajo …

 #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() { } } 

Actualizar

En Visual Basic, los atributos son técnicamente parte de la misma línea que la definición a la que se aplican. No puede colocar sentencias de comstackción condicionales dentro de una línea, por lo que está obligado a escribir la statement de la función dos veces: una con el atributo y otra sin ella. Funciona, sin embargo, si no te importa la fealdad.

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

Actualización 2

Varias personas han mencionado derivar de RequireHttpsAttribute sin proporcionar un ejemplo, por lo que aquí hay uno para usted. Creo que este enfoque sería mucho más limpio que el enfoque de comstackción condicional, y sería mi preferencia en su posición.

DESCARGO DE RESPONSABILIDAD: No he probado este código, aunque sea un poco, y mi VB está bastante oxidado. Todo lo que sé es que comstack. Lo escribí en base a las sugerencias de spot, queen3 y Lance Fisher. Si no funciona, al menos debería transmitir la idea general y darle el punto de partida.

 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 

Básicamente, el nuevo atributo simplemente se cierra en lugar de ejecutar el código de autorización SSL predeterminado, si la solicitud actual es local (es decir, está accediendo al sitio a través de localhost). Puedes usarlo así:

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

¡Mucho más limpio! Siempre que mi código no probado realmente funcione.

Si alguien necesita la versión 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); } } } 

Derivar de RequireHttps es un buen enfoque.

Para echar a un lado el problema por completo, puede usar IIS en su máquina local con un certificado autofirmado también. IIS es más rápido que el servidor web incorporado, y tiene la ventaja de que su entorno de desarrollo se parece más a la producción.

Scott Hanselman tiene un gran recurso en algunas formas de implementar HTTPS local con VS2010 e IIS Express.

Aprovechando el sistema de filtro MVC y Global.asax.cs, asumo que podrías hacer esto …

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

Como fue el ASP.Net Development Server el que causó su problema en primer lugar, vale la pena señalar que Microsoft ahora tiene IIS Express , que se envía con Visual Studio (desde VS2010 SP1). Esta es una versión reducida de IIS que es tan fácil de usar como el servidor de desarrollo, pero admite el conjunto completo de características de IIS 7.5, incluido SSL.

Scott Hanselman tiene una publicación detallada sobre cómo trabajar con SSL en IIS Express .

¿Qué hay de heredar el atributo RequireHttps en un atributo personalizado? Luego, dentro de su atributo personalizado, verifique la propiedad IsLocal de la solicitud actual para ver si la solicitud proviene de la máquina local. Si es así, entonces no aplique la funcionalidad básica. De lo contrario, llame a la operación base.

Esto funcionó para mí, MVC 6 (ASP.NET Core 1.0) . El código verifica si la depuración está en desarrollo, y si no, no se requiere SSL. Todas las ediciones están en Startup.cs .

Añadir:

 private IHostingEnvironment CurrentEnvironment { get; set; } 

Añadir:

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

Editar:

 public void ConfigureServices(IServiceCollection services) { // additional services... services.AddMvc(options => { if (!CurrentEnvironment.IsDevelopment()) { options.Filters.Add(typeof(RequireHttpsAttribute)); } }); } 

Si puede derivar y anular, hágalo. Si no puede: MVC viene con fonts, solo tome las fonts y cree su propio atributo [ForceHttps] que verifique IsLocal.

Para MVC 3 agregué mi propio FilterProvider (basado en el código que se encuentra aquí: filtros globales y condicionales que, entre otras cosas (mostrar información de depuración para usuarios locales, etc.) decorarán todas las acciones con RequireHttpsAttribute cuando HttpContext.Request.IsLocal == false .

Después de investigar aroud, pude resolver este problema con IIS Express y anular el método OnAuthorization de la clase Controller (Ref # 1). También me he ido con la ruta recomendada por Hanselman (Ref # 2). Sin embargo, no estaba completamente satisfecho con estas dos soluciones debido a dos razones: 1. La Autorización de Ref # 1 solo funciona en el nivel de acción, no en el nivel de clase del controlador 2. La Ref. 2 requiere mucha configuración (Win7 SDK para makecert ), comandos netsh y, para usar el puerto 80 y el puerto 443, necesito iniciar VS2010 como administrador, lo que desaprovecho.

Entonces, se me ocurrió esta solución que se enfoca en la simplicidad con las siguientes condiciones:

  1. Quiero poder utilizar el requisito de atthatttbute en la clase de controlador o nivel de acción

  2. Quiero que MVC use HTTPS cuando el atributo RequireHttps esté presente, y use HTTP si está ausente

  3. No quiero tener que ejecutar Visual Studio como administrador

  4. Quiero poder usar cualquier puerto HTTP y HTTPS asignado por IIS Express (Ver Nota n. ° 1)

  5. Puedo reutilizar el certificado SSL autofirmado de IIS Express, y no me importa si veo el aviso SSL no válido

  6. Quiero que dev, test y producción tengan exactamente la misma base de código y el mismo binario y sean independientes de la configuración adicional (por ejemplo, utilizando netsh, complemento de cert de mmc, etc.) como sea posible.

Ahora, con los antecedentes y la explicación a un lado, espero que este código ayude a alguien y ahorre tiempo. Básicamente, crea una clase BaseController que hereda de Controller y deriva tus clases de controlador de esta clase base. Como ha leído hasta aquí, supongo que sabe cómo hacer esto. Entonces, ¡feliz encoding!

Nota n. ° 1: esto se logra mediante el uso de una función útil ‘getConfig’ (ver código)

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

Ref # 2: http://www.hanselman.com/blog/WorkingWithSSLAtDevelopmentTimeIsEasierWithIISExpress.aspx

========== Código en 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); } 

============== end code ================

En Web.Release.Config, agregue lo siguiente para borrar HttpPort y HttpsPort (para usar el valor predeterminado 80 y 443).

     

Una solución que puede usar en la producción y en la estación de trabajo de desarrollo. Se basa en su opción desde la configuración de la aplicación en web.config

     

Si no quiere usar SSL, elimine la clave. Si usa el puerto SSL estándar 443, elimine el valor o especifique 443.

A continuación, utilice la implementación personalizada de RequireHttpsAttribute que se ocupa de su condición. Se deriva en realidad de RequireHttps y utiliza la misma implementación del método base, excepto para agregar condiciones.

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

No te olvides de decorar el método LogOn en AccountController

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

y algo así en su LogOn View para publicar el formulario en https.

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

Como mencionó Joel, puedes alterar la comstackción usando la directiva #if !DEBUG .

Acabo de descubrir que puede modificar el valor del símbolo DEBUG en el elemento de comstackción del archivo web.config. Espero que ayude.

MVC 6 (ASP.NET Core 1.0):

La solución adecuada sería usar env.IsProduction () o env.IsDevelopment (). Lea más sobre el motivo en esta respuesta sobre cómo requerir https solo en producción .

A continuación, encontrará una respuesta condensada (consulte el enlace anterior para obtener más información sobre las decisiones de diseño) para 2 estilos diferentes:

  1. Startup.cs – filtro de registro
  2. BaseController – estilo de atributo

Startup.cs (filtro de registro):

 public void ConfigureServices(IServiceCollection services) { // TODO: Register other services services.AddMvc(options => { options.Filters.Add(typeof(RequireHttpsInProductionAttribute)); }); } 

BaseController.cs (estilo de atributo):

 [RequireHttpsInProductionAttribute] public class BaseController : Controller { // Maybe you have other shared controller logic.. } public class HomeController : BaseController { // Add endpoints (GET / POST) for Home controller } 

RequireHttpsInProductionAttribute : ambos de los anteriores están utilizando la herencia de atributos personalizados de 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); } } 

Esta fue la manera más limpia para mí. En mi archivo App_Start\FilterConfig.cs . Sin embargo, ya no se pueden ejecutar versiones de lanzamiento.

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

Alternativamente, puede configurarlo para que solo requiera https cuando su página de error personalizada esté activada.

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

Consulte esta publicación de Rick Anderson en RickAndMSFT en Azure y MVC Rellenar la brecha azul

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