ASP.NET MVC RequireHttps en production uniquement

121

Je souhaite utiliser RequireHttpsAttribute pour empêcher les requêtes HTTP non sécurisées d'être envoyées à une méthode d'action.

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

Malheureusement, ASP.NET Development Server ne prend pas en charge HTTPS.

Comment puis-je faire en sorte que mon application ASP.NET MVC utilise RequireHttps lorsqu'elle est publiée dans l'environnement de production, mais pas lorsqu'elle est exécutée sur mon poste de travail de développement sur le serveur de développement ASP.NET?

Zack Peterson
la source
3
Testez avec votre IIS local et avec IIS Express. Voir mon blog SSL blogs.msdn.com/b/rickandy/archive/2011/04/22/… et blogs.msdn.com/b/rickandy/archive/2012/03/23/…
RickAndMSFT

Réponses:

129

Cela n'aidera pas si vous exécutez les versions Release sur votre poste de travail de développement, mais la compilation conditionnelle pourrait faire le travail ...

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

}

Mettre à jour

Dans Visual Basic, les attributs font techniquement partie de la même ligne que la définition à laquelle ils s'appliquent. Vous ne pouvez pas mettre des instructions de compilation conditionnelle dans une ligne, vous êtes donc obligé d'écrire la déclaration de fonction deux fois - une fois avec l'attribut et une fois sans. Cela fonctionne, cependant, si vous ne vous souciez pas de la laideur.

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

Mise à jour 2

Plusieurs personnes ont mentionné dériver de RequireHttpsAttributesans donner d'exemple, alors en voici un pour vous. Je pense que cette approche serait beaucoup plus propre que l’approche de compilation conditionnelle, et ce serait ma préférence à votre place.

AVERTISSEMENT: je n'ai pas testé ce code, même un peu, et mon VB est assez rouillé. Tout ce que je sais, c'est qu'il compile. Je l'ai écrit sur la base des suggestions de spot, queen3 et Lance Fisher. Si cela ne fonctionne pas, cela devrait au moins transmettre l'idée générale et vous donner un point de départ.

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

Fondamentalement, le nouvel attribut se ferme au lieu d'exécuter le code d'autorisation SSL par défaut, si la demande actuelle est locale (c'est-à-dire que vous accédez au site via localhost). Vous pouvez l'utiliser comme ceci:

<RemoteRequireHttps()> _
Public Class SomeController

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

End Class

Beaucoup plus propre! À condition que mon code non testé fonctionne réellement.

Joel Mueller
la source
Merci d'avoir édité mon message pour moi, Zack. Votre question était en C #, j'ai donc publié une réponse C #. Je ne savais pas que VB était pertinent. Quelqu'un sait s'il existe un moyen d'utiliser la compilation conditionnelle pour contrôler les attributs dans VB, ou n'est-ce pas possible?
Joel Mueller
Oui, cela fonctionne pour C #, et cela fonctionne aussi pour VB, mais vous devez faire une duplication assez moche de la définition de la fonction / classe. Voir ma réponse mise à jour ci-dessus.
Joel Mueller
Désolé. Les échantillons de code VB sont de plus en plus difficiles à trouver. Je ne pensais pas que ce serait important. J'ai mis à jour la question d'origine. La compilation conditionnelle autour des attributs fonctionne-t-elle à coup sûr en C #? Je n'ai pas testé. Cela semble être une solution parfaite et élégante.
Zack Peterson
Votre code RemoteRequireHttpsAttribute fonctionne parfaitement. C'est beaucoup plus élégant pour VB que la compilation conditionnelle. Merci encore Joel.
Zack Peterson
2
Merci - c'était exactement ce dont j'avais besoin. À votre santé!
davecoulter
65

Si quelqu'un a besoin de la version C #:

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
la source
ok lors de la lecture ce et ce en tant que mesure de sécurité devrait - on ajouter filters.Add(new MyRequireHttpsAttribute ());à FilterConfig?
shaijut
Sur la base de cette réponse, j'ai créé une solution pour MVC 6 en utilisant soit le filtre dans Startup.cs, soit le style d'attribut sur Controller.
Nick Niebling
26

Dériver de RequireHttps est une bonne approche.

Pour éviter complètement le problème, vous pouvez également utiliser IIS sur votre ordinateur local avec un certificat auto-signé. IIS est plus rapide que le serveur Web intégré et vous avez l'avantage que votre environnement de développement ressemble plus à la production.

Scott Hanselman a une excellente ressource sur quelques façons d'implémenter HTTPS local avec VS2010 et IIS Express.

Lance Fisher
la source
ya - jusqu'à ce que vous essayiez de faire la redirection de port avec un appareil Mifi wifi Verizon et que vous trouviez que le port 443 n'est pas disponible pour la redirection !!! # * & # * & $
Simon_Weaver
Ce que je n'aime pas dans l'utilisation d'IIS sur votre ordinateur local avec un certificat auto-signé, c'est que je dois passer par une étape supplémentaire de déploiement pour tester les modifications. Je pense que si vous testez quelque chose lié à la sécurité, cela a du sens, mais disons que si vous ne faites que vérifier un autre changement mineur, il est difficile de devoir déployer juste pour contourner l'incapacité de Cassini à prendre en charge HTTPS.
davecoulter
1
@davecoulter - Utilisez IIS express sur les versions clientes de Windows, aucun cassini n'est nécessaire et il fonctionnera exactement comme IIS, y compris en ayant la capacité SSL.
Erik Funkenbusch
@Mystere Man - ouais, je l'ai découvert depuis ce commentaire. Merci pour le tuyau :)
davecoulter
plus d'informations ou un lien devrait être ajouté sur la façon de faire de telles choses.
stephenbayer
12

En exploitant le système de filtre MVC et Global.asax.cs, je suppose que vous pouvez le faire ...

    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
la source
Je préfère cette réponse car elle implique une vérification par durée de vie d'application plutôt que d'implémenter un nouveau filtre qui va être exécuté \ appelé avec chaque requête.
Abdulhameed
10

Comme c'est le serveur de développement ASP.Net qui a causé votre problème en premier lieu, il convient de noter que Microsoft a maintenant IIS Express , qui est livré avec Visual Studio (depuis VS2010 SP1). Il s'agit d'une version réduite d'IIS qui est aussi facile à utiliser que le serveur de développement, mais qui prend en charge l'ensemble des fonctionnalités d'IIS 7.5, y compris SSL.

Scott Hanselman a publié un article détaillé sur l'utilisation de SSL dans IIS Express .

Samuel Jack
la source
9

Que diriez-vous d'hériter de l'attribut RequireHttps dans un attribut personnalisé. Ensuite, dans votre attribut personnalisé, vérifiez la propriété IsLocal de la demande actuelle pour voir si la demande provient de l'ordinateur local. Si tel est le cas, n'appliquez pas la fonctionnalité de base. Sinon, appelez l'opération de base.

place
la source
4

Cela a fonctionné pour moi, MVC 6 (ASP.NET Core 1.0) . Le code vérifie si le débogage est en cours de développement, et sinon, SSL n'est pas requis. Toutes les modifications sont dans Startup.cs .

Ajouter:

private IHostingEnvironment CurrentEnvironment { get; set; }

Ajouter:

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

Éditer:

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

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

Si vous pouvez dériver et remplacer - faites-le. Si vous ne pouvez pas - MVC est livré avec des sources, prenez simplement les sources et créez votre propre attribut [ForceHttps] qui vérifie IsLocal.

reine3
la source
3

Pour MVC 3, j'ai ajouté mon propre FilterProvider (basé sur le code trouvé ici: Filtres globaux et conditionnels qui, entre autres choses (affichage des informations de débogage pour les utilisateurs locaux, etc.) décoreront toutes les actions avec RequireHttpsAttributequand HttpContext.Request.IsLocal == false.

juhan_h
la source
Ou vous pouvez simplement l'ajouter conditionnellement à la collection de filtres globale lorsque la demande est locale. Notez que vous voudrez vérifier cela dans un bloc try / catch si l'application est configurée pour démarrer immédiatement car la demande peut ne pas être disponible.
tvanfosson
3

Après avoir fait des recherches, j'ai pu résoudre ce problème avec IIS Express et un remplacement de la méthode OnAuthorization de la classe Controller (Ref # 1). J'ai également suivi l'itinéraire recommandé par Hanselman (Ref # 2). Cependant, je n'étais pas complètement satisfait de ces deux solutions pour deux raisons: 1. L'OnAuthorization de Ref # 1 ne fonctionne qu'au niveau de l'action, pas au niveau de la classe de contrôleur 2. La Ref # 2 nécessite beaucoup de configuration (Win7 SDK pour makecert ), les commandes netsh et, pour utiliser le port 80 et le port 443, je dois lancer VS2010 en tant qu'administrateur, ce que je désapprouve.

Donc, j'ai trouvé cette solution qui met l'accent sur la simplicité avec les conditions suivantes:

  1. Je veux pouvoir utiliser l'attribut RequireHttps au niveau de la classe ou de l'action Controller

  2. Je veux que MVC utilise HTTPS lorsque l'attribut RequireHttps est présent et utilise HTTP s'il est absent

  3. Je ne veux pas avoir à exécuter Visual Studio en tant qu'administrateur

  4. Je souhaite pouvoir utiliser tous les ports HTTP et HTTPS attribués par IIS Express (voir la remarque n ° 1)

  5. Je peux réutiliser le certificat SSL auto-signé d'IIS Express, et je m'en fiche si je vois l'invite SSL non valide

  6. Je veux que le développement, le test et la production aient exactement la même base de code et le même binaire et soient aussi indépendants de la configuration supplémentaire (par exemple, en utilisant netsh, le composant logiciel enfichable mmc cert, etc.) que possible.

Maintenant, avec le contexte et l'explication à l'écart, j'espère que ce code aidera quelqu'un et fera gagner du temps. Fondamentalement, créez une classe BaseController qui hérite de Controller et dérivez vos classes de contrôleur de cette classe de base. Puisque vous avez lu jusqu'ici, je suppose que vous savez comment faire cela. Alors, bon codage!

Note # 1: Ceci est réalisé par l'utilisation d'une fonction utile 'getConfig' (voir le code)

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

Réf # 2: http://www.hanselman.com/blog/WorkingWithSSLAtDevelopmentTimeIsEasierWithIISExpress.aspx

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

============== code de fin =================

Dans Web.Release.Config, ajoutez ce qui suit pour effacer HttpPort et HttpsPort (pour utiliser les valeurs par défaut 80 et 443).

<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
la source
3

Une solution que vous pouvez utiliser aussi bien en production que sur poste de travail de développement. Il est basé sur votre option dans les paramètres de l'application dans web.config

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

Si vous ne souhaitez pas utiliser SSL, supprimez la clé. Si vous utilisez le port SSL standard 443, supprimez la valeur ou spécifiez 443.

Ensuite, utilisez une implémentation personnalisée de RequireHttpsAttribute qui prend soin de votre condition. Il est en fait dérivé de RequireHttps et utilise la même implémentation de la méthode de base, sauf pour l'ajout de conditions.

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

N'oubliez pas de décorer la méthode LogOn dans AccountController

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

et quelque chose comme ça dans votre vue LogOn afin de publier le formulaire sur https.

<% using (Html.BeginFormSecure("LogOn", "Account", new { ReturnUrl = Request.QueryString["ReturnUrl"] }, Request.IsSecureConnection, Request.Url)) { %>
pseudo
la source
J'obtiens cette erreur: XMLHttpRequest ne peut pas charger m.XXX.com/Auth/SignIn . Aucun en-tête «Access-Control-Allow-Origin» n'est présent sur la ressource demandée. L' accès à l' origine ' m.XXX.com ' n'est donc pas autorisé.
Ranjith Kumar Nagiri
2

Comme Joel l'a mentionné, vous pouvez modifier la compilation en utilisant le #if !DEBUG directive.

Je viens de découvrir que vous pouvez modifier la valeur du symbole DEBUG dans l'élément de compilation de fichier web.config. J'espère que cela pourra aider.

José
la source
1

MVC 6 (ASP.NET Core 1.0):

La bonne solution serait d'utiliser env.IsProduction () ou env.IsDevelopment (). En savoir plus sur la raison derrière cette réponse sur la façon d'exiger https uniquement en production .

Réponse condensée ci-dessous (voir le lien ci-dessus pour en savoir plus sur les décisions de conception) pour 2 styles différents:

  1. Startup.cs - Filtre d'enregistrement
  2. BaseController - style d'attribut

Startup.cs (filtre de registre):

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

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

BaseController.cs (style d'attribut):

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

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

RequireHttpsInProductionAttribute : les deux ci-dessus utilisent un attribut personnalisé héritant de RequireHttpsAttribute :

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
la source
1

C'était la manière la plus propre pour moi. Dans mon App_Start\FilterConfig.csdossier. Cependant, je ne peux plus exécuter les versions de version.

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

Vous pouvez également le configurer pour qu'il n'exige que https lorsque votre page d'erreur personnalisée est activée.

... 
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
        if (Web.HttpContext.Current.IsCustomErrorEnabled) {
            filters.Add(new RequireHttpsAttribute());   
        }
        ...
}
Carter Medlin
la source
C'est une solution simple qui fonctionne très bien dans MVC 5 :)
MWD