Je souhaite utiliser RequireHttpsAttribute pour empêcher l'envoi de requêtes HTTP non sécurisé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 MVC ASP.NET 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?
Cela n'aidera pas si vous exécutez des 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()
{
}
}
En 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 cela ne vous dérange pas.
#If Not Debug Then
<RequireHttps()> _
Function SomeAction() As ActionResult
#Else
Function SomeAction() As ActionResult
#End If
...
End Function
Plusieurs personnes ont mentionné dériver de RequireHttpsAttribute
sans fournir 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 dans votre position.
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.
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);
}
}
}
Dériver de RequireHttps est une bonne approche.
Pour contourner complètement le problème, vous pouvez également utiliser IIS sur votre machine locale 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 davantage à la production.
En utilisant le système de filtre MVC et Global.asax.cs, je suppose que vous pourriez 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());
}
}
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 de IIS qui est aussi facile à utiliser que le serveur de développement, mais prend en charge l'ensemble complet des fonctionnalités de IIS 7.5, y compris SSL).
Scott Hanselman a un article détaillé sur travailler avec SSL dans IIS Express .
Que diriez-vous d'hériter de l'attribut RequireHttps dans un attribut personnalisé. Ensuite, à l'intérieur de votre attribut personnalisé, vérifiez la propriété IsLocal de la demande actuelle pour voir si la demande provient de la machine locale. Si c'est le cas, n'appliquez pas la fonctionnalité de base. Sinon, appelez l'opération de base.
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;
}
Modifier:
public void ConfigureServices(IServiceCollection services)
{
// additional services...
services.AddMvc(options =>
{
if (!CurrentEnvironment.IsDevelopment())
{
options.Filters.Add(typeof(RequireHttpsAttribute));
}
});
}
Une solution que vous pouvez utiliser aussi bien en production qu'en station 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, retirez la clé. Si vous utilisez le port SSL standard 443, supprimez la valeur ou spécifiez 443.
Utilisez ensuite l'implémentation personnalisée de RequireHttpsAttribute qui prend soin de votre état. Il est dérivé en fait de RequireHttps et utilise la même implémentation de la méthode de base, sauf pour ajouter des 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 LogOn Voir afin de poster un formulaire sur https.
<% using (Html.BeginFormSecure("LogOn", "Account", new { ReturnUrl = Request.QueryString["ReturnUrl"] }, Request.IsSecureConnection, Request.Url)) { %>
Pour MVC 3, j'ai ajouté mon propre FilterProvider (basé sur le code trouvé ici: Filtres globaux et conditionnels qui, entre autres (afficher les informations de débogage pour les utilisateurs locaux, etc.) décorera toutes les actions avec RequireHttpsAttribute
quand HttpContext.Request.IsLocal == false
.
Si vous pouvez dériver et remplacer - faites-le. Si vous ne pouvez pas - MVC est fourni avec les sources, prenez simplement les sources et créez votre propre attribut [ForceHttps] qui vérifie IsLocal.
Après avoir fait des recherches sur Aroud, 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 opté pour l'itinéraire recommandé par Hanselman (Ref # 2). Cependant, je n'étais pas complètement satisfait de ces deux solutions pour deux raisons: 1. OnAuthorization de la référence # 1 ne fonctionne qu'au niveau de l'action, pas au niveau de la classe du contrôleur 2. La référence # 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.
J'ai donc trouvé cette solution qui met l'accent sur la simplicité dans les conditions suivantes:
Je veux pouvoir utiliser l'attribut RequireHttps au niveau de la classe ou de l'action Controller
Je veux que MVC utilise HTTPS lorsque l'attribut RequireHttps est présent et utilise HTTP s'il est absent
Je ne veux pas avoir à exécuter Visual Studio en tant qu'administrateur
Je veux pouvoir utiliser tous les ports HTTP et HTTPS attribués par IIS Express (voir la note # 1)
Je peux réutiliser le certificat SSL auto-signé de IIS Express, et je m'en fiche si je vois l'invite SSL non valide
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 aussi indépendants de la configuration supplémentaire (par exemple, en utilisant netsh, le composant logiciel enfichable mmc cert, etc.) que possible
Maintenant, avec l'arrière-plan 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 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>
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 du fichier web.config. J'espère que ça t'as aidé.
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 dans cette réponse sur comment 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:
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érité 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);
}
}
C'était la façon la plus propre pour moi. Dans mon App_Start\FilterConfig.cs
fichier. Cependant, je ne peux plus exécuter les versions de versions.
...
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
if (!Web.HttpContext.Current.IsDebuggingEnabled) {
filters.Add(new RequireHttpsAttribute());
}
...
}
Alternativement, vous pouvez le configurer pour ne nécessiter 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());
}
...
}