Páginas SSL en ASP.NET MVC

80

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

Steve Sanderson tiene un tutorial bastante bueno sobre cómo hacer esto de forma SECA en Preview 4 en:

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

¿Existe una forma mejor / actualizada con Preview 5?

David Laing
fuente
3
Esto está muy anticuado. Para MVC4 y superior, consulte la publicación de mi blog blogs.msdn.com/b/rickandy/archive/2012/03/23/…
RickAndMSFT

Respuestas:

92

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

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

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

Amadiere
fuente
23
También puede hacer esto en el nivel del controlador. Mejor aún, si desea que toda la aplicación sea SSL, puede crear un controlador base, extenderlo para todos los controladores y aplicar el atributo allí.
cenizas999
22
Alternativamente, puede agregar que es un filtro global MVC3 en Global.asax GlobalFilters.Filters.Add (new RequireHttpsAttribute ());
GraemeMiller
2
Ningún otro desarrollador garantizado utilizará su controlador derivado. Puede hacer una llamada para forzar HTTPS; consulte mi publicación de blog blogs.msdn.com/b/rickandy/archive/2012/03/23/…
RickAndMSFT
17

MVCFutures tiene un atributo 'RequireSSL'.

(gracias Adam por señalarlo en tu publicación de blog actualizada)

Simplemente aplíquelo a su método de acción, con 'Redirect = true' si desea que una solicitud http: // se convierta automáticamente en https: //:

    [RequireSsl(Redirect = true)]

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

Simon_Weaver
fuente
¿Tendría que subclasificarlo para manejar las solicitudes de localhost?
Mr Rogers
una forma es crear un certificado para su máquina local y usarlo. Creo que para deshabilitarlo por completo para localhost, de hecho, necesitaría crear una subclase o duplicar el código. no estoy seguro de cuál es el enfoque recomendado
Simon_Weaver
1
Parece que está sellado, así que tendría que engañar al código. Gorrón. El certificado para la máquina local solo funcionaría en IIS, aunque correcto, no en el servidor web de desarrollo.
Mr Rogers
@mr rogers - eche un vistazo a esto: stackoverflow.com/questions/1639707/…
Simon_Weaver
Actualizando esto a MVC4 + vea mi publicación de blog blogs.msdn.com/b/rickandy/archive/2012/03/23/…
RickAndMSFT
9

Como escribió Amadiere , [RequireHttps] funciona muy bien en MVC 2 para ingresar HTTPS. Pero si solo desea usar HTTPS para algunas páginas como dijo, MVC 2 no le da ningún amor: una vez que cambia a un usuario a HTTPS, se atascan allí hasta que los redirige manualmente.

El enfoque que utilicé es utilizar otro atributo personalizado, [ExitHttpsIfNotRequired]. Cuando se adjunta a un controlador o acción, se 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 fue un GET (redireccionar un POST daría lugar a todo tipo de problemas).

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

Luke Sampson
fuente
AllowAnonymous corrige eso. Para MVC4 y superior, consulte mi publicación de blog blogs.msdn.com/b/rickandy/archive/2012/03/23/…
RickAndMSFT
8

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

Utiliza un atributo ActionFilter.

Kevin LaBranche
fuente
2
Esta parece ser la mejor manera en este momento.
royco
+1 un año después, ya que la llamada de isLocal me ayudó a resolver un problema que se estaba convirtiendo en un verdadero dolor de
cabeza
1
Lo anterior está fechado, para MVC4 y superior, consulte mi publicación de blog blogs.msdn.com/b/rickandy/archive/2012/03/23/…
RickAndMSFT
3

Para aquellos que no son fanáticos de los enfoques de desarrollo orientados a atributos, aquí hay un fragmento 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 pasar por alto todos los controladores en la solución.

usuario1015515
fuente
Creo que la mayoría de la gente no estaría de acuerdo contigo en este caso, aunque proporcionar una forma alternativa siempre es útil ...
Serj Sagan
2

Crucé 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 compilación en el atributo RequireHttps, lo cual es genial, pero nos redirigirá con https: //. - Debemos hacer que nuestros enlaces, formularios y tales "sean compatibles con SSL".

Generalmente, mi solución permite especificar rutas que usarán una URL absoluta, además de la capacidad de especificar el protocolo. Puede utilizar este enfoque para especificar el protocolo "https".

Entonces, en primer lugar, he creado una enumeración ConnectionProtocol:

/// <summary>
/// Enum representing the available secure connection requirements
/// </summary>
public enum ConnectionProtocol
{
    /// <summary>
    /// No secure connection requirement
    /// </summary>
    Ignore,

    /// <summary>
    /// No secure connection should be used, use standard http request.
    /// </summary>
    Http,

    /// <summary>
    /// The connection should be secured using SSL (https protocol).
    /// </summary>
    Https
}

Ahora, he creado una versión enrollada a mano de RequireSsl. He modificado el código fuente de 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 <see cref="SecureConnectionRequirement" /> 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;
    }

    /// <summary>
    /// Gets or sets the secure connection required protocol scheme level
    /// </summary>
    public ConnectionProtocol Protocol { get; set; }

    /// <summary>
    /// Gets the value that indicates if secure connections are been allowed
    /// </summary>
    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 el valor de su 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 / action que le gustaría requerir SSL, simplemente configure este atributo con ConnectionProtocol.Https.

Ahora pasemos a las URL: tenemos algunos 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 esta publicación es teóricamente buena, pero vieja y no me gusta el enfoque.

Mis soluciones son las siguientes: Cree una subclase de la clase básica "Ruta":

clase pública AbsoluteUrlRoute: Ruta {#region ctor

    /// <summary>
    /// Initializes a new instance of the System.Web.Routing.Route class, by using
    ///     the specified URL pattern and handler class.
    /// </summary>
    /// <param name="url">The URL pattern for the route.</param>
    /// <param name="routeHandler">The object that processes requests for the route.</param>
    public AbsoluteUrlRoute(string url, IRouteHandler routeHandler)
        : base(url, routeHandler)
    {

    }

    /// <summary>
    /// Initializes a new instance of the System.Web.Routing.Route class, by using
    ///     the specified URL pattern and handler class.
    /// </summary>
    /// <param name="url">The URL pattern for the route.</param>
    /// <param name="defaults">The values to use for any parameters that are missing in the URL.</param>
    /// <param name="routeHandler">The object that processes requests for the route.</param>
    public AbsoluteUrlRoute(string url, RouteValueDictionary defaults, IRouteHandler routeHandler)
        : base(url, defaults, routeHandler)
    {

    }

    /// <summary>
    /// Initializes a new instance of the System.Web.Routing.Route class, by using
    ///     the specified URL pattern and handler class.
    /// </summary>
    /// <param name="url">The URL pattern for the route.</param>
    /// <param name="defaults">The values to use for any parameters that are missing in the URL.</param>
    /// <param name="constraints">A regular expression that specifies valid values for a URL parameter.</param>
    /// <param name="routeHandler">The object that processes requests for the route.</param>
    public AbsoluteUrlRoute(string url, RouteValueDictionary defaults, RouteValueDictionary constraints,
                            IRouteHandler routeHandler)
        : base(url, defaults, constraints, routeHandler)
    {

    }

    /// <summary>
    /// Initializes a new instance of the System.Web.Routing.Route class, by using
    ///     the specified URL pattern and handler class.
    /// </summary>
    /// <param name="url">The URL pattern for the route.</param>
    /// <param name="defaults">The values to use for any parameters that are missing in the URL.</param>
    /// <param name="constraints">A regular expression that specifies valid values for a URL parameter.</param>
    /// <param name="dataTokens">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.</param>
    /// <param name="routeHandler">The object that processes requests for the route.</param>
    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

    /// <summary>
    /// Creates an absolute url
    /// </summary>
    /// <param name="requestContext">The request context</param>
    /// <param name="virtualPath">The initial virtual relative path</param>
    /// <param name="scheme">The protocol scheme</param>
    /// <returns>The absolute URL</returns>
    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á una URL absoluta. El truco aquí, seguido de la sugerencia del autor de la publicación del blog, es usar DataToken para especificar el esquema (ejemplo al final :)).

Ahora, si generamos una URL, por ejemplo, para la ruta "Account / LogOn" obtendremos "/ http://example.com/Account/LogOn ", ya que UrlRoutingModule ve todas las URL como 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);
            }
        }
    }
}

Dado que este módulo anula la implementación base de UrlRoutingModule, deberíamos eliminar el httpModule base y registrar el nuestro en web.config. Entonces, en "system.web", establezca:

<httpModules>
  <!-- Removing the default UrlRoutingModule and inserting our own absolute url routing module -->
  <remove name="UrlRoutingModule-4.0" />
  <add name="UrlRoutingModule-4.0" type="MyApp.Web.Mvc.Routing.AbsoluteUrlRoutingModule" />
</httpModules>

Eso es :).

Para registrar una ruta absoluta / seguida por protocolo, 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 sus comentarios y 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 use el equilibrio de carga, por lo que este método puede omitirlo (tomado de nopCommerce).

    /// <summary>
    /// Gets a value indicating whether current connection is secured
    /// </summary>
    /// <param name="request">The base request context</param>
    /// <returns>true - secured, false - not secured</returns>
    /// <remarks><![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: <a href="http://nopcommerce.codeplex.com/SourceControl/changeset/view/16de4a113aa9#src/Libraries/Nop.Core/WebHelper.cs">nopCommerce WebHelper IsCurrentConnectionSecured()</a>]]></remarks>
    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";
    }
Gindi Bar Yahav
fuente
0

MVC 6 (ASP.NET Core 1.0) funciona de forma 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 para que todos sus controladores hereden).

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, consulte mi respuesta a una pregunta similar sobre cómo excluir que las solicitudes de localhost sean manejadas por RequireHttpsAttribute .

Nick Niebling
fuente
0

Alternativamente agregue un filtro a Global.asax.cs

GlobalFilters.Filters.Add (nuevo RequireHttpsAttribute ());

RequireHttpsAttribute (clase)

using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;

namespace xxxxxxxx
{
    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            GlobalFilters.Filters.Add(new RequireHttpsAttribute());
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);
        }
    }
}
Chris Catignani
fuente