ASP.NET MVC RequireHttps только в производстве

Я хочу использовать RequireHttpsAttribute, чтобы предотвратить отправку необработанных HTTP-запросов методу действия.

С#

[RequireHttps] //apply to all actions in controller
public class SomeController 
{
    [RequireHttps] //apply to this action only
    public ActionResult SomeAction()
    {
        ...
    }
}

В. Б.

<RequireHttps()> _
Public Class SomeController

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

End Class

К сожалению, ASP.NET Development Server не поддерживает HTTPS.

Как я могу заставить приложение ASP.NET MVC использовать RequireHttps при публикации в рабочей среде, но не при запуске на моей рабочей станции разработки на сервере разработки ASP.NET?

Ответ 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()
    {
    }

}

Update

В 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 по умолчанию, если текущий запрос является локальным (то есть вы получаете доступ к сайту через localhost). Вы можете использовать его следующим образом:

<RemoteRequireHttps()> _
Public Class SomeController

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

End Class

Гораздо чище! Если мой незапрошенный код действительно работает.

Ответ 2

Если кому-то нужна версия С#:

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

Ответ 3

Вывод из RequireHttps - хороший подход.

Чтобы полностью решить проблему, вы можете использовать IIS на своей локальной машине с самозаверяющим сертификатом. IIS быстрее, чем встроенный веб-сервер, и у вас есть то преимущество, что ваша среда разработки больше похожа на процесс производства.

Скотт Ханзельман обладает отличным ресурсом по нескольким способам реализации локальных HTTPS с VS2010 и IIS Express.

Ответ 4

Как и прежде, ASP.NET Development Server вызвал вашу проблему, стоит отметить, что Microsoft теперь IIS Express, который поставляется с Visual Studio (начиная с VS2010 SP1). Это сокращенная версия IIS, которая так же проста в использовании, как и сервер разработки, но поддерживает полный набор функций IIS 7.5, включая SSL.

Скотт Ханзельман имеет подробное сообщение о работе с SSL в IIS Express.

Ответ 5

Используя систему фильтров 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());
      }
    }

Ответ 6

Как насчет наследования атрибута RequireHttps в пользовательском атрибуте. Затем, внутри вашего настраиваемого атрибута, проверьте свойство IsLocal текущего запроса, чтобы узнать, поступает ли запрос с локальной машины. Если это так, не применяйте базовую функциональность. В противном случае вызовите базовую операцию.

Ответ 7

Если вы можете получить и переопределить - сделайте это. Если вы не можете - MVC поставляется с источниками, просто возьмите источники и создайте свой собственный атрибут [ForceHttps], который проверяет IsLocal.

Ответ 8

Для MVC 3 я добавил свой собственный FilterProvider (на основе кода, найденного здесь: Глобальные и условные фильтры, которые, среди прочего (отображение информации Debug для локальных пользователей и т.д.), украсит все действия с помощью RequireHttpsAttribute, когда HttpContext.Request.IsLocal == false.

Ответ 9

После исследования aroud я смог решить эту проблему с помощью IIS Express и переопределить метод OnAuthorization класса Controller (Ref # 1). Я также отправился с маршрутом, рекомендованным Hanselman (Ref № 2). Однако я не был полностью удовлетворен этими двумя решениями по двум причинам: 1. Ссылка № 1 OnAuthorization работает только на уровне действия, а не на уровне класса контроллера 2. Ссылка № 2 требует большой настройки (Win7 SDK для makecert), команд netsh, и, чтобы использовать порт 80 и порт 443, мне нужно запустить VS2010 в качестве администратора, на который я нахмурился.

Итак, я придумал это решение, которое фокусируется на простоте со следующими условиями:

  • Я хочу, чтобы использовать RequireHttps attbbute в классе Controller или уровне действия

  • Я хочу, чтобы MVC использовал HTTPS, когда присутствует атрибут RequireHttps, и используйте HTTP, если он отсутствует

  • Я не хочу запускать Visual Studio в качестве администратора

  • Я хочу иметь возможность использовать любые HTTP и HTTPS-порты, назначенные IIS Express (см. примечание № 1)

  • Я могу повторно использовать самоподписанный SSL-сертификат IIS Express, и мне все равно, вижу ли я неверное приглашение SSL

  • Я хочу, чтобы dev, test и production имели ту же самую базу кода и одну и ту же двоичную и независимую от дополнительной настройки (например, используя netsh, mmc cert snap-in и т.д.), насколько это возможно

Теперь, с учетом фона и объяснений, я надеюсь, что этот код поможет кому-то и сэкономит время. В принципе, создайте класс BaseController, который наследуется от Controller, и выведите классы контроллера из этого базового класса. Поскольку вы зачитали это далеко, я предполагаю, что вы знаете, как это сделать. Итак, счастливая кодировка!

Примечание № 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>

Ответ 10

Одно решение, которое вы можете использовать как на производстве, так и на рабочей станции разработки. Он основан на вашей опции из настроек приложения в 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);
            }
        }
    }
}

Не забудьте украсить метод LogOn в AccountController

[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)) { %>

Ответ 11

Это сработало для меня, MVC 6 (ASP.NET Core 1.0). Код проверяет, находится ли debug в разработке, а если нет, ssl не требуется. Все изменения находятся в Startup.cs.

Добавить

private IHostingEnvironment CurrentEnvironment { get; set; }

Добавить

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

Edit:

public void ConfigureServices(IServiceCollection services)
{
    // additional services...

    services.AddMvc(options =>
    {
        if (!CurrentEnvironment.IsDevelopment())
        {
            options.Filters.Add(typeof(RequireHttpsAttribute));
        }
    });
}

Ответ 12

Как сказал Джоэл, вы можете изменить компиляцию с помощью директивы #if !DEBUG.

Я только узнал, что вы можете изменить значение символа DEBUG в элементе компиляции файла web.config. Надеюсь, что это поможет.

Ответ 13

MVC 6 (ASP.NET Core 1.0):

Правильным решением было бы использовать env.IsProduction() или env.IsDevelopment(). Узнайте больше о причине в этом ответе о том, как требовать https только в процессе производства.

Сжатый ответ ниже (см. ссылку выше, чтобы узнать больше о дизайнерских решениях) для двух разных стилей:

  • Startup.cs - зарегистрировать фильтр
  • 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());   
        }
        ...
}