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,

Ответы:

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 и Ланса Фишера. Если это не сработает, это должно хотя бы передать общую идею и дать вам отправную точку.

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

Намного чище! При условии, что мой непроверенный код действительно работает.

Джоэл Мюллер
источник
Спасибо за редактирование моего поста, Зак. Ваш вопрос был на C #, поэтому я отправил ответ C #. Я не знал, что VB имеет отношение к делу. Кто-нибудь знает, есть ли способ использовать условную компиляцию для управления атрибутами в VB, или это просто невозможно?
Джоэл Мюллер
Да, это работает для C #, и это работает и для VB, но вам нужно сделать довольно уродливое дублирование определения функции / класса. См. Мой обновленный ответ выше.
Джоэл Мюллер
Сожалею. Образцы кода VB становятся все труднее и труднее. Я не думал, что это имеет значение. Я обновил исходный вопрос. Обязательно ли работает условная компиляция вокруг атрибутов в C #? Я не тестировал. Это кажется идеальным и элегантным решением.
Зак Петерсон,
Ваш код RemoteRequireHttpsAttribute работает отлично. Это намного элегантнее для VB, чем условная компиляция. Еще раз спасибо, Джоэл.
Зак Петерсон,
2
Спасибо - это именно то, что мне нужно. Ура!
davecoulter
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);
        }
    }
}
mikesl
источник
КИ при чтении этого и это в качестве меры безопасности мы должны добавить filters.Add(new MyRequireHttpsAttribute ());в FilterConfig?
Shaijut
На основе этого ответа я создал решение для MVC 6, используя либо фильтр в Startup.cs, либо стиль атрибута в контроллере.
Ник Ниблинг 07
26

Унаследование от RequireHttps - хороший подход.

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

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

Лэнс Фишер
источник
да - пока вы не попытаетесь сделать переадресацию портов с помощью устройства Mifi wifi Verizon и не обнаружите, что порт 443 недоступен для пересылки !!! # * & # * & $
Simon_Weaver
Что мне не нравится в использовании IIS на вашем локальном компьютере с самозаверяющим сертификатом, так это то, что мне нужно пройти дополнительный этап развертывания для проверки изменений. Я думаю, что если вы тестируете что-то, связанное с безопасностью, это имеет смысл, но скажем, что если вы просто проверяете какое-то другое незначительное изменение, вам сложно развернуть, просто чтобы обойти неспособность Cassini поддерживать HTTPS.
davecoulter
1
@davecoulter - используйте IIS Express в клиентских версиях окон, не требуется cassini, и он будет работать точно так же, как IIS, включая возможность ssl.
Эрик Функенбуш
@Mystere Man - да, я узнал это после того комментария. Спасибо за подсказку :)
davecoulter
следует добавить дополнительную информацию или ссылку о том, как делать такие вещи.
Стивенбайер
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());
      }
    }
gt124
источник
Я предпочитаю этот ответ, поскольку он включает одну проверку на время жизни приложения, а не реализацию нового фильтра, который будет выполняться \ вызываться с каждым отдельным запросом.
Абдулхамид
10

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

У Скотта Хансельмана есть подробный пост о работе с SSL в IIS Express .

Сэмюэл Джек
источник
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.

queen3
источник
3

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

juhan_h
источник
Или вы можете просто условно добавить его в глобальную коллекцию фильтров, когда запрос является локальным. Обратите внимание, что вы захотите проверить это в блоке try / catch, если приложение настроено на немедленный запуск, поскольку запрос может быть недоступен.
tvanfosson 05
3

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

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

  1. Я хочу иметь возможность использовать атрибут RequireHttps на уровне класса или действия контроллера

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

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

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

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

  6. Я хочу, чтобы разработчики, тестовые и продакшн имели одну и ту же базу кода и один и тот же двоичный файл и были максимально независимы от дополнительных настроек (например, с использованием оснастки для сертификатов netsh, mmc и т. Д.).

Теперь, когда предыстория и объяснения разошлись, я надеюсь, что этот код кому-то поможет и сэкономит время. По сути, создайте класс 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>
Ленг Кенг
источник
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);
            }
        }
    }
}

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

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

и что-то подобное в вашем LogOn View, чтобы разместить форму по 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 », следовательно , не имеет права доступа.
Ранджит Кумар Нагири
2

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

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

Хосе
источник
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