MVC 仅在生产环境中需要 Https

我想使用 RequreHttpsAttribute来防止不安全的 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 应用程序在发布到生产环境时使用 RequreHttps,而不是在 ASP.NET Development Server 上的开发工作站上运行?

44067 次浏览

How about inheriting the RequireHttps attribute in a custom attribute. Then, inside your custom attribute, check the IsLocal property of the current request to see if the request is coming from the local machine. If it is, then do not apply the base functionality. Otherwise, call the base operation.

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

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


}

更新

在 VisualBasic 中,属性在技术上与应用它们的定义属于同一行。不能将条件编译语句放在一行中,因此必须编写两次函数声明——一次使用属性,一次不使用属性。不过,如果你不介意它的丑陋,它还是有用的。

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

更新2

Several people have mentioned deriving from RequireHttpsAttribute without providing an example, so here's one for you. I think that this approach would be much cleaner than the conditional compilation approach, and it would be my preference in your position.

免责声明: 我还没有测试这段代码,哪怕是一点点,我的 VB 已经相当生疏了。我只知道它会编译。我是根据点,女王3号和兰斯 · 费舍尔的建议写的。如果它不起作用,它至少应该传达一个大概的想法,并给你一个起点。

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

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

<RemoteRequireHttps()> _
Public Class SomeController


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


End Class

干净多了! 前提是我未经测试的代码能正常工作。

如果你能推导和重写,那就去做。如果你不能-MVC 自带源代码,只需要获取源代码并创建自己的[ ForceHttps ]属性来检查 IsLocal。

Deriving from RequireHttps is a good approach.

要完全避免这个问题,还可以在本地计算机上使用 IIS 和自签名证书。IIS 比内置的 webserver 更快,而且您的优势在于您的开发环境更像是生产环境。

Scott Hanselman 有很多关于使用 VS2010和 IIS Express 实现本地 HTTPS 的方法的资料。

首先,是 ASP.Net 开发服务器导致了您的问题,因此值得注意的是,微软现在已经有了与 Visual Studio (从 VS2010 SP1开始)一起提供的 IIS 快递。这是 IIS 的一个精简版本,与 Development Server 一样易于使用,但是支持包括 SSL 在内的 IIS 7.5的全部特性集。

Scott Hanselman 在 在 IIS Express 中使用 SSL上有一篇详细的文章。

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

我刚发现可以在 web.config 文件编译元素中修改 DEBUG 符号的值。希望能帮上忙。

对于 MVC 3,我添加了我自己的 FilterProvider (基于这里找到的代码: 全局和条件筛选器,除了其他事情(为本地用户显示调试信息等) ,当 HttpContext.Request.IsLocal == false时,它将用 RequireHttpsAttribute装饰所有的操作。

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

Leveraging the MVC filter system and Global.asax.cs, I'm assuming you could do this...

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

经过深入研究,我使用 IIS Express 和 Controller 类的 OnAuthorization 方法(参考文献 # 1)解决了这个问题。我也走了汉塞尔曼推荐的路线(参考文献 # 2)。然而,由于两个原因,我对这两个解决方案并不完全满意: 1. Ref#1's OnAuthorization only works at the action level, not at the controller class level 2.参考文献 # 2需要大量的设置(用于 makecert 的 Win7 SDK)、 netsh 命令,并且,为了使用端口80和端口443,我需要以管理员身份启动 VS2010,这是我不赞成的。

因此,我想出了这个解决方案,它关注于简单性,但有以下条件:

  1. 我希望能够在 Controller 类或操作级别使用 RequreHttps 属性

  2. 我希望 MVC 在 RequreHttps 属性出现时使用 HTTPS,如果没有使用 HTTP,则使用 HTTP

  3. 我不想以管理员身份运行 VisualStudio

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

  5. 我可以重用 IISExpress 的自签名 SSL 证书,而且我不在乎是否看到无效的 SSL 提示符

  6. 我希望开发、测试和生产尽可能具有完全相同的代码库和相同的二进制文件,并且独立于其他设置(例如使用 netsh、 mmc cert 管理单元等)

现在,背景和解释已经说明完毕,我希望这段代码能够帮助某些人并节省一些时间。基本上,创建一个从 Controller 继承的 BaseController 类,并从这个基类派生控制器类。既然你已经读到这里,我想你知道如何做这些。编程愉快!

Note#1: This is achieved by the use of a useful function 'getConfig' (see code)

裁判 # 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>

一个可以在生产和开发工作站上使用的解决方案。它基于 web.config 中应用程序设置的选项

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

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

然后使用 RequreHttpsAttribute的自定义实现来处理您的条件。它实际上是从 RequreHttps派生出来的,除了添加条件之外,它使用相同的基方法实现。

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 中修饰 登入方法

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

以及类似这样的东西在您的 登入视图,以便通过 https 发布表单。

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

请参考 Rick Anderson 在 RickAndMSFT 上关于 Azure 和 MVC 填补 Azure 空白的文章

Http://blogs.msdn.com/b/rickandy/archive/2011/04/22/better-faster-easier-ssl-testing-for-asp-net-mvc-amp-webforms.aspx

MVC 6 (ASP.NET Core 1.0) :

正确的解决方案是使用 env.IsProduction()或 env.IsDevelopment ()。

下面是针对两种不同风格的简要回答(参见上面的链接以了解更多关于设计决策的信息) :

  1. Cs-寄存器过滤器
  2. BaseController 属性样式

Cs (寄存器过滤器) :

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


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

BaseController.cs (attribute style):

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


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

RequreHttpsInProductionAttribute : Both of above are using custom attribute inheriting from RequreHttpsAttribute:

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

这招对我很管用 MVC 6(ASP.NET Core 1.0)。 代码检查调试是否在开发中,如果没有,则不需要 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));
}
});
}

这对我来说是最干净的方式。在我的 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());
}
...
}

可以在 global.asax 中设置只使用 SSL。 然后使用 # if! DEBUG 避免在本地使用 RequreHttpsAttribute ()

public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(System.Web.Http.GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
#if !DEBUG
GlobalFilters.Filters.Add(new RequireHttpsAttribute());
#endif
}
}