ASP.NET MVC Потрібні HTTPps лише у виробництві


121

Я хочу використовувати RequireHttpsAttribute для запобігання надсилання незахищених запитів HTTP методу дії.

C #

[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 не підтримує 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 досить іржавий. Все, що я знаю, - це те, що вона компілює. Я написав це на основі пропозицій спот, 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

Набагато чистіше! За умови, що мій неперевірений код справді працює.


Дякую за, гм, що ви редагували свою публікацію для мене, Зак. Ваше запитання було на 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);
        }
    }
}

КІ при читанні цього і це в якості міри безпеки ми повинні додати filters.Add(new MyRequireHttpsAttribute ());в FilterConfig?
shaijut

На основі цієї відповіді я створив рішення для MVC 6, використовуючи або фільтр у Startup.cs, або стиль атрибута на Controller.
Нік Нібіблінг

26

Вихід з RequireHttps - це хороший підхід.

Щоб повністю вирішити проблему, ви можете використовувати IIS на своїй локальній машині і з власноручним підписом. IIS швидше, ніж вбудований веб-сервер, і ви маєте перевагу в тому, що ваше середовище розробки більше схоже на виробництво.

Скотт Хензельман має чудовий ресурс у кількох способах впровадження локальних HTTPS за допомогою VS2010 та IIS Express.


ya - поки ви не спробуєте зробити переадресацію порту за допомогою пристрою Mifi wifi Verizon і не виявите, що порт 443 недоступний для пересилання !!! # * & # * & $
Simon_Weaver

Що мені не подобається використовувати IIS на вашій локальній машині з підписаним сертифікатом, це те, що мені потрібно пройти додатковий крок розгортання, щоб перевірити зміни. Я думаю, що якщо ви тестуєте щось, що стосується безпеки, ніж це має сенс, але скажіть, якщо ви просто перевіряєте якісь інші незначні зміни, болісно доведеться розгортатись лише для того, щоб обійти нездатність Кассіні підтримувати HTTPS.
davecoulter

1
@davecoulter - Використовуйте IIS express на клієнтських версіях Windows, не потрібні кассіні, і він буде працювати точно так само, як IIS, у тому числі з функцією ssl.
Ерік Функенбуш

@Mystere Man - так, я дізнався це з того коментаря. Дякую за пораду :)
davecoulter

більше інформації або посилання слід додати про те, як робити такі речі.
Stephenbayer

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

Я віддаю перевагу цій відповіді, оскільки вона передбачає одну перевірку протягом життя програми, а не реалізацію нового фільтра, який буде виконуватися \ викликається з кожним запитом.
Абдулхамед

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.


3

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


Або ви можете просто умовно додати його до глобальної колекції фільтрів, коли запит є локальним. Зауважте, що ви захочете перевірити це у блоці спробу / лову, якщо програма налаштована для запуску негайно, оскільки запит може бути недоступним.
tvanfosson

3

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

Отже, я придумав таке рішення, яке зосереджує увагу на простоті з наступними умовами:

  1. Я хочу мати можливість використовувати аттрибутор RequireHttps на рівні Controller або на рівні дії

  2. Я хочу, щоб MVC використовував HTTPS, коли присутній атрибут RequireHttps, і використовувати HTTP, якщо він відсутній

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

  4. Я хочу мати змогу використовувати будь-які порти HTTP та HTTPS, призначені IIS Express (Див. Примітку №1)

  5. Я можу повторно використовувати самопідписаний сервер SSL IIS Express, і мені байдуже, чи бачу недійсне запит SSL

  6. Я хочу, щоб розробник, тест і виробництво мали точно таку саму базу коду, таку ж двійкову та незалежну від додаткової настройки (наприклад, використання вбудованого модуля 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

Одне рішення, яке можна використовувати як на виробництві, так і на розробці робочої станції. Він заснований на вашому варіанті з налаштувань програми в web.config

<appSettings>
     <!--Use SSL port 44300 in IIS Express on development workstation-->
     <add key="UseSSL" value="44300" />
</appSettings>

Якщо ви не хочете використовувати SSL, видаліть ключ. Якщо ви використовуєте стандартний порт 443 SSL, видаліть значення або вкажіть 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)) { %>

я отримую цю помилку: 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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.