Я зіткнувся з цим питанням і сподіваюся, що моє рішення може комусь допомогти.
У нас було декілька проблем: - Нам потрібно захистити певні дії, наприклад "LogOn" в "Account". Ми можемо використовувати збірку в атрибуті RequireHttps, що чудово - але це перенаправить нас назад за допомогою https: //. - Ми повинні робити наші посилання, форми та такі "SSL обізнані".
Як правило, моє рішення дозволяє вказати маршрути, які використовуватимуть абсолютну URL-адресу, крім можливості вказати протокол. Ви можете використовувати цей підхід для вказівки протоколу "https".
Отже, спочатку я створив перелік ConnectionProtocol:
public enum ConnectionProtocol
{
Ignore,
Http,
Https
}
Тепер я створив ручну версію RequireSsl. Я змінив вихідний вихідний код RequireSsl, щоб дозволити перенаправлення назад на http: // urls. Крім того, я поставив поле, яке дозволяє нам визначити, чи потрібно нам вимагати SSL чи ні (я використовую його з попереднім процесором DEBUG).
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public sealed class RequireHttpsAttribute : FilterAttribute, IAuthorizationFilter
{
public RequireHttpsAttribute()
{
Protocol = ConnectionProtocol.Ignore;
}
public ConnectionProtocol Protocol { get; set; }
public bool SecureConnectionsAllowed
{
get
{
#if DEBUG
return false;
#else
return true;
#endif
}
}
public void OnAuthorization(System.Web.Mvc.AuthorizationContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
if (!SecureConnectionsAllowed)
return;
switch (Protocol)
{
case ConnectionProtocol.Https:
if (!filterContext.HttpContext.Request.IsCurrentConnectionSecured())
{
HandleNonHttpsRequest(filterContext);
}
break;
case ConnectionProtocol.Http:
if (filterContext.HttpContext.Request.IsCurrentConnectionSecured())
{
HandleNonHttpRequest(filterContext);
}
break;
}
}
private void HandleNonHttpsRequest(AuthorizationContext filterContext)
{
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);
}
private void HandleNonHttpRequest(AuthorizationContext filterContext)
{
if (!String.Equals(filterContext.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException("The requested resource can only be accessed without SSL.");
}
string url = "http://" + filterContext.HttpContext.Request.Url.Host + filterContext.HttpContext.Request.RawUrl;
filterContext.Result = new RedirectResult(url);
}
}
Тепер цей RequireSsl зробить наступну базу на вашому значенні атрибуту Requirements: - Ignore: Нічого не робитиме. - Http: Примусове перенаправлення на протокол http. - Https: Примусове перенаправлення на протокол https.
Вам слід створити власний базовий контролер і встановити цей атрибут Http.
[RequireSsl(Requirement = ConnectionProtocol.Http)]
public class MyController : Controller
{
public MyController() { }
}
Тепер у кожному cpntroller / дії, для якого ви хочете вимагати SSL - просто встановіть цей атрибут за допомогою ConnectionProtocol.Https.
Тепер давайте перейдемо до URL-адрес: У нас було кілька проблем з механізмом маршрутизації URL-адрес. Ви можете прочитати більше про них на веб-сайті http://blog.stevensanderson.com/2008/08/05/adding-httpsssl-support-to-aspnet-mvc-routing/ . Рішення, запропоноване в цій публікації, теоретично хороше, але старе, і я не люблю схвалення.
Моє рішення полягає в наступному: Створіть підклас базового класу "Маршрут":
відкритий клас AbsoluteUrlRoute: Маршрут {#region ctor
public AbsoluteUrlRoute(string url, IRouteHandler routeHandler)
: base(url, routeHandler)
{
}
public AbsoluteUrlRoute(string url, RouteValueDictionary defaults, IRouteHandler routeHandler)
: base(url, defaults, routeHandler)
{
}
public AbsoluteUrlRoute(string url, RouteValueDictionary defaults, RouteValueDictionary constraints,
IRouteHandler routeHandler)
: base(url, defaults, constraints, routeHandler)
{
}
public AbsoluteUrlRoute(string url, RouteValueDictionary defaults, RouteValueDictionary constraints,
RouteValueDictionary dataTokens, IRouteHandler routeHandler)
: base(url, defaults, constraints, dataTokens, routeHandler)
{
}
#endregion
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
var virtualPath = base.GetVirtualPath(requestContext, values);
if (virtualPath != null)
{
var scheme = "http";
if (this.DataTokens != null && (string)this.DataTokens["scheme"] != string.Empty)
{
scheme = (string) this.DataTokens["scheme"];
}
virtualPath.VirtualPath = MakeAbsoluteUrl(requestContext, virtualPath.VirtualPath, scheme);
return virtualPath;
}
return null;
}
#region Helpers
private string MakeAbsoluteUrl(RequestContext requestContext, string virtualPath, string scheme)
{
return string.Format("{0}://{1}{2}{3}{4}",
scheme,
requestContext.HttpContext.Request.Url.Host,
requestContext.HttpContext.Request.ApplicationPath,
requestContext.HttpContext.Request.ApplicationPath.EndsWith("/") ? "" : "/",
virtualPath);
}
#endregion
}
Ця версія класу "Маршрут" створить абсолютну URL-адресу. Фокус, за яким слідує пропозиція автора допису в блозі, полягає у використанні DataToken для визначення схеми (приклад в кінці :)).
Тепер, якщо ми будемо генерувати URL-адресу, наприклад, для маршруту "Account / LogOn", ми отримаємо "/ http://example.com/Account/LogOn " - це тому, що UrlRoutingModule розглядає всі URL-адреси як відносні. Ми можемо це виправити за допомогою користувацького HttpModule:
public class AbsoluteUrlRoutingModule : UrlRoutingModule
{
protected override void Init(System.Web.HttpApplication application)
{
application.PostMapRequestHandler += application_PostMapRequestHandler;
base.Init(application);
}
protected void application_PostMapRequestHandler(object sender, EventArgs e)
{
var wrapper = new AbsoluteUrlAwareHttpContextWrapper(((HttpApplication)sender).Context);
}
public override void PostResolveRequestCache(HttpContextBase context)
{
base.PostResolveRequestCache(new AbsoluteUrlAwareHttpContextWrapper(HttpContext.Current));
}
private class AbsoluteUrlAwareHttpContextWrapper : HttpContextWrapper
{
private readonly HttpContext _context;
private HttpResponseBase _response = null;
public AbsoluteUrlAwareHttpContextWrapper(HttpContext context)
: base(context)
{
this._context = context;
}
public override HttpResponseBase Response
{
get
{
return _response ??
(_response =
new AbsoluteUrlAwareHttpResponseWrapper(_context.Response));
}
}
private class AbsoluteUrlAwareHttpResponseWrapper : HttpResponseWrapper
{
public AbsoluteUrlAwareHttpResponseWrapper(HttpResponse response)
: base(response)
{
}
public override string ApplyAppPathModifier(string virtualPath)
{
int length = virtualPath.Length;
if (length > 7 && virtualPath.Substring(0, 7) == "/http:/")
return virtualPath.Substring(1);
else if (length > 8 && virtualPath.Substring(0, 8) == "/https:/")
return virtualPath.Substring(1);
return base.ApplyAppPathModifier(virtualPath);
}
}
}
}
Оскільки цей модуль замінює базову реалізацію UrlRoutingModule, нам слід видалити базовий httpModule та зареєструвати наш у web.config. Отже, під "system.web" встановіть:
<httpModules>
<remove name="UrlRoutingModule-4.0" />
<add name="UrlRoutingModule-4.0" type="MyApp.Web.Mvc.Routing.AbsoluteUrlRoutingModule" />
</httpModules>
Це воно :).
Для того, щоб зареєструвати абсолютний / протокол, яким слід маршрут, вам слід зробити:
routes.Add(new AbsoluteUrlRoute("Account/LogOn", new MvcRouteHandler())
{
Defaults = new RouteValueDictionary(new {controller = "Account", action = "LogOn", area = ""}),
DataTokens = new RouteValueDictionary(new {scheme = "https"})
});
Будемо раді почути ваші відгуки + вдосконалення. Сподіваюся, це може допомогти! :)
Редагувати: я забув включити метод розширення IsCurrentConnectionSecured () (занадто багато фрагментів: P). Це метод розширення, який зазвичай використовує Request.IsSecuredConnection. Однак цей підхід не буде працювати при використанні балансування навантаження - тому цей метод може обійти це (взяте з nopCommerce).
public static bool IsCurrentConnectionSecured(this HttpRequestBase request)
{
return request != null && request.IsSecureConnection;
}