SSL-Seiten unter ASP.NET MVC

80

Wie verwende ich HTTPS für einige Seiten meiner ASP.NET MVC-basierten Site?

Steve Sanderson hat in Preview 4 ein ziemlich gutes Tutorial dazu, wie man das auf DRY-Weise macht:

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

Gibt es einen besseren / aktualisierten Weg mit Preview 5?,

David Laing
quelle
3
Das ist sehr veraltet. Informationen zu MVC4 und höher finden Sie in meinem Blogbeitrag blogs.msdn.com/b/rickandy/archive/2012/03/23/…
RickAndMSFT

Antworten:

92

Wenn Sie ASP.NET MVC 2 Preview 2 oder höher verwenden , können Sie jetzt einfach Folgendes verwenden:

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

Der hier erwähnte Bestellparameter ist jedoch erwähnenswert .

Amadiere
quelle
23
Sie können dies auch auf Controller-Ebene tun. Besser noch, wenn Sie möchten, dass die gesamte Anwendung SSL ist, können Sie einen Basis-Controller erstellen, ihn für alle Controller erweitern und das Attribut dort anwenden.
Asche999
22
Alternativ können Sie hinzufügen, dass es sich um einen globalen Filter MVC3 in Global.asax GlobalFilters.Filters.Add (new RequireHttpsAttribute ()) handelt.
GraemeMiller
2
Kein garantierter anderer Entwickler wird Ihren abgeleiteten Controller verwenden. Sie können einen Anruf tätigen, um HTTPS zu erzwingen - siehe meinen Blog-Beitrag blogs.msdn.com/b/rickandy/archive/2012/03/23/…
RickAndMSFT
17

MVCFutures verfügt über das Attribut 'RequireSSL'.

(Danke Adam , dass er in deinem aktualisierten Blogpost darauf hingewiesen hat )

Wenden Sie es einfach mit 'Redirect = true' auf Ihre Aktionsmethode an, wenn eine http: // -Anforderung automatisch zu https: //: werden soll.

    [RequireSsl(Redirect = true)]

Siehe auch: ASP.NET MVC RequireHttps nur in der Produktion

Simon_Weaver
quelle
Müsste ich es in Unterklassen unterteilen, um Localhost-Anfragen zu bearbeiten?
Herr Rogers
Eine Möglichkeit besteht darin, ein Zertifikat für Ihren lokalen Computer zu erstellen und dieses zu verwenden. Ich denke, um es für localhost vollständig zu deaktivieren, müssten Sie in der Tat den Code unterklassifizieren oder duplizieren.
Ich bin
1
Sieht so aus, als wäre es versiegelt, also müsste ich den Code betrügen. Schade. Das Zertifikat für den lokalen Computer funktioniert zwar nur in IIS, nicht jedoch auf dem Dev-Webserver.
Herr Rogers
@ Herr Rogers - werfen Sie einen Blick darauf: stackoverflow.com/questions/1639707/…
Simon_Weaver
Aktualisierung auf MVC4 + siehe meinen Blog-Beitrag blogs.msdn.com/b/rickandy/archive/2012/03/23/…
RickAndMSFT
9

Wie Amadiere schrieb , funktioniert [RequireHttps] in MVC 2 hervorragend für die Eingabe von HTTPS. Wenn Sie HTTPS jedoch nur für einige Seiten verwenden möchten, wie Sie sagten, gibt Ihnen MVC 2 keine Liebe - sobald ein Benutzer auf HTTPS umgeschaltet wird, bleibt er dort hängen, bis Sie ihn manuell umleiten.

Der Ansatz, den ich verwendet habe, besteht darin, ein anderes benutzerdefiniertes Attribut zu verwenden, [ExitHttpsIfNotRequired]. Wenn es an einen Controller oder eine Aktion angeschlossen ist, wird dies zu HTTP umgeleitet, wenn:

  1. Die Anfrage war HTTPS
  2. Das Attribut [RequireHttps] wurde nicht auf die Aktion (oder den Controller) angewendet.
  3. Die Anfrage war ein GET (das Umleiten eines POST würde zu allen möglichen Problemen führen).

Es ist etwas zu groß, um hier zu posten, aber Sie können den Code hier sowie einige zusätzliche Details sehen.

Luke Sampson
quelle
AllowAnonymous behebt das. Für MVC4 und höher siehe meinen Blog-Beitrag blogs.msdn.com/b/rickandy/archive/2012/03/23/…
RickAndMSFT
8

Hier ist ein kürzlich veröffentlichter Beitrag von Dan Wahlin dazu:

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

Er verwendet ein ActionFilter-Attribut.

Kevin LaBranche
quelle
2
Dies scheint im Moment der beste Weg zu sein.
Royco
+1 ein Jahr später, als der isLocal-Anruf mir half, ein Problem zu lösen, das im @@@
heisenberg
1
Das obige ist datiert. Für MVC4 und höher siehe meinen Blog-Beitrag blogs.msdn.com/b/rickandy/archive/2012/03/23/…
RickAndMSFT
3

Für diejenigen, die kein Fan von attributorientierten Entwicklungsansätzen sind, gibt es hier einen Code, der helfen könnte:

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

Es gibt mehrere Gründe, Attribute zu vermeiden. Einer davon ist, dass Sie, wenn Sie sich die Liste aller gesicherten Seiten ansehen möchten, über alle Controller in Lösung springen müssen.

user1015515
quelle
Ich denke, die meisten Leute würden Ihnen in diesem Punkt nicht zustimmen, obwohl es immer nützlich ist, einen alternativen Weg anzugeben ...
Serj Sagan
2

Ich habe diese Frage beantwortet und hoffe, dass meine Lösung jemandem helfen kann.

Wir haben einige Probleme: - Wir müssen bestimmte Aktionen sichern, zum Beispiel "LogOn" in "Account". Wir können das Build-in-RequireHttps-Attribut verwenden, was großartig ist - aber es leitet uns mit https: // zurück. - Wir sollten unsere Links, Formulare und solche "SSL-fähig" machen.

Im Allgemeinen ermöglicht meine Lösung die Angabe von Routen, die eine absolute URL verwenden, zusätzlich zur Möglichkeit, das Protokoll anzugeben. Mit diesem Ansatz können Sie das "https" -Protokoll angeben.

Also habe ich zuerst eine ConnectionProtocol-Enumeration erstellt:

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

Jetzt habe ich eine handgerollte Version von RequireSsl erstellt. Ich habe den ursprünglichen RequireSsl-Quellcode geändert, um die Umleitung zurück zu http: // urls zu ermöglichen. Außerdem habe ich ein Feld eingefügt, mit dem wir bestimmen können, ob SSL erforderlich ist oder nicht (ich verwende es mit dem DEBUG-Vorprozessor).

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

Jetzt führt diese RequireSsl die folgende Basis basierend auf Ihrem Anforderungsattributwert aus: - Ignorieren: Nichts tun. - HTTP: Erzwingt die Umleitung zum http-Protokoll. - Https: Erzwingt die Umleitung zum https-Protokoll.

Sie sollten Ihren eigenen Basis-Controller erstellen und dieses Attribut auf HTTP setzen.

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

Jetzt legen Sie in jedem cpntroller / jeder Aktion, für die Sie SSL benötigen, dieses Attribut einfach mit ConnectionProtocol.Https fest.

Wechseln wir jetzt zu URLs: Wir haben einige Probleme mit der URL-Routing-Engine. Weitere Informationen finden Sie unter http://blog.stevensanderson.com/2008/08/05/adding-httpsssl-support-to-aspnet-mvc-routing/ . Die in diesem Beitrag vorgeschlagene Lösung ist theoretisch gut, aber alt und ich mag den Ansatz nicht.

Meine Lösungen lauten wie folgt: Erstellen Sie eine Unterklasse der Basisklasse "Route":

öffentliche Klasse AbsoluteUrlRoute: Route {#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
}

Diese Version der "Route" -Klasse erstellt eine absolute URL. Der Trick hier, gefolgt vom Vorschlag des Blogpost-Autors, besteht darin, das Schema mithilfe des DataToken anzugeben (Beispiel am Ende :)).

Wenn wir nun eine URL generieren, zum Beispiel für die Route "Account / LogOn", erhalten wir "/ http://example.com/Account/LogOn " - da das UrlRoutingModule alle URLs als relativ ansieht. Wir können das mit benutzerdefiniertem HttpModule beheben:

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

Da dieses Modul die Basisimplementierung von UrlRoutingModule überschreibt, sollten wir das Basis-httpModule entfernen und unser Modul in web.config registrieren. Also unter "system.web" setzen:

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

Das ist es :).

Um eine absolute / protokollierte Route zu registrieren, sollten Sie Folgendes tun:

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

Ich werde gerne Ihr Feedback + Verbesserungen hören. Hoffe es kann helfen! :) :)

Bearbeiten: Ich habe vergessen, die Erweiterungsmethode IsCurrentConnectionSecured () einzuschließen (zu viele Snippets: P). Dies ist eine Erweiterungsmethode, die im Allgemeinen Request.IsSecuredConnection verwendet. Dieser Ansatz funktioniert jedoch nicht, wenn der Lastenausgleich verwendet wird. Daher kann diese Methode dies umgehen (aus nopCommerce übernommen).

    /// <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
quelle
1

Hier ist ein Blog-Beitrag von Pablo M. Cibrano aus dem Januar 2009, in dem einige Techniken zusammengefasst sind, darunter ein HttpModule und Erweiterungsmethoden.

Robin Minto
quelle
1

Dies ist nicht unbedingt MVC-spezifisch, aber diese Lösung funktioniert sowohl für ASP.NET WebForms als auch für MVC:

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

Ich habe dies seit mehreren Jahren verwendet und mag die Trennung von Bedenken und Verwaltung über die Datei web.config.

Steven Pena
quelle
0

MVC 6 (ASP.NET Core 1.0) funktioniert mit Startup.cs etwas anders.

Um RequireHttpsAttribute (wie in der Antwort von Amadiere erwähnt) auf allen Seiten zu verwenden, können Sie dies in Startup.cs hinzufügen, anstatt den Attributstil auf jedem Controller zu verwenden (oder einen BaseController zu erstellen, von dem alle Ihre Controller erben können).

Startup.cs - Registerfilter:

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

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

Weitere Informationen zu Entwurfsentscheidungen für den oben genannten Ansatz finden Sie in meiner Antwort auf eine ähnliche Frage zum Ausschließen von Localhost-Anforderungen von der Verarbeitung durch RequireHttpsAttribute .

Nick Niebling
quelle
0

Fügen Sie alternativ einen Filter zu Global.asax.cs hinzu

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

RequireHttpsAttribute-Klasse

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
quelle