仅在生产中使用ASP.NET MVC RequireHttps

我想使用RequireHttpsAttribute来防止不安全的HTTP请求被发送到一个操作方法。

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 

不幸的是,ASP.NET开发服务器不支持HTTPS。

我如何使ASP.NET MVC应用程序在发布到生产环境时使用RequireHttps,但在ASP.NET开发服务器上的开发工作站上运行时不能使用RequireHttps?

如果在开发工作站上运行Release版本,这将无济于事,但是条件编译可以完成这项工作。

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

更新

在Visual Basic中,属性在技术上与它们所应用的定义是同一行的一部分。 你不能把条件编译语句放在一行内,所以你不得不两次编写函数声明 – 一次使用属性,一次不使用。 不过,如果你不介意丑陋的话,它确实有用。

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

更新2

有几个人提到从RequireHttpsAttribute而没有提供一个例子,所以这里有一个给你。 我认为这种方法比条件编译方法要干净得多,而且我的意愿是你的立场。

免责声明:我还没有testing过这个代码,甚至一点点,我的VB是相当生锈的。 我所知道的是它编译。 我是根据现场,queen3和Lance Fisher的build议写的。 如果不起作用,至less应该传达一般的想法,并给出你的出发点。

 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 

基本上,如果当前请求是本地请求(即,您通过本地主机访问站点),则新属性只是退出而不是运行默认的SSL授权代码。 你可以像这样使用它:

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

更干净! 提供我的未经testing的代码实际上工作。

如果有人需要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); } } } 

从RequireHttps派生是一个好方法。

为了完全解决问题,您可以在本地计算机上使用IIS并使用自签名证书。 IIS比内置的Web服务器更快,而且您的优势在于您的开发环境更像是生产环境。

Scott Hanselman在VS2010和IIS Express上实现本地HTTPS的方法有很多。

利用MVCfilter系统和Global.asax.cs,我假设你可以这样做…

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

由于ASP.Net开发服务器首先引起了您的问题,值得注意的是,微软现在已经有了Visual Studio(自VS2010 SP1以后)附带的IIS Express 。 这是IIS的简化版本,与开发服务器一样易于使用,但支持包括SSL在内的IIS 7.5的完整function集。

Scott Hanselman 在IIS Express中详细介绍了如何使用SSL 。

如何inheritance自定义属性中的RequireHttps属性。 然后,在您的自定义属性中,检查当前请求的IsLocal属性,以查看请求是否来自本地机器。 如果是,则不要应用基本function。 否则,请调用基本操作。

如果你可以派生和覆盖 – 做到这一点。 如果你不能 – MVC带有源代码,只需要获取源代码并创build你自己的[ForceHttps]属性来检查IsLocal。

对于MVC 3我添加了我自己的FilterProvider(基于这里find的代码: 全局和有条件的filter ,其中包括(显示本地用户的debugging信息等)将修饰所有的操作与RequireHttpsAttributeHttpContext.Request.IsLocal == false

经过研究,我可以用IIS Express解决这个问题,并覆盖了Controller类的OnAuthorization方法(Ref#1)。 我也走了Hanselman推荐的路线(参考#2)。 然而,由于以下两个原因,我对这两个解决scheme并不完全满意:1,Ref#1的OnAuthorization只能在操作级别上工作,而不能在控制器级别2上工作。参考2需要很多设置(Win7 SDK for makecert ),netsh命令,并且,为了使用端口80和端口443,我需要启动VS2010作为pipe理员,我皱眉了。

所以,我提出了这个解决scheme,重点是简单的以下条件:

  1. 我希望能够在控制器类或操作级别使用RequireHttps attbbute

  2. 当RequireHttps属性存在时,我希望MVC使用HTTPS,如果不存在,则使用HTTP

  3. 我不想以pipe理员身份运行Visual Studio

  4. 我希望能够使用由IIS Express分配的任何HTTP和HTTPS端口(请参见注释1)

  5. 我可以重复使用IIS Express的自签名SSL证书,我不在乎是否看到无效的SSL提示

  6. 我希望开发,testing和生产具有完全相同的代码库和相同的二进制文件,并且独立于附加设置(例如使用netsh,mmc certpipe理单元等)

现在,以背景和解释的方式,我希望这个代码将帮助某人,并节省一些时间。 基本上,创build一个从Controllerinheritance的BaseController类,并从这个基类派生你的控制器类。 既然你已经读了这么多,我假设你知道如何做到这些。 所以,快乐的编码!

注意#1:这是通过使用一个有用的函数“getConfig”(见代码)

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

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

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

==============结束码================

在Web.Release.Config中,添加以下内容以清除HttpPort和HttpsPort(使用默认的80和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> 

一个解决scheme,您可以使用生产以及开发工作站。 它基于web.config中应用程序设置的选项

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

如果你不想使用SSL删除密钥。 如果使用标准的SSL端口443,则删除该值或指定443。

然后使用RequireHttpsAttribute的自定义实现来照顾你的条件。 它实际上是从RequireHttps派生的,除了添加条件之外,它使用基本方法的相同实现。

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

不要忘记在AccountController中修饰LogOn方法

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

和类似的东西在你的login视图,以通过https张贴表单。

 <% using (Html.BeginFormSecure("LogOn", "Account", new { ReturnUrl = Request.QueryString["ReturnUrl"] }, Request.IsSecureConnection, Request.Url)) { %> 

这对我来说, MVC 6(ASP.NET Core 1.0) 。 代码检查是否正在开发debugging,如果不是,则不需要ssl。 所有的编辑都在Startup.cs中

加:

 private IHostingEnvironment CurrentEnvironment { get; set; } 

加:

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

编辑:

 public void ConfigureServices(IServiceCollection services) { // additional services... services.AddMvc(options => { if (!CurrentEnvironment.IsDevelopment()) { options.Filters.Add(typeof(RequireHttpsAttribute)); } }); } 

正如Joel提到的,你可以通过使用#if !DEBUG指令来改变编译。

我只是发现你可以改变web.config文件编译元素中DEBUG符号的值。 希望有所帮助。

MVC 6 (ASP.NET Core 1.0):

正确的解决scheme是使用env.IsProduction()或env.IsDevelopment()。 阅读关于如何仅在生产中需要https的答案背后的原因。

下面的简明答案(请参阅上面的链接,阅读更多关于devise决策)2种不同的风格:

  1. Startup.cs – 注册filter
  2. BaseController – 属性样式

Startup.cs (注册filter):

 public void ConfigureServices(IServiceCollection services) { // TODO: Register other services services.AddMvc(options => { options.Filters.Add(typeof(RequireHttpsInProductionAttribute)); }); } 

BaseController.cs (属性样式):

 [RequireHttpsInProductionAttribute] public class BaseController : Controller { // Maybe you have other shared controller logic.. } public class HomeController : BaseController { // Add endpoints (GET / POST) for Home controller } 

RequireHttpsInProductionAttribute :以上两个都使用从RequireHttpsAttributeinheritance的自定义属性:

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

这对我来说是最干净的。 在我的App_Start\FilterConfig.cs文件中。 不能再运行发布版本了。

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

或者,您可以将其设置为仅在您的自定义错误页面处于打开状态时才需要https。

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