web-dev-qa-db-fra.com

Pages SSL sous ASP.NET MVC

Comment utiliser HTTPS pour certaines des pages de mon site basé sur ASP.NET MVC?

Steve Sanderson a un très bon tutoriel sur la façon de procéder de manière DRY dans Preview 4 à l'adresse:

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

Y a-t-il un moyen meilleur/mis à jour avec Preview 5?

80
David Laing

Si vous utilisez ASP.NET MVC 2 Preview 2 ou supérieur , vous pouvez désormais utiliser simplement:

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

Cependant, le paramètre order mérite d'être noté, comme mentionné ici .

92
Amadiere

MVCFutures a un attribut 'RequireSSL'.

(merci Adam pour en le soulignant dans votre blog mis à jour)

Appliquez-le simplement à votre méthode d'action, avec 'Redirect = true' si vous souhaitez qu'une requête http: // devienne automatiquement https: //:

    [RequireSsl(Redirect = true)]

Voir aussi: ASP.NET MVC RequireHttps en production uniquement

17
Simon_Weaver

Comme Amadiere a écrit , [RequireHttps] fonctionne très bien dans MVC 2 pour entrer HTTPS. Mais si vous voulez uniquement utiliser HTTPS pour certaines pages comme vous l'avez dit, MVC 2 ne vous donne aucun amour - une fois qu'un utilisateur passe à HTTPS, il est bloqué jusqu'à ce que vous le redirigiez manuellement.

L'approche que j'ai utilisée consiste à utiliser un autre attribut personnalisé, [ExitHttpsIfNotRequired]. Lorsqu'il est attaché à un contrôleur ou à une action, il sera redirigé vers HTTP si:

  1. La demande était HTTPS
  2. L'attribut [RequireHttps] n'a pas été appliqué à l'action (ou au contrôleur)
  3. La demande était un GET (rediriger un POST entraînerait toutes sortes de problèmes).

C'est un peu trop gros pour poster ici, mais vous pouvez voir le code ici } ainsi que quelques détails supplémentaires.

9
Luke Sampson

Voici un article récent de Dan Wahlin à ce sujet:

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

Il utilise un attribut ActionFilter.

8
klabranche

Certaines extensions ActionLink: http://www.squaredroot.com/post/2008/06/11/MVC-and-SSL.aspx Ou un attribut d’action de contrôleur qui redirige vers https: // http://forums.asp.net/p/1260198/2358380.aspx#2358380

3
David Laing

Pour ceux qui ne sont pas fan des approches de développement axées sur les attributs, voici un morceau de code qui pourrait aider:

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

Il y a plusieurs raisons d'éviter les attributs et l'une d'entre elles est que si vous souhaitez consulter la liste de toutes les pages sécurisées, vous devez passer par-dessus tous les contrôleurs de la solution. 

3
user1015515

Je suis allé à travers cette question et espère que ma solution peut aider quelqu'un.

Nous avons eu quelques problèmes: - Nous devons sécuriser des actions spécifiques, par exemple "Connexion" dans "Compte". Nous pouvons utiliser l'attribut RequireHttps dans la construction, ce qui est excellent - mais il nous redirigera avec https: //. - Nous devrions rendre nos liens, formulaires et autres "conscients de SSL".

De manière générale, ma solution permet de spécifier des itinéraires qui utiliseront une adresse URL absolue, en plus de la possibilité de spécifier le protocole. Vous pouvez utiliser cette approche pour spécifier le protocole "https".

Donc, premièrement, j'ai créé un enum 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
}

Maintenant, j'ai créé une version roulée à la main de RequireSsl. J'ai modifié le code source RequireSsl d'origine pour autoriser la redirection vers les URL http: //. De plus, j'ai mis un champ qui nous permet de déterminer si nous devrions exiger ou non le protocole SSL (je l'utilise avec le pré-processeur 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);
    }
}

Maintenant, ce RequireSsl fera la base suivante sur la valeur de votre attribut Exigences: - Ignorer: Ne fera rien . - Http: forcera la redirection vers le protocole http . - Https: forcera la redirection vers le protocole https.

Vous devez créer votre propre contrôleur de base et définir cet attribut sur Http.

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

Maintenant, dans chaque action/cpntroller pour laquelle vous souhaitez utiliser SSL, il suffit de définir cet attribut avec ConnectionProtocol.Https.

Passons maintenant aux URL: Nous avons eu peu de problèmes avec le moteur de routage d’URL. Vous pouvez en savoir plus à leur sujet à http://blog.stevensanderson.com/2008/08/05/adding-httpsssl-support-to-aspnet-mvc-routing/ . La solution proposée dans cet article est théoriquement bonne, mais ancienne et je n'aime pas l'approche.

Ma solution est la suivante: Créer une sous-classe de la classe "Route" de base:

classe publique AbsoluteUrlRoute: Route { #région 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
}

Cette version de la classe "Route" créera une URL absolue. L'astuce ici, suivie de la suggestion de l'auteur du blog, consiste à utiliser DataToken pour spécifier le schéma (exemple à la fin :)).

Maintenant, si nous générons une URL, par exemple pour la route "Account/LogOn", nous aurons "/ http://example.com/Account/LogOn " - c'est depuis que UrlRoutingModule voit tous les URL en tant que parent. Nous pouvons résoudre ce problème en utilisant HttpModule personnalisé:

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

Comme ce module remplace l'implémentation de base de UrlRoutingModule, nous devrions supprimer le httpModule de base et enregistrer le nôtre dans web.config. Donc, sous "system.web", définissez: 

<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>

C'est tout :).

Pour enregistrer un itinéraire suivi par un protocole/absolu, vous devez procéder comme suit:

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

J'adorerai entendre vos commentaires + améliorations. J'espère que ça peut aider! :)

Edit: J'ai oublié d'inclure la méthode d'extension IsCurrentConnectionSecured () (trop d'extraits de code: P). Il s'agit d'une méthode d'extension qui utilise généralement Request.IsSecuredConnection. Cependant, cette approche ne fonctionnera pas avec l'équilibrage de charge - cette méthode peut donc contourner ce problème (tiré 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";
    }
2
Gindi Bar Yahav

Ce n'est pas nécessairement spécifique à MVC, mais cette solution fonctionne à la fois pour ASP.NET WebForms et MVC:

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

Je l'utilise depuis plusieurs années et j'aime bien la séparation des préoccupations et de la gestion via le fichier web.config.

1
Steven Pena

Voici un billet de blog de Pablo M. Cibrano de janvier 2009 qui rassemble quelques techniques, dont un HttpModule et des méthodes d'extension.

1
Robin Minto

Voici un billet de blog d'Adam Salvo qui utilise un ActionFilter.

1
Robin Minto

MVC 6 (ASP.NET Core 1.0) fonctionne légèrement différemment avec Startup.cs.

Pour utiliser RequireHttpsAttribute (comme indiqué dans answer par Amadiere) sur toutes les pages, vous pouvez l'ajouter dans Startup.cs au lieu d'utiliser un style d'attribut sur chaque contrôleur (ou au lieu de créer un BaseController pour l'héritage de tous vos contrôleurs). .

Startup.cs - filtre de registre:

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

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

Pour plus d'informations sur les décisions de conception pour l'approche ci-dessus, voir ma réponse à la question similaire à propos de comment exclure les demandes d'hôte local du traitement de RequireHttpsAttribute .

0
Nick Niebling