SSL pages under ASP.NET MVC

前端 未结 12 1020
情话喂你
情话喂你 2020-11-28 01:49

How do I go about using HTTPS for some of the pages in my ASP.NET MVC based site?

Steve Sanderson has a pretty good tutorial on how to do this in a DRY way on Previe

相关标签:
12条回答
  • 2020-11-28 02:42

    Alternately add a filter to Global.asax.cs

    GlobalFilters.Filters.Add(new RequireHttpsAttribute());

    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);
            }
        }
    }
    
    0 讨论(0)
  • 2020-11-28 02:44

    As Amadiere wrote, [RequireHttps] works great in MVC 2 for entering HTTPS. But if you only want to use HTTPS for some pages as you said, MVC 2 doesn't give you any love - once it switches a user to HTTPS they're stuck there until you manually redirect them.

    The approach I used is to use another custom attribute, [ExitHttpsIfNotRequired]. When attached to a controller or action this will redirect to HTTP if:

    1. The request was HTTPS
    2. The [RequireHttps] attribute wasn't applied to the action (or controller)
    3. The request was a GET (redirecting a POST would lead to all sorts of trouble).

    It's a bit too big to post here, but you can see the code here plus some additional details.

    0 讨论(0)
  • 2020-11-28 02:44

    I went accross this question and hope my solution can helps someone.

    We got few problems: - We need to secure specific actions, for instance "LogOn" in "Account". We can use the build in RequireHttps attribute, which is great - but it'll redirect us back with https://. - We should make our links, forms and such "SSL aware".

    Generally, my solution allows to specify routes that will use absolute url, in addition to the ability to specify the protocol. You can use this approch to specify the "https" protocol.

    So, firstly I've created an ConnectionProtocol enum:

    /// <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
    }
    

    Now, I've created hand-rolled version of RequireSsl. I've modified the original RequireSsl source code to allow redirection back to http:// urls. In addition, I've put a field that allows us to determine if we should require SSL or not (I'm using it with the DEBUG pre-processor).

    /* 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);
        }
    }
    

    Now, this RequireSsl will do the following base on your Requirements attribute value: - Ignore: Won't do nothing. - Http: Will force redirection to http protocol. - Https: Will force redirection to https protocol.

    You should create your own base controller and set this attribute to Http.

    [RequireSsl(Requirement = ConnectionProtocol.Http)]
    public class MyController : Controller
    {
        public MyController() { }
    }
    

    Now, in each cpntroller/action you'd like to require SSL - just set this attribute with ConnectionProtocol.Https.

    Now lets move to URLs: We got few problems with the url routing engine. You can read more about them at http://blog.stevensanderson.com/2008/08/05/adding-httpsssl-support-to-aspnet-mvc-routing/. The solution suggested in this post is theoreticly good, but old and I don't like the approch.

    My solutions is the following: Create a subclass of the basic "Route" class:

    public class 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
    }
    

    This version of "Route" class will create absolute url. The trick here, followed by the blog post author suggestion, is to use the DataToken to specify the scheme (example at the end :) ).

    Now, if we'll generate an url, for example for the route "Account/LogOn" we'll get "/http://example.com/Account/LogOn" - that's since the UrlRoutingModule sees all the urls as relative. We can fix that using custom 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);
                }
            }
        }
    }
    

    Since this module is overriding the base implementation of UrlRoutingModule, we should remove the base httpModule and register ours in web.config. So, under "system.web" set:

    <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>
    

    Thats it :).

    In order to register an absolute / protocol followed route, you should do:

            routes.Add(new AbsoluteUrlRoute("Account/LogOn", new MvcRouteHandler())
                {
                    Defaults = new RouteValueDictionary(new {controller = "Account", action = "LogOn", area = ""}),
                    DataTokens = new RouteValueDictionary(new {scheme = "https"})
                });
    

    Will love to hear your feedback + improvements. Hope it can help! :)

    Edit: I forgot to include the IsCurrentConnectionSecured() extension method (too many snippets :P). This is an extension method that generally uses Request.IsSecuredConnection. However, this approch will not work when using load-balancing - so this method can bypass this (took from 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";
        }
    
    0 讨论(0)
  • 2020-11-28 02:46

    Here's a blog post by Pablo M. Cibrano from January 2009 that gathers up a couple of techniques including a HttpModule and extension methods.

    0 讨论(0)
  • 2020-11-28 02:50

    Here's a blog post by Adam Salvo that uses an ActionFilter.

    0 讨论(0)
  • 2020-11-28 02:51

    This isn't necessarily MVC specific, but this solution does work for both ASP.NET WebForms and MVC:

    http://www.codeproject.com/KB/web-security/WebPageSecurity_v2.aspx

    I've used this for several years and like the separation of concerns and management via the web.config file.

    0 讨论(0)
提交回复
热议问题