ASP.NET MVC RequireHttps только в производстве
Я хочу использовать 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 Development Server?
16 ответов
Это не поможет, если вы запустите сборку 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
Гораздо чище! При условии, что мой непроверенный код действительно работает.
Если кому-то нужна версия 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);
}
}
}
Вывод из RequireHttps - это хороший подход.
Чтобы полностью обойти проблему, вы также можете использовать IIS на локальном компьютере с самозаверяющим сертификатом. IIS работает быстрее, чем встроенный веб-сервер, и у вас есть преимущество в том, что ваша среда разработки больше похожа на рабочую.
Используя систему фильтров 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());
}
}
Поскольку именно сервер разработки ASP.Net вызвал вашу проблему в первую очередь, стоит отметить, что у Microsoft теперь есть IIS Express, который поставляется с Visual Studio (начиная с VS2010 SP1). Это урезанная версия IIS, которая так же проста в использовании, как и сервер разработки, но поддерживает полный набор функций IIS 7.5, включая SSL.
Скотт Хансельман имеет подробный пост о работе с SSL в IIS Express.
Как насчет наследования атрибута RequireHttps в пользовательском атрибуте. Затем, внутри вашего пользовательского атрибута, проверьте свойство IsLocal текущего запроса, чтобы увидеть, поступает ли запрос с локального компьютера. Если это так, то не применяйте базовую функциональность. В противном случае вызовите базовую операцию.
Это сработало для меня, 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));
}
});
}
Одно решение, которое вы можете использовать как на производстве, так и на рабочей станции. Это зависит от вашего варианта из настроек приложения в 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)
и что-то подобное в вашем представлении входа в систему, чтобы разместить форму через https.
<% using (Html.BeginFormSecure("LogOn", "Account", new { ReturnUrl = Request.QueryString["ReturnUrl"] }, Request.IsSecureConnection, Request.Url)) { %>
Как упоминал Джоэл, вы можете изменить компиляцию, используя #if !DEBUG
директивы.
Я только что узнал, что вы можете изменить значение символа DEBUG в элементе компиляции файла web.config. Надеюсь, это поможет.
Для MVC 3 я добавил свой собственный FilterProvider (на основе кода, найденного здесь: глобальный и условный фильтры, который, помимо прочего (отображение информации об отладке для локальных пользователей и т. Д.), Будет украшать все действия с помощью RequireHttpsAttribute
когда HttpContext.Request.IsLocal == false
,
После исследования aroud я смог решить эту проблему с помощью IIS Express и переопределения метода OnAuthorization класса Controller (ссылка № 1). Я также пошел по маршруту, рекомендованному Гансельманом (ссылка № 2). Однако я не был полностью удовлетворен этими двумя решениями по двум причинам: 1. OnAuthorization в ReF#1 работает только на уровне действий, а не на уровне класса контроллера 2. ReF#2 требует большой настройки (Win7 SDK для makecert), команды netsh и, чтобы использовать порт 80 и порт 443, мне нужно запустить VS2010 в качестве администратора, на что я нахмурился.
Итак, я придумал это решение, которое фокусируется на простоте при следующих условиях:
Я хочу иметь возможность использовать attbbute RequireHttps на уровне контроллера или уровне действия
Я хочу, чтобы MVC использовал HTTPS, когда присутствует атрибут RequireHttps, и использую HTTP, если он отсутствует
Я не хочу запускать Visual Studio от имени администратора
Я хочу использовать любые порты HTTP и HTTPS, назначенные IIS Express (см. Примечание № 1).
Я могу повторно использовать самозаверяющий сертификат SSL IIS Express, и мне все равно, вижу ли я неверное приглашение SSL
Я хочу, чтобы dev, test и production имели одинаковую кодовую базу и тот же двоичный файл и как можно более независимые от дополнительных настроек (например, используя netsh, mmc-оснастку cert и т. Д.), Насколько это возможно.
Теперь, с учетом фона и объяснений, я надеюсь, что этот код поможет кому-то и сэкономит время. По сути, создайте класс Base Controller, который наследуется от Controller, и выведите классы контроллера из этого базового класса. Поскольку вы читали это далеко, я предполагаю, что вы знаете, как это сделать. Итак, счастливого кодирования!
Примечание № 1: Это достигается с помощью полезной функции 'getConfig' (см. Код)
Ссылка № 1: http://puredotnetcoder.blogspot.com/2011/09/requirehttps-attribute-in-mvc3.html
Ссылка № 2: http://www.hanselman.com/blog/WorkingWithSSLAtDevelopmentTimeIsEasierWithIISExpress.aspx
========== Код в Base Controller ===================
#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 stackru.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>
Если вы можете извлечь и переопределить - сделайте это. Если вы не можете - MVC поставляется с источниками, просто возьмите источники и создайте свой собственный атрибут [ForceHttps], который проверяет IsLocal.
MVC 6 (ASP.NET Core 1.0):
Правильным решением было бы использовать env.IsProduction() или env.IsDevelopment(). Узнайте больше о причинах в этом ответе о том, как требовать https только в производстве.
Сокращенный ответ ниже (см. Ссылку выше, чтобы узнать больше о дизайнерских решениях) для 2 разных стилей:
- 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);
}
}
Это был самый чистый путь для меня. В моем 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());
}
...
}
Вы можете установить в global.asax использование только SSL. Затем используйте #if !DEBUG, чтобы избежать использования RequireHttpsAttribute() на локальном компьютере.
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(System.Web.Http.GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
#if !DEBUG
GlobalFilters.Filters.Add(new RequireHttpsAttribute());
#endif
}
}
Пожалуйста, обратитесь к этому сообщению Рика Андерсона о RickAndMSFT в Azure и MVC. Заполнение пробела в Azure.