ASP.NET MVC: как автоматически отключить [RequireHttps] на localhost?

Я хочу, чтобы моя страница входа в систему была только SSL:

    [RequireHttps]
    public ActionResult Login()
    {
        if (Helper.LoggedIn)
        {
            Response.Redirect("/account/stats");
        }

        return View();
    }

Но, очевидно, он не работает на localhost, когда я разрабатываю и отлаживаю приложение. Я не хочу использовать IIS 7 с сертификатами SSL, как я могу автоматически отключить атрибут RequireHttps?

Обновление

Основываясь на информации, предоставленной пользователями StackOverflow и исходным кодом ASP.NET MVC 2, я создал следующий класс, который решает проблему.

public class RequireSSLAttribute : FilterAttribute, IAuthorizationFilter
{
    public virtual void OnAuthorization(AuthorizationContext filterContext)
    {
        if (filterContext == null)
        {
            throw new ArgumentNullException("filterContext");
        }

        if (!filterContext.HttpContext.Request.IsSecureConnection)
        {
            HandleNonHttpsRequest(filterContext);
        }
    }

    protected virtual void HandleNonHttpsRequest(AuthorizationContext filterContext)
    {
        if (filterContext.HttpContext.Request.Url.Host.Contains("localhost")) return;

        if (!String.Equals(filterContext.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
        {
            throw new InvalidOperationException("The requested resource can only be accessed via SSL");
        }

        string url = "https://" + filterContext.HttpContext.Request.Url.Host + filterContext.HttpContext.Request.RawUrl;
        filterContext.Result = new RedirectResult(url);
    }
}

И он используется следующим образом:

[RequireSSL]
public ActionResult Login()
{
    if (Helper.LoggedIn)
    {
        Response.Redirect("/account/stats");
    }

    return View();
}

Ответ 1

Проще всего было бы получить новый атрибут из RequireHttps и переопределить HandleNonHttpsRequest

protected override void HandleNonHttpsRequest(AuthorizationContext filterContext)
        {
            if (!filterContext.HttpContext.Request.Url.Host.Contains("localhost"))
            {
                base.HandleNonHttpsRequest(filterContext);
            }
        }

HandleNonHttpsRequest - это метод, который генерирует исключение, здесь все, что мы делаем, не вызывает его, если хост является localhost (и, как говорит Джефф в своем комментарии, вы можете распространить это на тестовые среды или на самом деле любые другие исключения, которые вы хотите).

Ответ 2

    public static void RegisterGlobalFilters(GlobalFilterCollection filters) {

        if (!HttpContext.Current.IsDebuggingEnabled) {
            filters.Add(new RequireHttpsAttribute());
        }
    }

Ответ 3

#if (!DEBUG)
[RequireHttps]
#endif
public ActionResult Login()
{
    if (Helper.LoggedIn)
    {
        Response.Redirect("/account/stats");
    }

    return View();
}

Ответ 4

Вы можете инкапсулировать это требование в производный атрибут:

class RequireHttpsNonDebugAttribute : RequireHttpsAttribute {
    public override void HandleNonHttpsRequest(AuthorizationContext ctx) {
        #if (!DEBUG)
        base.HandleNonHttpsRequest(ctx);
        #endif
    }
}

Ответ 5

MVC 6 (ASP.NET Core 1.0):

Правильным решением было бы использовать env.IsProduction() или env.IsDevelopment().

Пример:

Startup.cs - AddMvc с настраиваемым фильтром:

public void ConfigureServices(IServiceCollection services)
{
    // TODO: Register other services

    services.AddMvc(options =>
    {
        options.Filters.Add(typeof(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);
    }
}

Обсуждаются конструкторские решения:

  • Использовать среду IsProduction() или IsDevelopment(), например. "#if DEBUG". Иногда мы выпускаем/публикуем в режиме DEBUG на нашем тестовом сервере и не хотим отключать это требование безопасности. Это необходимо отключить только в localhost/development (поскольку мы слишком ленивы для установки localhost SSL в IIS Express или в том, что мы используем локально).
  • Использовать фильтр в Startup.cs для глобальной настройки (поскольку мы хотим, чтобы это применимо повсюду). Запуск должен отвечать за регистрацию и настройку всех глобальных правил. Если в вашей компании будет работать новый разработчик, она рассчитывает найти глобальную настройку в Startup.cs.
  • Используйте RequireHttpsAttribute логику, поскольку она доказана (Microsoft). Единственное, что мы хотим изменить, - это когда применяется логика (производство), а когда нет (development/localhost). Никогда не используйте "магические" строки, такие как "http://" и "https://", когда этого можно избежать, повторно используя компонент Microsoft, созданный для обеспечения той же логики.

Выше я бы рассмотрел "правильное" решение.

Примечание:

Как альтернатива , мы можем создать "класс BaseController: Controller" и сделать все наши контроллеры наследуемыми от "BaseController" (вместо Controller). Тогда нам нужно только установить атрибут 1 глобальное место (и не нужно регистрировать фильтр в Startup.cs).

Некоторые люди предпочитают стиль атрибута. Обратите внимание, что это исключит преимущества решения по дизайну №2.

Пример использования:

[RequireHttpsInProductionAttribute]
public class BaseController : Controller
{
    // Maybe you have other shared controller logic..
}

public class HomeController : BaseController
{
    // Add endpoints (GET / POST) for Home controller
}