Jak mogę przejść do korzystania z protokołu HTTPS dla niektórych stron w mojej witrynie opartej na ASP.NET MVC?
Steve Sanderson ma całkiem niezły tutorial, jak to zrobić w SUCHY sposób w Preview 4 pod adresem:
http://blog.codeville.net/2008/08/05/adding-httpsssl-support-to-aspnet-mvc-routing/
Czy jest lepszy / zaktualizowany sposób w wersji Preview 5 ?,
asp.net
asp.net-mvc
ssl
https
David Laing
źródło
źródło
Odpowiedzi:
Jeśli używasz ASP.NET MVC 2 Preview 2 lub nowszego , możesz teraz po prostu użyć:
[RequireHttps] public ActionResult Login() { return View(); }
Warto jednak zwrócić uwagę na parametr order, o którym mowa tutaj .
źródło
MVCFutures ma atrybut „RequireSSL”.
(dziękuję Adamowi za wskazanie tego w zaktualizowanym poście na blogu)
Po prostu zastosuj go do swojej metody akcji, z „Redirect = true”, jeśli chcesz, aby żądanie http: // automatycznie stało się https: //:
[RequireSsl(Redirect = true)]
Zobacz też: ASP.NET MVC RequireHttps in Production
źródło
Jak napisał Amadiere , [RequireHttps] działa świetnie w MVC 2 do wprowadzania HTTPS. Ale jeśli chcesz używać HTTPS tylko dla niektórych stron, jak powiedziałeś, MVC 2 nie daje ci żadnej miłości - po przełączeniu użytkownika na HTTPS utkną tam, dopóki nie przekierujesz ich ręcznie.
Podejście, które zastosowałem, polega na użyciu innego atrybutu niestandardowego, [ExitHttpsIfNotRequired]. Po podłączeniu do kontrolera lub akcji przekieruje do HTTP, jeśli:
Jest trochę za duży, aby opublikować tutaj, ale możesz zobaczyć kod tutaj i kilka dodatkowych szczegółów.
źródło
Oto najnowszy post od Dana Wahlina na ten temat:
http://weblogs.asp.net/dwahlin/archive/2009/08/25/requiring-ssl-for-asp-net-mvc-controllers.aspx
Używa atrybutu ActionFilter.
źródło
Niektóre rozszerzenia ActionLink: http://www.squaredroot.com/post/2008/06/11/MVC-and-SSL.aspx lub atrybut akcji kontrolera, który przekierowuje do https: // http://forums.asp.net /p/1260198/2358380.aspx#2358380
źródło
Dla tych, którzy nie są fanami podejść do programowania zorientowanych na atrybuty, oto fragment kodu, który może pomóc:
public static readonly string[] SecurePages = new[] { "login", "join" }; protected void Application_AuthorizeRequest(object sender, EventArgs e) { var pageName = RequestHelper.GetPageNameOrDefault(); if (!HttpContext.Current.Request.IsSecureConnection && (HttpContext.Current.Request.IsAuthenticated || SecurePages.Contains(pageName))) { Response.Redirect("https://" + Request.ServerVariables["HTTP_HOST"] + HttpContext.Current.Request.RawUrl); } if (HttpContext.Current.Request.IsSecureConnection && !HttpContext.Current.Request.IsAuthenticated && !SecurePages.Contains(pageName)) { Response.Redirect("http://" + Request.ServerVariables["HTTP_HOST"] + HttpContext.Current.Request.RawUrl); } }
Istnieje kilka powodów, dla których należy unikać atrybutów, a jednym z nich jest to, że jeśli chcesz spojrzeć na listę wszystkich zabezpieczonych stron, będziesz musiał przeskoczyć przez wszystkie kontrolery w rozwiązaniu.
źródło
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:
/// <summary> /// Enum representing the available secure connection requirements /// </summary> public enum ConnectionProtocol { /// <summary> /// No secure connection requirement /// </summary> Ignore, /// <summary> /// No secure connection should be used, use standard http request. /// </summary> Http, /// <summary> /// The connection should be secured using SSL (https protocol). /// </summary> 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).
/* Note: * This is hand-rolled version of the original System.Web.Mvc.RequireHttpsAttribute. * This version contains three improvements: * - Allows to redirect back into http:// addresses, based on the <see cref="SecureConnectionRequirement" /> Requirement property. * - Allows to turn the protocol scheme redirection off based on given condition. * - Using Request.IsCurrentConnectionSecured() extension method, which contains fix for load-balanced servers. */ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)] public sealed class RequireHttpsAttribute : FilterAttribute, IAuthorizationFilter { public RequireHttpsAttribute() { Protocol = ConnectionProtocol.Ignore; } /// <summary> /// Gets or sets the secure connection required protocol scheme level /// </summary> public ConnectionProtocol Protocol { get; set; } /// <summary> /// Gets the value that indicates if secure connections are been allowed /// </summary> 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"); } /* Are we allowed to use secure connections? */ 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) { // only redirect for GET requests, otherwise the browser might not propagate the verb and request // body correctly. if (!String.Equals(filterContext.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase)) { throw new InvalidOperationException("The requested resource can only be accessed via SSL."); } // redirect to HTTPS version of page 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."); } // redirect to HTTP version of page 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
/// <summary> /// Initializes a new instance of the System.Web.Routing.Route class, by using /// the specified URL pattern and handler class. /// </summary> /// <param name="url">The URL pattern for the route.</param> /// <param name="routeHandler">The object that processes requests for the route.</param> public AbsoluteUrlRoute(string url, IRouteHandler routeHandler) : base(url, routeHandler) { } /// <summary> /// Initializes a new instance of the System.Web.Routing.Route class, by using /// the specified URL pattern and handler class. /// </summary> /// <param name="url">The URL pattern for the route.</param> /// <param name="defaults">The values to use for any parameters that are missing in the URL.</param> /// <param name="routeHandler">The object that processes requests for the route.</param> public AbsoluteUrlRoute(string url, RouteValueDictionary defaults, IRouteHandler routeHandler) : base(url, defaults, routeHandler) { } /// <summary> /// Initializes a new instance of the System.Web.Routing.Route class, by using /// the specified URL pattern and handler class. /// </summary> /// <param name="url">The URL pattern for the route.</param> /// <param name="defaults">The values to use for any parameters that are missing in the URL.</param> /// <param name="constraints">A regular expression that specifies valid values for a URL parameter.</param> /// <param name="routeHandler">The object that processes requests for the route.</param> public AbsoluteUrlRoute(string url, RouteValueDictionary defaults, RouteValueDictionary constraints, IRouteHandler routeHandler) : base(url, defaults, constraints, routeHandler) { } /// <summary> /// Initializes a new instance of the System.Web.Routing.Route class, by using /// the specified URL pattern and handler class. /// </summary> /// <param name="url">The URL pattern for the route.</param> /// <param name="defaults">The values to use for any parameters that are missing in the URL.</param> /// <param name="constraints">A regular expression that specifies valid values for a URL parameter.</param> /// <param name="dataTokens">Custom values that are passed to the route handler, but which are not used /// to determine whether the route matches a specific URL pattern. These values /// are passed to the route handler, where they can be used for processing the /// request.</param> /// <param name="routeHandler">The object that processes requests for the route.</param> 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 /// <summary> /// Creates an absolute url /// </summary> /// <param name="requestContext">The request context</param> /// <param name="virtualPath">The initial virtual relative path</param> /// <param name="scheme">The protocol scheme</param> /// <returns>The absolute URL</returns> 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> <!-- Removing the default UrlRoutingModule and inserting our own absolute url routing module --> <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).
/// <summary> /// Gets a value indicating whether current connection is secured /// </summary> /// <param name="request">The base request context</param> /// <returns>true - secured, false - not secured</returns> /// <remarks><![CDATA[ This method checks whether or not the connection is secured. /// There's a standard Request.IsSecureConnection attribute, but it won't be loaded correctly in case of load-balancer. /// See: <a href="http://nopcommerce.codeplex.com/SourceControl/changeset/view/16de4a113aa9#src/Libraries/Nop.Core/WebHelper.cs">nopCommerce WebHelper IsCurrentConnectionSecured()</a>]]></remarks> public static bool IsCurrentConnectionSecured(this HttpRequestBase request) { return request != null && request.IsSecureConnection; // when your hosting uses a load balancer on their server then the Request.IsSecureConnection is never got set to true, use the statement below // just uncomment it //return request != null && request.ServerVariables["HTTP_CLUSTER_HTTPS"] == "on"; }
źródło
Oto post na blogu autorstwa Pablo M. Cibrano ze stycznia 2009, w którym zebrano kilka technik, w tym HttpModule i metody rozszerzające.
źródło
Oto post na blogu Adama Salvo, który używa filtra ActionFilter.
źródło
Niekoniecznie jest to specyficzne dla MVC, ale to rozwiązanie działa zarówno dla ASP.NET WebForms, jak i MVC:
http://www.codeproject.com/KB/web-security/WebPageSecurity_v2.aspx
Używam tego od kilku lat i podoba mi się oddzielanie problemów i zarządzanie za pomocą pliku web.config.
źródło
MVC 6 (ASP.NET Core 1.0) działa nieco inaczej z Startup.cs.
Aby użyć RequireHttpsAttribute (jak wspomniano w odpowiedzi Amadiere) na wszystkich stronach, możesz dodać to w Startup.cs zamiast używać stylu atrybutu na każdym kontrolerze (lub zamiast tworzyć BaseController dla wszystkich kontrolerów, z których będą dziedziczyć).
Startup.cs - filtr rejestru:
public void ConfigureServices(IServiceCollection services) { // TODO: Register other services services.AddMvc(options => { options.Filters.Add(typeof(RequireHttpsAttribute)); }); }
Aby uzyskać więcej informacji na temat decyzji projektowych dla powyższego podejścia, zobacz moją odpowiedź na podobne pytanie dotyczące wykluczania żądań hosta lokalnego z obsługi przez RequireHttpsAttribute .
źródło
Alternatywnie dodaj filtr do Global.asax.cs
RequireHttpsAttribute Class
using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; namespace xxxxxxxx { public class MvcApplication : System.Web.HttpApplication { protected void Application_Start() { AreaRegistration.RegisterAllAreas(); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); GlobalFilters.Filters.Add(new RequireHttpsAttribute()); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); } } }
źródło