ASP.NET MVC 下的 SSL 页面

Posted

技术标签:

【中文标题】ASP.NET MVC 下的 SSL 页面【英文标题】:SSL pages under ASP.NET MVC 【发布时间】:2010-09-14 11:30:23 【问题描述】:

如何对基于 ASP.NET MVC 的站点中的某些页面使用 HTTPS?

Steve Sanderson 在 Preview 4 上有一个关于如何以 DRY 方式执行此操作的非常好的教程:

http://blog.codeville.net/2008/08/05/adding-httpsssl-support-to-aspnet-mvc-routing/

Preview 5 是否有更好/更新的方法?,

【问题讨论】:

这是非常过时的。对于 MVC4 及更高版本,请参阅我的博文blogs.msdn.com/b/rickandy/archive/2012/03/23/… 【参考方案1】:

另外添加一个过滤器到 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);
        
    

【讨论】:

【参考方案2】:

MVC 6 (ASP.NET Core 1.0) 与 Startup.cs 的工作方式略有不同。

要在所有页面上使用 RequireHttpsAttribute(正如 Amadiere 在 answer 中提到的),您可以在 Startup.cs 中添加它,而不是在每个控制器上使用属性样式(或者而不是为所有控制器创建 BaseController 来继承)。

Startup.cs - 注册过滤器:

public void ConfigureServices(IServiceCollection services)

    // TODO: Register other services

    services.AddMvc(options =>
    
        options.Filters.Add(typeof(RequireHttpsAttribute));
    );

有关上述方法的设计决策的更多信息,请参阅我对有关 how to exclude localhost requests from being handled by the RequireHttpsAttribute 的类似问题的回答。

【讨论】:

【参考方案3】:

我遇到了这个问题,希望我的解决方案可以帮助某人。

我们遇到了一些问题: - 我们需要保护特定操作,例如“帐户”中的“登录”。我们可以使用 RequireHttps 属性中的构建,这很棒 - 但它会使用 https:// 将我们重定向回来。 - 我们应该让我们的链接、表单和此类“SSL 感知”。

一般来说,我的解决方案除了能够指定协议外,还允许指定将使用绝对 url 的路由。您可以使用此方法指定“https”协议。

所以,首先我创建了一个 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

现在,我创建了 RequireSsl 的手卷版本。我修改了原始的 RequireSsl 源代码以允许重定向回 http:// url。此外,我还添加了一个字段,让我们可以确定是否需要 SSL(我将它与 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);
    

现在,此 RequireSsl 将根据您的需求属性值执行以下操作: - 忽略:什么都不做。 - Http:将强制重定向到 http 协议。 - Https:将强制重定向到 https 协议。

您应该创建自己的基本控制器并将此属性设置为 Http。

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

    public MyController()  

现在,在您希望使用 SSL 的每个 cpntroller/action 中 - 只需使用 ConnectionProtocol.Https 设置此属性。

现在让我们转到 URL:我们在使用 url 路由引擎时遇到了一些问题。您可以在http://blog.stevensanderson.com/2008/08/05/adding-httpsssl-support-to-aspnet-mvc-routing/ 阅读有关它们的更多信息。这篇文章中建议的解决方案在理论上很好,但是老了,我不喜欢这种方法。

我的解决方案如下: 创建基本“Route”类的子类:

公共类 AbsoluteUrlRoute : 路由 #区域演员

    /// <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://1234",
                             scheme,
                             requestContext.HttpContext.Request.Url.Host,
                             requestContext.HttpContext.Request.ApplicationPath,
                             requestContext.HttpContext.Request.ApplicationPath.EndsWith("/") ? "" : "/",
                             virtualPath);
    

    #endregion

此版本的“Route”类将创建绝对 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>
  <!-- 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>

就是这样:)。

为了注册一个绝对/协议遵循的路线,你应该这样做:

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

很想听听您的反馈和改进。希望它可以帮助! :)

编辑: 我忘记包含 IsCurrentConnectionSecured() 扩展方法(太多 sn-ps :P)。这是一种扩展方法,一般使用 Request.IsSecuredConnection。但是,当使用负载平衡时,此方法将不起作用 - 因此此方法可以绕过此方法(取自 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";
    

【讨论】:

【参考方案4】:

对于那些不喜欢面向属性的开发方法的人,这里有一段代码可能会有所帮助:

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

有几个原因可以避免使用属性,其中之一是如果您想查看所有安全页面的列表,您将不得不跳过解决方案中的所有控制器。

【讨论】:

我想大多数人会在这个问题上不同意你的观点,尽管提供另一种方法总是有用的......【参考方案5】:

这不一定是 MVC 特定的,但此解决方案确实适用于 ASP.NET WebForms 和 MVC:

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

我已经使用了几年,喜欢通过 web.config 文件分离关注点和管理。

【讨论】:

【参考方案6】:

作为Amadiere wrote,[RequireHttps] 在 MVC 2 中非常适合进入 HTTPS。但是,如果您只想对 some 页面使用 HTTPS,如您所说,MVC 2 不会给您任何帮助 - 一旦将用户切换到 HTTPS,他们就会卡在那里,直到您手动重定向它们。

我使用的方法是使用另一个自定义属性,[ExitHttpsIfNotRequired]。当附加到控制器或操作时,如果出现以下情况,它将重定向到 HTTP:

    请求是 HTTPS [RequireHttps] 属性未应用于操作(或控制器) 请求是 GET(重定向 POST 会导致各种麻烦)。

这里有点太大了,但你可以看到the code here 以及一些额外的细节。

【讨论】:

AllowAnonymous 解决了这个问题。对于 MVC4 及更高版本,请参阅我的博文blogs.msdn.com/b/rickandy/archive/2012/03/23/…【参考方案7】:

如果您使用的是ASP.NET MVC 2 Preview 2 or higher,您现在可以简单地使用:

[RequireHttps]
public ActionResult Login()

   return View();

不过,order 参数值得注意,如mentioned here。

【讨论】:

您也可以在控制器级别执行此操作。更好的是,如果您希望整个应用程序都是 SSL,您可以创建一个基本控制器,为所有控制器扩展它,然后在其中应用属性。 或者您可以在 Global.asax GlobalFilters.Filters.Add(new RequireHttpsAttribute()); 中添加一个全局过滤器 MVC3; 不保证其他开发人员会使用您的派生控制器。您可以拨打一个电话强制使用 HTTPS - 请参阅我的博客文章 blogs.msdn.com/b/rickandy/archive/2012/03/23/…【参考方案8】:

MVCFutures 具有“RequireSSL”属性。

(感谢 Adam 在您更新的博文中提供 pointing that out)

只需将其应用于您的操作方法,如果您希望 http:// 请求自动变为 https://,请使用“Redirect=true”:

    [RequireSsl(Redirect = true)]

另见:ASP.NET MVC RequireHttps in Production Only

【讨论】:

我是否必须对其进行子类化才能处理 localhost 请求? 一种方法是为您的本地计算机创建一个证书并使用它。我认为要为 localhost 完全禁用它,您确实需要子类化或复制代码。不确定推荐的方法是什么 看起来它是密封的,所以我需要复制代码。真可惜。本地机器的证书只能在 IIS 中使用,但不能在开发 Web 服务器中使用。 @mr rogers - 看看这个:***.com/questions/1639707/… 更新到 MVC4+ 看我的博文blogs.msdn.com/b/rickandy/archive/2012/03/23/…【参考方案9】:

这是 Dan Wahlin 最近发表的一篇文章:

http://weblogs.asp.net/dwahlin/archive/2009/08/25/requiring-ssl-for-asp-net-mvc-controllers.aspx

他使用了一个 ActionFilter 属性。

【讨论】:

这看起来是目前最好的方法。 +1 一年后,因为 isLocal 调用帮助我解决了一个在 @@@ 中变得真正痛苦的问题 以上内容已过时,对于MVC4及更高版本,请看我的博文blogs.msdn.com/b/rickandy/archive/2012/03/23/…【参考方案10】:

这是一个使用 ActionFilter 的blog post by Adam Salvo。

【讨论】:

确保您看到他自己写的以下帖子:blog.salvoz.com/2009/04/25/…【参考方案11】:

这是 2009 年 1 月的 blog post by Pablo M. Cibrano,它收集了一些技术,包括 HttpModule 和扩展方法。

【讨论】:

【参考方案12】:

一些 ActionLink 扩展:http://www.squaredroot.com/post/2008/06/11/MVC-and-SSL.aspx 或重定向到 https://http://forums.asp.net/p/1260198/2358380.aspx#2358380

的控制器操作属性

【讨论】:

以上是关于ASP.NET MVC 下的 SSL 页面的主要内容,如果未能解决你的问题,请参考以下文章

ASP.Net MVC4排序检索分页的实现

在 ASP.NET MVC 5 应用程序中启用 SSL 会导致 OpenIdConnectProtocolValidator 问题

ASP.NET MVC:如何在 localhost 上自动禁用 [RequireHttps]?

InCell 编辑模式下的 Kendo UI ASP.Net MVC ForeignKey 列 DataSource

asp.net mvc 站点的安全最佳实践?

ASP.Net MVC开发基础学习笔记:走向MVC模式