Ich habe diese Frage beantwortet und hoffe, dass meine Lösung jemandem helfen kann.
Wir haben einige Probleme: - Wir müssen bestimmte Aktionen sichern, zum Beispiel "LogOn" in "Account". Wir können das Build-in-RequireHttps-Attribut verwenden, was großartig ist - aber es leitet uns mit https: // zurück. - Wir sollten unsere Links, Formulare und solche "SSL-fähig" machen.
Im Allgemeinen ermöglicht meine Lösung die Angabe von Routen, die eine absolute URL verwenden, zusätzlich zur Möglichkeit, das Protokoll anzugeben. Mit diesem Ansatz können Sie das "https" -Protokoll angeben.
Also habe ich zuerst eine ConnectionProtocol-Enumeration erstellt:
public enum ConnectionProtocol
{
Ignore,
Http,
Https
}
Jetzt habe ich eine handgerollte Version von RequireSsl erstellt. Ich habe den ursprünglichen RequireSsl-Quellcode geändert, um die Umleitung zurück zu http: // urls zu ermöglichen. Außerdem habe ich ein Feld eingefügt, mit dem wir bestimmen können, ob SSL erforderlich ist oder nicht (ich verwende es mit dem DEBUG-Vorprozessor).
[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);
}
}
Jetzt führt diese RequireSsl die folgende Basis basierend auf Ihrem Anforderungsattributwert aus: - Ignorieren: Nichts tun. - HTTP: Erzwingt die Umleitung zum http-Protokoll. - Https: Erzwingt die Umleitung zum https-Protokoll.
Sie sollten Ihren eigenen Basis-Controller erstellen und dieses Attribut auf HTTP setzen.
[RequireSsl(Requirement = ConnectionProtocol.Http)]
public class MyController : Controller
{
public MyController() { }
}
Jetzt legen Sie in jedem cpntroller / jeder Aktion, für die Sie SSL benötigen, dieses Attribut einfach mit ConnectionProtocol.Https fest.
Wechseln wir jetzt zu URLs: Wir haben einige Probleme mit der URL-Routing-Engine. Weitere Informationen finden Sie unter http://blog.stevensanderson.com/2008/08/05/adding-httpsssl-support-to-aspnet-mvc-routing/ . Die in diesem Beitrag vorgeschlagene Lösung ist theoretisch gut, aber alt und ich mag den Ansatz nicht.
Meine Lösungen lauten wie folgt: Erstellen Sie eine Unterklasse der Basisklasse "Route":
öffentliche Klasse AbsoluteUrlRoute: Route {#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
}
Diese Version der "Route" -Klasse erstellt eine absolute URL. Der Trick hier, gefolgt vom Vorschlag des Blogpost-Autors, besteht darin, das Schema mithilfe des DataToken anzugeben (Beispiel am Ende :)).
Wenn wir nun eine URL generieren, zum Beispiel für die Route "Account / LogOn", erhalten wir "/ http://example.com/Account/LogOn " - da das UrlRoutingModule alle URLs als relativ ansieht. Wir können das mit benutzerdefiniertem HttpModule beheben:
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);
}
}
}
}
Da dieses Modul die Basisimplementierung von UrlRoutingModule überschreibt, sollten wir das Basis-httpModule entfernen und unser Modul in web.config registrieren. Also unter "system.web" setzen:
<httpModules>
<remove name="UrlRoutingModule-4.0" />
<add name="UrlRoutingModule-4.0" type="MyApp.Web.Mvc.Routing.AbsoluteUrlRoutingModule" />
</httpModules>
Das ist es :).
Um eine absolute / protokollierte Route zu registrieren, sollten Sie Folgendes tun:
routes.Add(new AbsoluteUrlRoute("Account/LogOn", new MvcRouteHandler())
{
Defaults = new RouteValueDictionary(new {controller = "Account", action = "LogOn", area = ""}),
DataTokens = new RouteValueDictionary(new {scheme = "https"})
});
Ich werde gerne Ihr Feedback + Verbesserungen hören. Hoffe es kann helfen! :) :)
Bearbeiten: Ich habe vergessen, die Erweiterungsmethode IsCurrentConnectionSecured () einzuschließen (zu viele Snippets: P). Dies ist eine Erweiterungsmethode, die im Allgemeinen Request.IsSecuredConnection verwendet. Dieser Ansatz funktioniert jedoch nicht, wenn der Lastenausgleich verwendet wird. Daher kann diese Methode dies umgehen (aus nopCommerce übernommen).
public static bool IsCurrentConnectionSecured(this HttpRequestBase request)
{
return request != null && request.IsSecureConnection;
}