Páginas SSL bajo ASP.NET MVC

¿Cómo hago para usar HTTPS en algunas de las páginas de mi sitio ASP.NET MVC?

Steve Sanderson tiene un buen tutorial sobre cómo hacer esto de manera SECA en la Vista previa 4 en:

http://blog.codeville.net/2008/08/05/adding-httpsssl-support-to-aspnet-mvc-routing/

¿Hay alguna manera mejor / actualizada con Preview 5?

Si está utilizando ASP.NET MVC 2 Preview 2 o superior , ahora puede usar simplemente:

[RequireHttps] public ActionResult Login() { return View(); } 

Sin embargo, vale la pena observar el parámetro de orden, como se menciona aquí .

MVCFutures tiene un atributo ‘RequireSSL’.

(gracias a Adam por señalar eso en su blog actualizado actualizado)

Simplemente aplíquelo a su método de acción, con ‘Redirect = true’ si desea una solicitud http: // para convertirse automáticamente en https: //:

  [RequireSsl(Redirect = true)] 

Ver también: ASP.NET MVC RequireHttps solo en producción

Como escribió Amadiere , [RequireHttps] funciona muy bien en MVC 2 para ingresar a HTTPS. Pero si solo quieres usar HTTPS para algunas páginas como dijiste, MVC 2 no te quiere: una vez que cambia a un usuario a HTTPS, permanece bloqueado hasta que lo redirigas manualmente.

El enfoque que utilicé es usar otro atributo personalizado, [ExitHttpsIfNotRequired]. Cuando se adjunta a un controlador o acción, esto redirigirá a HTTP si:

  1. La solicitud fue HTTPS
  2. El atributo [RequireHttps] no se aplicó a la acción (o controlador)
  3. La solicitud era un GET (redirigir un POST conduciría a todo tipo de problemas).

Es un poco demasiado grande para publicar aquí, pero puedes ver el código aquí más algunos detalles adicionales.

Aquí hay una publicación reciente de Dan Wahlin sobre esto:

http://weblogs.asp.net/dwahlin/archive/2009/08/25/requiring-ssl-for-asp-net-mvc-controllers.aspx

Él usa un Atributo de ActionFilter.

Algunas extensiones de ActionLink: http://www.squaredroot.com/post/2008/06/11/MVC-and-SSL.aspx O un atributo de acción de controlador que redirige a https: // http://forums.asp.net /p/1260198/2358380.aspx#2358380

Para aquellos que no son fanáticos de los enfoques de desarrollo orientados a atributos, aquí hay una pieza de código que podría ayudar:

 public static readonly string[] SecurePages = new[] { "login", "join" }; protected void Application_AuthorizeRequest(object sender, EventArgs e) { var pageName = RequestHelper.GetPageNameOrDefault(); if (!HttpContext.Current.Request.IsSecureConnection && (HttpContext.Current.Request.IsAuthenticated || SecurePages.Contains(pageName))) { Response.Redirect("https://" + Request.ServerVariables["HTTP_HOST"] + HttpContext.Current.Request.RawUrl); } if (HttpContext.Current.Request.IsSecureConnection && !HttpContext.Current.Request.IsAuthenticated && !SecurePages.Contains(pageName)) { Response.Redirect("http://" + Request.ServerVariables["HTTP_HOST"] + HttpContext.Current.Request.RawUrl); } } 

Hay varias razones para evitar los atributos y una de ellas es que si desea ver la lista de todas las páginas seguras, tendrá que saltar sobre todos los controladores en la solución.

Me crucé con esta pregunta y espero que mi solución pueda ayudar a alguien.

Tenemos algunos problemas: – Necesitamos asegurar acciones específicas, por ejemplo, “Iniciar sesión” en “Cuenta”. Podemos usar la comstackción en el atributo RequireHttps, que es genial, pero nos redireccionará de nuevo con https: //. – Deberíamos hacer que nuestros enlaces, formularios y “SSL estén al tanto”.

En general, mi solución permite especificar rutas que usarán url absoluta, además de la capacidad de especificar el protocolo. Puede usar esta aproximación para especificar el protocolo “https”.

Entonces, primero he creado una enumeración ConnectionProtocol:

 ///  /// Enum representing the available secure connection requirements ///  public enum ConnectionProtocol { ///  /// No secure connection requirement ///  Ignore, ///  /// No secure connection should be used, use standard http request. ///  Http, ///  /// The connection should be secured using SSL (https protocol). ///  Https } 

Ahora, he creado una versión enrollada a mano de RequireSsl. Modifiqué el código fuente RequireSsl original para permitir la redirección a http: // urls. Además, he puesto un campo que nos permite determinar si debemos requerir SSL o no (lo estoy usando con el preprocesador DEBUG).

 /* Note: * This is hand-rolled version of the original System.Web.Mvc.RequireHttpsAttribute. * This version contains three improvements: * - Allows to redirect back into http:// addresses, based on the  Requirement property. * - Allows to turn the protocol scheme redirection off based on given condition. * - Using Request.IsCurrentConnectionSecured() extension method, which contains fix for load-balanced servers. */ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)] public sealed class RequireHttpsAttribute : FilterAttribute, IAuthorizationFilter { public RequireHttpsAttribute() { Protocol = ConnectionProtocol.Ignore; } ///  /// Gets or sets the secure connection required protocol scheme level ///  public ConnectionProtocol Protocol { get; set; } ///  /// Gets the value that indicates if secure connections are been allowed ///  public bool SecureConnectionsAllowed { get { #if DEBUG return false; #else return true; #endif } } public void OnAuthorization(System.Web.Mvc.AuthorizationContext filterContext) { if (filterContext == null) { throw new ArgumentNullException("filterContext"); } /* Are we allowed to use secure connections? */ if (!SecureConnectionsAllowed) return; switch (Protocol) { case ConnectionProtocol.Https: if (!filterContext.HttpContext.Request.IsCurrentConnectionSecured()) { HandleNonHttpsRequest(filterContext); } break; case ConnectionProtocol.Http: if (filterContext.HttpContext.Request.IsCurrentConnectionSecured()) { HandleNonHttpRequest(filterContext); } break; } } private void HandleNonHttpsRequest(AuthorizationContext filterContext) { // only redirect for GET requests, otherwise the browser might not propagate the verb and request // body correctly. if (!String.Equals(filterContext.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase)) { throw new InvalidOperationException("The requested resource can only be accessed via SSL."); } // redirect to HTTPS version of page string url = "https://" + filterContext.HttpContext.Request.Url.Host + filterContext.HttpContext.Request.RawUrl; filterContext.Result = new RedirectResult(url); } private void HandleNonHttpRequest(AuthorizationContext filterContext) { if (!String.Equals(filterContext.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase)) { throw new InvalidOperationException("The requested resource can only be accessed without SSL."); } // redirect to HTTP version of page string url = "http://" + filterContext.HttpContext.Request.Url.Host + filterContext.HttpContext.Request.RawUrl; filterContext.Result = new RedirectResult(url); } } 

Ahora, este RequireSsl hará la siguiente base en su valor de atributo de requisitos: – Ignorar: no hará nada. – Http: forzará la redirección al protocolo http. – Https: forzará la redirección al protocolo https.

Debe crear su propio controlador base y establecer este atributo en Http.

 [RequireSsl(Requirement = ConnectionProtocol.Http)] public class MyController : Controller { public MyController() { } } 

Ahora, en cada cpntroller / acción que desea requerir SSL, simplemente configure este atributo con ConnectionProtocol.Https.

Ahora pasemos a las URL: tuvimos pocos problemas con el motor de enrutamiento de URL. Puede leer más sobre ellos en http://blog.stevensanderson.com/2008/08/05/adding-httpsssl-support-to-aspnet-mvc-routing/ . La solución sugerida en este post es teóricamente buena, pero vieja y no me gusta el enfoque.

Mi solución es la siguiente: crear una subclase de la clase básica “Ruta”:

clase pública AbsoluteUrlRoute: Route {#region ctor

  ///  /// Initializes a new instance of the System.Web.Routing.Route class, by using /// the specified URL pattern and handler class. ///  /// The URL pattern for the route. /// The object that processes requests for the route. public AbsoluteUrlRoute(string url, IRouteHandler routeHandler) : base(url, routeHandler) { } ///  /// Initializes a new instance of the System.Web.Routing.Route class, by using /// the specified URL pattern and handler class. ///  /// The URL pattern for the route. /// The values to use for any parameters that are missing in the URL. /// The object that processes requests for the route. public AbsoluteUrlRoute(string url, RouteValueDictionary defaults, IRouteHandler routeHandler) : base(url, defaults, routeHandler) { } ///  /// Initializes a new instance of the System.Web.Routing.Route class, by using /// the specified URL pattern and handler class. ///  /// The URL pattern for the route. /// The values to use for any parameters that are missing in the URL. /// A regular expression that specifies valid values for a URL parameter. /// The object that processes requests for the route. public AbsoluteUrlRoute(string url, RouteValueDictionary defaults, RouteValueDictionary constraints, IRouteHandler routeHandler) : base(url, defaults, constraints, routeHandler) { } ///  /// Initializes a new instance of the System.Web.Routing.Route class, by using /// the specified URL pattern and handler class. ///  /// The URL pattern for the route. /// The values to use for any parameters that are missing in the URL. /// A regular expression that specifies valid values for a URL parameter. /// Custom values that are passed to the route handler, but which are not used /// to determine whether the route matches a specific URL pattern. These values /// are passed to the route handler, where they can be used for processing the /// request. /// The object that processes requests for the route. public AbsoluteUrlRoute(string url, RouteValueDictionary defaults, RouteValueDictionary constraints, RouteValueDictionary dataTokens, IRouteHandler routeHandler) : base(url, defaults, constraints, dataTokens, routeHandler) { } #endregion public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values) { var virtualPath = base.GetVirtualPath(requestContext, values); if (virtualPath != null) { var scheme = "http"; if (this.DataTokens != null && (string)this.DataTokens["scheme"] != string.Empty) { scheme = (string) this.DataTokens["scheme"]; } virtualPath.VirtualPath = MakeAbsoluteUrl(requestContext, virtualPath.VirtualPath, scheme); return virtualPath; } return null; } #region Helpers ///  /// Creates an absolute url ///  /// The request context /// The initial virtual relative path /// The protocol scheme /// The absolute URL private string MakeAbsoluteUrl(RequestContext requestContext, string virtualPath, string scheme) { return string.Format("{0}://{1}{2}{3}{4}", scheme, requestContext.HttpContext.Request.Url.Host, requestContext.HttpContext.Request.ApplicationPath, requestContext.HttpContext.Request.ApplicationPath.EndsWith("/") ? "" : "/", virtualPath); } #endregion } 

Esta versión de la clase “Ruta” creará url absoluta. El truco aquí, seguido de la sugerencia del autor de la publicación de blog, es utilizar DataToken para especificar el esquema (ejemplo al final :)).

Ahora, si generamos una URL, por ejemplo, para la ruta “Cuenta / LogOn”, obtendremos “/ http://example.com/Account/LogOn “, ya que el UrlRoutingModule considera que todas las URL son relativas. Podemos arreglar eso usando HttpModule personalizado:

 public class AbsoluteUrlRoutingModule : UrlRoutingModule { protected override void Init(System.Web.HttpApplication application) { application.PostMapRequestHandler += application_PostMapRequestHandler; base.Init(application); } protected void application_PostMapRequestHandler(object sender, EventArgs e) { var wrapper = new AbsoluteUrlAwareHttpContextWrapper(((HttpApplication)sender).Context); } public override void PostResolveRequestCache(HttpContextBase context) { base.PostResolveRequestCache(new AbsoluteUrlAwareHttpContextWrapper(HttpContext.Current)); } private class AbsoluteUrlAwareHttpContextWrapper : HttpContextWrapper { private readonly HttpContext _context; private HttpResponseBase _response = null; public AbsoluteUrlAwareHttpContextWrapper(HttpContext context) : base(context) { this._context = context; } public override HttpResponseBase Response { get { return _response ?? (_response = new AbsoluteUrlAwareHttpResponseWrapper(_context.Response)); } } private class AbsoluteUrlAwareHttpResponseWrapper : HttpResponseWrapper { public AbsoluteUrlAwareHttpResponseWrapper(HttpResponse response) : base(response) { } public override string ApplyAppPathModifier(string virtualPath) { int length = virtualPath.Length; if (length > 7 && virtualPath.Substring(0, 7) == "/http:/") return virtualPath.Substring(1); else if (length > 8 && virtualPath.Substring(0, 8) == "/https:/") return virtualPath.Substring(1); return base.ApplyAppPathModifier(virtualPath); } } } } 

Como este módulo está anulando la implementación básica de UrlRoutingModule, deberíamos eliminar el httpModule base y registrar el nuestro en web.config. Entonces, bajo “system.web” establece:

      

Eso es :).

Para registrar una ruta absoluta / protocolo seguida, debe hacer:

  routes.Add(new AbsoluteUrlRoute("Account/LogOn", new MvcRouteHandler()) { Defaults = new RouteValueDictionary(new {controller = "Account", action = "LogOn", area = ""}), DataTokens = new RouteValueDictionary(new {scheme = "https"}) }); 

Me encantará escuchar tus comentarios + mejoras. Espero que pueda ayudar! 🙂

Editar: Olvidé incluir el método de extensión IsCurrentConnectionSecured () (demasiados fragmentos: P). Este es un método de extensión que generalmente usa Request.IsSecuredConnection. Sin embargo, este enfoque no funcionará cuando se utilice el equilibrio de carga, por lo que este método puede eludir esto (tomado de nopCommerce).

  ///  /// Gets a value indicating whether current connection is secured ///  /// The base request context /// true - secured, false - not secured /// < ![CDATA[ This method checks whether or not the connection is secured. /// There's a standard Request.IsSecureConnection attribute, but it won't be loaded correctly in case of load-balancer. /// See: nopCommerce WebHelper IsCurrentConnectionSecured()]]> public static bool IsCurrentConnectionSecured(this HttpRequestBase request) { return request != null && request.IsSecureConnection; // when your hosting uses a load balancer on their server then the Request.IsSecureConnection is never got set to true, use the statement below // just uncomment it //return request != null && request.ServerVariables["HTTP_CLUSTER_HTTPS"] == "on"; } 

Aquí hay una publicación de blog de Pablo M. Cibrano de enero de 2009 que reúne un par de técnicas, incluyendo un HttpModule y métodos de extensión.

Aquí hay una entrada de blog de Adam Salvo que usa un ActionFilter.

Esto no es necesariamente específico de MVC, pero esta solución funciona tanto para ASP.NET WebForms como para MVC:

http://www.codeproject.com/KB/web-security/WebPageSecurity_v2.aspx

Lo he usado durante varios años y me gusta la separación de preocupaciones y administración a través del archivo web.config.

MVC 6 (ASP.NET Core 1.0) funciona ligeramente diferente con Startup.cs.

Para usar RequireHttpsAttribute (como se menciona en la respuesta de Amadiere) en todas las páginas, puede agregar esto en Startup.cs en lugar de usar el estilo de atributo en cada controlador (o en lugar de crear un BaseController del que heredarán todos sus controladores).

Startup.cs – filtro de registro:

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

Para obtener más información sobre las decisiones de diseño para el enfoque anterior, vea mi respuesta a una pregunta similar sobre cómo excluir que las solicitudes de localhost sean manejadas por RequireHttpsAttribute .