ASP.NET MVC RequireHttps nur in der Produktion

121

Ich möchte das RequireHttpsAttribute verwenden , um zu verhindern, dass ungesicherte HTTP-Anforderungen an eine Aktionsmethode gesendet werden.

C #

[RequireHttps] //apply to all actions in controller
public class SomeController 
{
    [RequireHttps] //apply to this action only
    public ActionResult SomeAction()
    {
        ...
    }
}

VB

<RequireHttps()> _
Public Class SomeController

    <RequireHttps()> _
    Public Function SomeAction() As ActionResult
        ...
    End Function

End Class

Leider unterstützt ASP.NET Development Server HTTPS nicht.

Wie kann ich meine ASP.NET MVC-Anwendung dazu bringen, RequireHttps zu verwenden, wenn sie in der Produktionsumgebung veröffentlicht wird, aber nicht, wenn sie auf meiner Entwicklungsarbeitsstation auf dem ASP.NET Development Server ausgeführt wird?

Zack Peterson
quelle
3
Testen Sie mit Ihrem lokalen IIS und mit IIS Express. Siehe mein SSL-Blog blogs.msdn.com/b/rickandy/archive/2011/04/22/… und blogs.msdn.com/b/rickandy/archive/2012/03/23/…
RickAndMSFT

Antworten:

129

Dies hilft nicht, wenn Sie Release-Builds auf Ihrer Entwicklungsarbeitsstation ausführen, aber die bedingte Kompilierung könnte den Job erledigen ...

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

}

Aktualisieren

In Visual Basic sind Attribute technisch Teil derselben Zeile wie die Definition, für die sie gelten. Sie können keine bedingten Kompilierungsanweisungen in eine Zeile einfügen, daher müssen Sie die Funktionsdeklaration zweimal schreiben - einmal mit dem Attribut und einmal ohne. Es funktioniert jedoch, wenn Ihnen die Hässlichkeit nichts ausmacht.

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

Update 2

Einige Leute haben erwähnt, dass sie RequireHttpsAttributeohne Beispiel stammen, also hier eines für Sie. Ich denke, dass dieser Ansatz viel sauberer wäre als der Ansatz der bedingten Kompilierung, und es wäre meine Präferenz in Ihrer Position.

HAFTUNGSAUSSCHLUSS: Ich habe diesen Code nicht einmal ein bisschen getestet und mein VB ist ziemlich verrostet. Ich weiß nur, dass es kompiliert. Ich schrieb es basierend auf den Vorschlägen von spot, Queen3 und Lance Fisher. Wenn es nicht funktioniert, sollte es zumindest die allgemeine Idee vermitteln und Ihnen einen Ausgangspunkt geben.

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

Grundsätzlich wird das neue Attribut nur beendet, anstatt den Standard-SSL-Autorisierungscode auszuführen, wenn die aktuelle Anforderung lokal ist (dh Sie greifen über localhost auf die Site zu). Sie können es so verwenden:

<RemoteRequireHttps()> _
Public Class SomeController

    <RemoteRequireHttps()> _
    Public Function SomeAction() As ActionResult
        ...
    End Function

End Class

Viel sauberer! Vorausgesetzt, mein nicht getesteter Code funktioniert tatsächlich.

Joel Mueller
quelle
Danke, dass du meinen Beitrag für mich bearbeitet hast, Zack. Ihre Frage war in C #, daher habe ich eine C # -Antwort gepostet. Ich wusste nicht, dass VB relevant ist. Weiß jemand, ob es eine Möglichkeit gibt, die bedingte Kompilierung zur Steuerung von Attributen in VB zu verwenden, oder ist das einfach nicht möglich?
Joel Mueller
Ja, es funktioniert für C # und auch für VB, aber Sie müssen die Funktions- / Klassendefinition ziemlich hässlich duplizieren. Siehe meine aktualisierte Antwort oben.
Joel Mueller
Es tut uns leid. VB-Codebeispiele werden immer schwieriger zu bekommen. Ich hätte nicht gedacht, dass es wichtig sein würde. Ich habe die ursprüngliche Frage aktualisiert. Funktioniert die bedingte Kompilierung von Attributen in C # mit Sicherheit? Ich habe nicht getestet. Das scheint eine perfekte, elegante Lösung zu sein.
Zack Peterson
Ihr RemoteRequireHttpsAttribute-Code funktioniert einwandfrei. Das ist für VB viel eleganter als die bedingte Kompilierung. Nochmals vielen Dank Joel.
Zack Peterson
2
Danke - das war genau das, was ich brauchte. Prost!
Davecoulter
65

Wenn jemand die C # -Version benötigt:

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);
        }
    }
}
mikesl
quelle
ok beim Lesen dieser und dies als Sicherheitsmaßnahme sollten wir hinzufügen , filters.Add(new MyRequireHttpsAttribute ());in FilterConfig?
Shaijut
Basierend auf dieser Antwort habe ich eine Lösung für MVC 6 erstellt , die entweder einen Filter in Startup.cs oder einen Attributstil auf Controller verwendet.
Nick Niebling
26

Das Ableiten von RequireHttps ist ein guter Ansatz.

Um das Problem vollständig zu umgehen, können Sie IIS auf Ihrem lokalen Computer auch mit einem selbstsignierten Zertifikat verwenden. IIS ist schneller als der integrierte Webserver, und Sie haben den Vorteil, dass Ihre Entwicklungsumgebung eher der Produktion ähnelt.

Scott Hanselman verfügt über eine hervorragende Ressource für einige Möglichkeiten zur Implementierung von lokalem HTTPS mit VS2010 und IIS Express.

Lance Fisher
quelle
ya - bis Sie versuchen, eine Portweiterleitung mit einem Mifi-WLAN-Verizon-Gerät durchzuführen und feststellen, dass Port 443 nicht für die Weiterleitung verfügbar ist !!! # * & # * & $
Simon_Weaver
Was mir an der Verwendung von IIS auf Ihrem lokalen Computer mit einem selbstsignierten Zertifikat nicht gefällt, ist, dass ich einen zusätzlichen Bereitstellungsschritt durchlaufen muss, um Änderungen zu testen. Ich denke, wenn Sie etwas testen, das mit Sicherheit zu tun hat, als es sinnvoll ist, aber sagen Sie, wenn Sie nur eine andere geringfügige Änderung überprüfen, ist es schwierig, eine Bereitstellung durchzuführen, um Cassinis Unfähigkeit zu umgehen, HTTPS zu unterstützen.
Davecoulter
1
@davecoulter - Verwenden Sie IIS Express auf Client-Versionen von Windows, ohne dass Cassini erforderlich ist, und es funktioniert genau wie IIS, einschließlich SSL-Funktionen.
Erik Funkenbusch
@Mystere Man - ja, das habe ich seit diesem Kommentar herausgefunden. Danke für den Tipp :)
Davecoulter
Weitere Informationen oder Links sollten hinzugefügt werden, wie solche Dinge zu tun sind.
Stephenbayer
12

Ich gehe davon aus, dass Sie das MVC-Filtersystem und Global.asax.cs nutzen können ...

    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());
      }
    }
gt124
quelle
Ich bevorzuge diese Antwort, da sie eine Prüfung pro Anwendungslebensdauer umfasst, anstatt einen neuen Filter zu implementieren, der bei jeder einzelnen Anforderung ausgeführt wird.
Abdulhameed
10

Da es der ASP.Net Development Server war, der Ihr Problem in erster Linie verursacht hat, ist es erwähnenswert, dass Microsoft jetzt über IIS Express verfügt , das mit Visual Studio geliefert wird (seit VS2010 SP1). Dies ist eine abgespeckte Version von IIS, die genauso einfach zu verwenden ist wie der Development Server, jedoch den gesamten Funktionsumfang von IIS 7.5 einschließlich SSL unterstützt.

Scott Hanselman hat einen detaillierten Beitrag zur Arbeit mit SSL in IIS Express .

Samuel Jack
quelle
9

Wie wäre es, das RequireHttps-Attribut in einem benutzerdefinierten Attribut zu erben? Überprüfen Sie dann in Ihrem benutzerdefinierten Attribut die IsLocal-Eigenschaft der aktuellen Anforderung, um festzustellen, ob die Anforderung vom lokalen Computer stammt. Wenn dies der Fall ist, wenden Sie die Basisfunktionalität nicht an. Andernfalls rufen Sie die Basisoperation auf.

Stelle
quelle
4

Dies funktionierte bei mir, MVC 6 (ASP.NET Core 1.0) . Der Code prüft, ob sich das Debug in der Entwicklung befindet. Andernfalls ist ssl nicht erforderlich. Alle Änderungen befinden sich in Startup.cs .

Hinzufügen:

private IHostingEnvironment CurrentEnvironment { get; set; }

Hinzufügen:

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

Bearbeiten:

public void ConfigureServices(IServiceCollection services)
{
    // additional services...

    services.AddMvc(options =>
    {
        if (!CurrentEnvironment.IsDevelopment())
        {
            options.Filters.Add(typeof(RequireHttpsAttribute));
        }
    });
}
Eric Beijner
quelle
3

Wenn Sie ableiten und überschreiben können, tun Sie es. Wenn Sie nicht können - MVC wird mit Quellen geliefert, nehmen Sie einfach die Quellen und erstellen Sie Ihr eigenes [ForceHttps] -Attribut, das IsLocal überprüft.

Königin3
quelle
3

Für MVC 3 habe ich meinen eigenen FilterProvider hinzugefügt (basierend auf dem hier gefundenen Code: Globale und bedingte Filter , die unter anderem (Anzeigen von Debug-Informationen für lokale Benutzer usw.) alle Aktionen mit RequireHttpsAttributewann dekorieren HttpContext.Request.IsLocal == false.

juhan_h
quelle
Oder Sie können es nur bedingt zur globalen Filtersammlung hinzufügen, wenn die Anforderung lokal ist. Beachten Sie, dass Sie dies in einem Try / Catch-Block überprüfen möchten, wenn die App sofort gestartet werden soll, da die Anforderung möglicherweise nicht verfügbar ist.
Tvanfosson
3

Nachdem ich mich umgesehen hatte, konnte ich dieses Problem mit IIS Express und einer Überschreibung der OnAuthorization-Methode der Controller-Klasse (Ref. 1) lösen. Ich bin auch die von Hanselman empfohlene Route gegangen (Ref. 2). Mit diesen beiden Lösungen war ich jedoch aus zwei Gründen nicht vollständig zufrieden: 1. Die OnAuthorization von Ref. 1 funktioniert nur auf Aktionsebene, nicht auf Controller-Klassenebene. 2. Ref. 2 erfordert viel Setup (Win7 SDK für makecert) ), netsh-Befehle, und um Port 80 und Port 443 zu verwenden, muss ich VS2010 als Administrator starten, was ich missbillige.

Also habe ich diese Lösung entwickelt, die sich auf Einfachheit unter den folgenden Bedingungen konzentriert:

  1. Ich möchte das Attbbute RequireHttps auf Controller-Klassen- oder Aktionsebene verwenden können

  2. Ich möchte, dass MVC HTTPS verwendet, wenn das RequireHttps-Attribut vorhanden ist, und HTTP verwendet, wenn es nicht vorhanden ist

  3. Ich möchte Visual Studio nicht als Administrator ausführen müssen

  4. Ich möchte alle HTTP- und HTTPS-Ports verwenden können, die von IIS Express zugewiesen wurden (siehe Hinweis 1).

  5. Ich kann das selbstsignierte SSL-Zertifikat von IIS Express wiederverwenden, und es ist mir egal, ob die ungültige SSL-Eingabeaufforderung angezeigt wird

  6. Ich möchte, dass Entwickler, Test und Produktion genau dieselbe Codebasis und dieselbe Binärdatei haben und so unabhängig wie möglich von zusätzlichen Einstellungen (z. B. Verwendung von Netsh, MMC-Zertifikat-Snap-In usw.) sind

Mit dem Hintergrund und der Erklärung aus dem Weg hoffe ich, dass dieser Code jemandem hilft und Zeit spart. Erstellen Sie grundsätzlich eine BaseController-Klasse, die von Controller erbt, und leiten Sie Ihre Controller-Klassen von dieser Basisklasse ab. Da Sie so weit gelesen haben, gehe ich davon aus, dass Sie wissen, wie man das macht. Also viel Spaß beim Codieren!

Hinweis 1: Dies wird durch die Verwendung einer nützlichen Funktion 'getConfig' erreicht (siehe Code).

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

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

========== Code in 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);
    }

============== Endcode ================

Fügen Sie in Web.Release.Config Folgendes hinzu, um HttpPort und HttpsPort zu löschen (um die Standardeinstellungen 80 und 443 zu verwenden).

<appSettings>
<add key="HttpPort" value="" xdt:Transform="SetAttributes" xdt:Locator="Match(key)"/>
<add key="HttpsPort" value="" xdt:Transform="SetAttributes" xdt:Locator="Match(key)"/>
</appSettings>
Leng Keng
quelle
3

Eine Lösung, die Sie sowohl in der Produktion als auch auf der Entwicklungsarbeitsstation verwenden können. Es basiert auf Ihrer Option aus den Anwendungseinstellungen in web.config

<appSettings>
     <!--Use SSL port 44300 in IIS Express on development workstation-->
     <add key="UseSSL" value="44300" />
</appSettings>

Wenn Sie kein SSL verwenden möchten, entfernen Sie den Schlüssel. Wenn Sie den Standard-SSL-Port 443 verwenden, entfernen Sie den Wert oder geben Sie 443 an.

Verwenden Sie dann die benutzerdefinierte Implementierung von RequireHttpsAttribute , die sich um Ihren Zustand kümmert. Es wird tatsächlich von RequireHttps abgeleitet und verwendet dieselbe Implementierung der Basismethode, außer dass Bedingungen hinzugefügt werden.

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

Vergessen Sie nicht, die LogOn- Methode in AccountController zu dekorieren

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

und so etwas in Ihrer LogOn- Ansicht, um ein Formular über https zu veröffentlichen.

<% using (Html.BeginFormSecure("LogOn", "Account", new { ReturnUrl = Request.QueryString["ReturnUrl"] }, Request.IsSecureConnection, Request.Url)) { %>
Nick
quelle
Ich erhalte die folgende Fehlermeldung: XMLHttpRequest kann m.XXX.com/Auth/SignIn nicht laden . In der angeforderten Ressource ist kein Header 'Access-Control-Allow-Origin' vorhanden. Origin ' m.XXX.com ' ist daher kein Zugriff gestattet.
Ranjith Kumar Nagiri
2

Wie Joel bereits erwähnt hat, können Sie die Zusammenstellung mithilfe der #if !DEBUGDirektive ändern .

Ich habe gerade herausgefunden, dass Sie den Wert des DEBUG-Symbols im Kompilierungselement der Datei web.config ändern können. Hoffentlich hilft das.

Jose
quelle
1

MVC 6 (ASP.NET Core 1.0):

Die richtige Lösung wäre die Verwendung von env.IsProduction () oder env.IsDevelopment (). Lesen Sie in dieser Antwort mehr über die Gründe dafür, wie Sie https nur in der Produktion benötigen .

Verkürzte Antwort unten (siehe Link oben, um mehr über Entwurfsentscheidungen zu erfahren) für 2 verschiedene Stile:

  1. Startup.cs - Filter registrieren
  2. BaseController - Attributstil

Startup.cs (Registerfilter):

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

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

BaseController.cs (Attributstil):

[RequireHttpsInProductionAttribute]
public class BaseController : Controller
{
    // Maybe you have other shared controller logic..
}

public class HomeController : BaseController
{
    // Add endpoints (GET / POST) for Home controller
}

RequireHttpsInProductionAttribute : Beide oben genannten verwenden benutzerdefinierte Attribute, die von RequireHttpsAttribute erben :

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);
    }
}
Nick Niebling
quelle
1

Dies war der sauberste Weg für mich. In meiner App_Start\FilterConfig.csAkte. Release-Builds können jedoch nicht mehr ausgeführt werden.

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

Alternativ können Sie festlegen, dass nur https erforderlich ist, wenn Ihre benutzerdefinierte Fehlerseite aktiviert ist.

... 
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
        if (Web.HttpContext.Current.IsCustomErrorEnabled) {
            filters.Add(new RequireHttpsAttribute());   
        }
        ...
}
Carter Medlin
quelle
Dies ist eine einfache Lösung, die in MVC 5 hervorragend funktioniert :)
MWD