Przeszedłem przez to pytanie i mam nadzieję, że moje rozwiązanie może komuś pomóc.
Mamy kilka problemów: - Musimy zabezpieczyć określone akcje, na przykład „Zaloguj się” w „Konto”. Możemy użyć kompilacji w atrybucie RequireHttps, co jest świetne - ale przekieruje nas z powrotem za pomocą https: //. - Powinniśmy uwrażliwić nasze linki, formularze i takie „SSL”.
Generalnie moje rozwiązanie oprócz możliwości określenia protokołu pozwala na określenie tras, które będą korzystały z bezwzględnego adresu URL. Możesz użyć tej metody, aby określić protokół „https”.
Więc najpierw utworzyłem wyliczenie ConnectionProtocol:
public enum ConnectionProtocol
{
Ignore,
Http,
Https
}
Teraz stworzyłem ręcznie rozwijaną wersję RequireSsl. Zmodyfikowałem oryginalny kod źródłowy RequireSsl, aby umożliwić przekierowanie z powrotem do adresu http: // URL. Dodatkowo umieściłem pole, które pozwala nam określić, czy powinniśmy wymagać SSL, czy nie (używam go z preprocesorem 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);
}
}
Teraz ten RequireSsl wykona następującą podstawę na podstawie wartości atrybutu Requirements: - Ignore: nic nie zrobi. - Http: wymusi przekierowanie do protokołu http. - Https: wymusi przekierowanie do protokołu https.
Należy utworzyć własny kontroler podstawowy i ustawić ten atrybut na Http.
[RequireSsl(Requirement = ConnectionProtocol.Http)]
public class MyController : Controller
{
public MyController() { }
}
Teraz w każdym cpntroller / akcji, które chcesz wymagać SSL - po prostu ustaw ten atrybut za pomocą ConnectionProtocol.Https.
Przejdźmy teraz do adresów URL: Mamy kilka problemów z silnikiem routingu adresów URL. Więcej na ich temat można przeczytać pod adresem http://blog.stevensanderson.com/2008/08/05/adding-httpsssl-support-to-aspnet-mvc-routing/ . Rozwiązanie sugerowane w tym poście jest teoretycznie dobre, ale stare i nie podoba mi się podejście.
Moje rozwiązania są następujące: Utwórz podklasę podstawowej klasy „Trasa”:
klasa publiczna 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
}
Ta wersja klasy „Route” utworzy bezwzględny adres URL. Sztuczka tutaj, po której następuje sugestia autora posta na blogu, polega na użyciu DataToken do określenia schematu (przykład na końcu :)).
Teraz, jeśli wygenerujemy adres URL, na przykład dla trasy „Konto / Logowanie”, otrzymamy „/ http://example.com/Account/LogOn ” - to dlatego, że UrlRoutingModule traktuje wszystkie adresy URL jako względne. Możemy to naprawić za pomocą niestandardowego 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);
}
}
}
}
Ponieważ ten moduł zastępuje podstawową implementację UrlRoutingModule, powinniśmy usunąć podstawowy moduł httpModule i zarejestrować nasz w pliku web.config. Tak więc w zestawie „system.web”:
<httpModules>
<remove name="UrlRoutingModule-4.0" />
<add name="UrlRoutingModule-4.0" type="MyApp.Web.Mvc.Routing.AbsoluteUrlRoutingModule" />
</httpModules>
Otóż to :).
Aby zarejestrować ścieżkę bezwzględną / śledzoną według protokołu, należy:
routes.Add(new AbsoluteUrlRoute("Account/LogOn", new MvcRouteHandler())
{
Defaults = new RouteValueDictionary(new {controller = "Account", action = "LogOn", area = ""}),
DataTokens = new RouteValueDictionary(new {scheme = "https"})
});
Z przyjemnością usłyszę Twoją opinię + ulepszenia. Mam nadzieję, że to pomoże! :)
Edycja: zapomniałem dołączyć metodę rozszerzenia IsCurrentConnectionSecured () (za dużo fragmentów: P). Jest to metoda rozszerzenia, która zwykle używa Request.IsSecuredConnection. Jednak to podejście nie zadziała przy korzystaniu z równoważenia obciążenia - więc ta metoda może to ominąć (wzięte z nopCommerce).
public static bool IsCurrentConnectionSecured(this HttpRequestBase request)
{
return request != null && request.IsSecureConnection;
}