ASP.NET MVC RequireHttps только в производстве - PullRequest
121 голосов
/ 28 октября 2009

Я хочу использовать 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 Development Server не поддерживает HTTPS.

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

Ответы [ 15 ]

129 голосов
/ 28 октября 2009

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

<RemoteRequireHttps()> _
Public Class SomeController

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

End Class

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

65 голосов
/ 03 мая 2011

Если кому-то нужна версия 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);
        }
    }
}
26 голосов
/ 29 октября 2009

Получение от RequireHttps - это хороший подход.

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

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

11 голосов
/ 16 сентября 2011

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

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

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

9 голосов
/ 28 октября 2009

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

4 голосов
/ 19 сентября 2016

Это сработало для меня, MVC 6 (ASP.NET Core 1.0) . Код проверяет, находится ли отладка в разработке, а если нет, то 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));
        }
    });
}
3 голосов
/ 21 июля 2013

Одно решение, которое вы можете использовать как на производстве, так и на рабочей станции. Это зависит от вашего варианта из настроек приложения в 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)) { %>
3 голосов
/ 28 августа 2012

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

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

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

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

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

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

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

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

Теперь, с учетом фона и объяснений, я надеюсь, что этот код поможет кому-то и сэкономит время. По сути, создайте класс 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 голосов
/ 30 марта 2011

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...