【问题标题】:ASP.NET MVC RequireHttps in Production OnlyASP.NET MVC RequireHttps 仅在生产中
【发布时间】:2010-12-11 00:21:54
【问题描述】:

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

【问题讨论】:

标签: asp.net-mvc visual-studio ssl https


【解决方案1】:

如果您在开发工作站上运行 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

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

<RemoteRequireHttps()> _
Public Class SomeController

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

End Class

更干净!前提是我未经测试的代码确实有效。

【讨论】:

  • 谢谢你,嗯,为我编辑我的帖子,Zack。您的问题在 C# 中,所以我发布了 C# 回复。我不知道VB是相关的。任何人都知道是否有一种方法可以使用条件编译来控制 VB 中的属性,或者这是不可能的?
  • 是的,它适用于 C#,它也适用于 VB,但您必须对函数/类定义进行一些相当难看的复制。请参阅上面的更新答案。
  • 对不起。 VB 代码示例越来越难获得。我不认为这很重要。我已经更新了原始问题。围绕属性的条件编译是否在 C# 中确定有效?我没有测试过。这似乎是一个完美、优雅的解决方案。
  • 您的 RemoteRequireHttpsAttribute 代码运行良好。这对于 VB 来说比条件编译要优雅得多。再次感谢乔尔。
  • 谢谢——这正是我所需要的。干杯!
【解决方案2】:

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

【讨论】:

  • 在阅读thisthis 作为安全措施时,我们应该在FilterConfig 中添加filters.Add(new MyRequireHttpsAttribute ()); 吗?
  • 基于这个答案,我使用 Startup.cs 中的过滤器或 Controller 上的属性样式创建了一个 solution for MVC 6
【解决方案3】:

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

要完全避开这个问题,您也可以在本地计算机上使用带有自签名证书的 IIS。 IIS 比内置网络服务器更快,而且您的优势在于您的开发环境更像生产环境。

Scott Hanselman has a great resource on a few ways to implement local HTTPS with VS2010 and IIS Express.

【讨论】:

  • 是的 - 直到您尝试使用 Mifi wifi Verizon 设备进行端口转发并发现端口 443 无法转发!!! #**&$
  • 我不喜欢在本地机器上使用带有自签名证书的 IIS,因为我必须通过额外的部署步骤来测试更改。我认为,如果您正在测试与安全相关的东西,那么它是有道理的,但是如果您只是在检查其他一些小的更改,那么为了解决 Cassini 无法支持 HTTPS 而不得不进行部署是很痛苦的。
  • @davecoulter - 在客户端版本的 windows 上使用 IIS express,不需要 cassini,它可以像 IIS 一样工作,包括具有 ssl 功能。
  • @Mystere Man - 是的,我确实从那条评论中发现了这一点。感谢您的提示:)
  • 应添加更多信息或链接以了解如何进行此类操作。
【解决方案4】:

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

【讨论】:

  • 我更喜欢这个答案,因为它涉及每个应用程序生命周期的一次检查,而不是实现一个将在每个请求中执行\调用的新过滤器。
【解决方案5】:

由于最初是 ASP.Net 开发服务器引起了您的问题,因此值得注意的是,Microsoft 现在拥有 IIS Express,它随 Visual Studio 一起提供(自 VS2010 SP1 起)。这是 IIS 的精简版,与开发服务器一样易于使用,但支持 IIS 7.5 的完整功能集,包括 SSL。

Scott Hanselman 在working with SSL in IIS Express 上有详细的帖子。

【讨论】:

    【解决方案6】:

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

    【讨论】:

      【解决方案7】:

      这对我有用,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));
              }
          });
      }
      

      【讨论】:

        【解决方案8】:

        如果您可以派生和覆盖 - 就这样做。如果你不能 - MVC 附带源,只需获取源并创建自己的 [ForceHttps] 属性来检查 IsLocal。

        【讨论】:

          【解决方案9】:

          对于 MVC 3,我添加了自己的 FilterProvider(基于在此处找到的代码:Global and Conditional Filters,除其他外(显示本地用户的调试信息等)将在 HttpContext.Request.IsLocal == false 时使用 RequireHttpsAttribute 装饰所有操作。

          【讨论】:

          • 或者您可以在本地请求时有条件地将其添加到全局过滤器集合中。请注意,如果应用程序设置为立即启动,您需要在 try/catch 块中检查这一点,因为请求可能不可用。
          【解决方案10】:

          在研究之后,我能够使用 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 attbbute

          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>
          

          【讨论】:

            【解决方案11】:

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

            在您的 LogOn 视图中使用类似的内容,以便通过 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”标头。因此,不允许访问 Origin 'm.XXX.com'。
            【解决方案12】:

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

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

            【讨论】:

              【解决方案13】:

              MVC 6(ASP.NET Core 1.0):

              正确的解决方案是使用 env.IsProduction() 或 env.IsDevelopment()。在how to require https only in production 上阅读有关此答案背后原因的更多信息。

              以下针对 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);
                  }
              }
              

              【讨论】:

                【解决方案14】:

                这对我来说是最干净的方式。在我的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 中效果很好:)
                【解决方案15】:

                请参阅 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

                【讨论】:

                  猜你喜欢
                  • 2010-12-07
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2015-05-13
                  • 1970-01-01
                  • 2011-04-07
                  • 2023-03-30
                  相关资源
                  最近更新 更多