ASP.NET MVC RequireHttps仅在生产中


121

我想使用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开发服务器上的开发工作站上运行时如何使它使用?


3
使用本地IIS和IIS Express进行测试。见我的博客SSL blogs.msdn.com/b/rickandy/archive/2011/04/22/...blogs.msdn.com/b/rickandy/archive/2012/03/23/...
RickAndMSFT

Answers:


129

如果您在开发工作站上运行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没有提供示例的“ 派生” ,因此这里有一个适合您。我认为这种方法比有条件的编译方法要干净得多,这是我首选的位置。

免责声明:我什至没有测试过此代码,而且我的VB相当生锈。我所知道的是它可以编译。我是根据spot,queen3和Lance Fisher的建议编写的。如果不起作用,则至少应传达总体思路,并为您提供起点。

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

干净得多!只要我未经测试的代码可以正常工作。


谢谢,嗯,为我编辑我的帖子,扎克。您的问题在C#中,因此我发布了C#答复。我不知道VB是否有意义。任何人都知道是否有一种方法可以使用条件编译来控制VB中的属性,或者那是不可能的吗?
乔尔·穆勒

是的,它适用于C#,也适用于VB,但是您必须对函数/类定义进行一些相当难看的复制。请参阅上面的最新答案。
乔尔·穆勒

抱歉。VB代码示例变得越来越难。我认为没关系。我已经更新了原始问题。是否可以在C#中确保围绕属性进行条件编译?我还没测试 这似乎是一个完美,优雅的解决方案。
扎克·彼得森

您的RemoteRequireHttpsAttribute代码可以完美运行。对于VB而言,这比条件编译要优雅得多。再次感谢乔尔。
扎克·彼得森

2
谢谢-这正是我所需要的。干杯!
davecoulter's

65

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

OK阅读时,这个这个作为一种安全措施,我们要补充filters.Add(new MyRequireHttpsAttribute ());FilterConfig
shaijut

基于此答案,我使用Startup.cs中的过滤器或Controller上的属性样式创建了MVC 6解决方案
尼克·尼伯

26

从RequireHttps派生是一个很好的方法。

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

Scott Hanselman在使用VS2010和IIS Express实施本地HTTPS的几种方法方面拥有丰富的资源。


ya-在尝试使用Mifi wifi Verizon设备进行端口转发之前,发现端口443无法转发!#*&#*&$
Simon_Weaver 2011年

我不希望在具有自签名证书的本地计算机上使用IIS是因为我必须经过额外的部署步骤才能测试更改。我认为,如果您要测试与安全性相关的事情,那是没有意义的,但是说,如果您只是在检查其他一些小的更改,那么为了避免Cassini无法支持HTTPS而不得不进行部署是很痛苦的。
davecoulter's

1
@davecoulter-在Windows的客户端版本上使用IIS Express,不需要卡西尼,它的工作原理与IIS完全一样,包括具有ssl功能。
Erik Funkenbusch 2011年

@Mystere Man-是的,自那条评论以来,我确实发现了这一点。感谢您的提示:)
davecoulter 2011年

应该添加更多信息或链接以了解如何进行此类操作。
stephenbayer 2014年

12

利用MVC筛选器系统和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());
      }
    }

我更喜欢这个答案,因为它涉及到每个应用程序生命周期一次检查,而不是实现将要在每个单个请求中执行/调用的新过滤器。
Abdulhameed 19/09/24

10

由于最初是由ASP.Net Development Server引起问题的,因此值得注意的是,Microsoft现在拥有IIS Express,它随Visual Studio一起提供(自VS2010 SP1起)。这是IIS的简化版本,与Development Server一样易于使用,但支持IIS 7.5的全部功能集,包括SSL。

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


9

如何在自定义属性中继承RequireHttps属性。然后,在您的自定义属性中,检查当前请求的IsLocal属性,以查看该请求是否来自本地计算机。如果是这样,则不要应用基本功能。否则,请调用基本操作。


4

这对我来说很有效,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));
        }
    });
}

3

如果您可以派生和覆盖-做到这一点。如果不能-MVC附带了源代码,则只需获取源代码并创建自己的[ForceHttps]属性即可检查IsLocal。


3

对于MVC 3,我添加了自己的FilterProvider(基于此处找到的代码:全局过滤器和条件过滤器,除其他外(显示本地用户的调试信息等)将用RequireHttpsAttributewhen 装饰所有动作HttpContext.Request.IsLocal == false


或者,您可以只在本地请求时有条件地将其添加到全局过滤器集合中。请注意,如果应用程序设置为立即启动,则需要在try / catch块中进行检查,因为该请求可能不可用。
tvanfosson 2011年

3

经过深入研究,我能够使用IIS Express和对Controller类的OnAuthorization方法(Ref#1)的重写来解决此问题。我也选择了Hanselman(Ref#2)推荐的路线。但是,由于以下两个原因,我对这两种解决方案不完全满意:1. Ref#1的OnAuthorization仅在操作级别起作用,而不在控制器类级别起作用。2. Ref#2需要进行大量设置(用于makecert的Win7 SDK ),netsh命令,并且,为了使用端口80和端口443,我需要以管理员身份启动VS2010,但我对此并不满意。

因此,我想出了这种解决方案,该解决方案着重于满足以下条件的简单性:

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

  2. 我希望MVC在存在RequireHttps属性时使用HTTPS,并在不存在时使用HTTP

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

  4. 我希望能够使用IIS Express分配的所有HTTP和HTTPS端口(请参阅注1)。

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

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

现在,在没有背景知识和解释的情况下,我希望这段代码可以对某人有所帮助并节省一些时间。基本上,创建一个从Controller继承的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>

3

您可以在生产以及开发工作站上使用的一种解决方案。它基于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)

并在您的“ 登录视图”中进行类似操作,以便通过https发布表单。

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

我收到此错误:XMLHttpRequest无法加载m.XXX.com/Auth/SignIn。所请求的资源上没有“ Access-Control-Allow-Origin”标头。因此,不允许访问来源“ m.XXX.com ”。
Ranjith Kumar Nagiri 2014年

2

如Joel所述,您可以使用#if !DEBUG指令来更改编译。

我刚刚发现,您可以在web.config文件编译元素中更改DEBUG符号的值。希望有帮助。


1

MVC 6(ASP.NET Core 1.0):

正确的解决方案是使用env.IsProduction()或env.IsDevelopment()。在此答案中了解有关背后原因的更多信息,以了解如何仅在生产中要求使用https

以下为2种不同样式的简明答案(请参阅上面的链接以了解有关设计决策的更多信息):

  1. Startup.cs-注册过滤器
  2. BaseController-属性样式

Startup.cs(注册过滤器):

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:以上两种方法都使用自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);
    }
}

1

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

这是一个简单的解决方案,在MVC 5中非常有用:)
MWD

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.