65.9K
CodeProject 正在变化。 阅读更多。
Home

像大腕一样切换 HTTP 和 HTTPS

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.96/5 (24投票s)

2010年2月23日

CPOL

4分钟阅读

viewsIcon

162467

downloadIcon

1101

如何使用搜索引擎友好的重定向,自动强制执行和切换安全(HTTPS/SSL)和非安全(HTTP/非SSL)网页,而无需硬编码绝对 URL。

引言

作为开发人员,当我们一次又一次地遇到相同的编码场景时,我们自然会倾向于封装编码逻辑并加以重用,以节省时间并最大程度地减少维护。

最近,在我开发一个名为 Bigshot Hotshot 的网站时,我重新评估了在安全(HTTPS/SSL)和非安全(HTTP/非SSL)页面之间切换的必要性。我注意到,在编码时,在大多数情况下,我们不会考虑使用 SSL。一个原因是(在撰写本文时)ASP.NET 开发服务器不支持 SSL。要测试 SSL 页面,我们需要将应用程序/网站添加到 IIS 并进行相应的配置。我想要解决的另一个问题是如何提示 IIS 某些页面应该始终使用 HTTPS,而其他页面应该始终使用 HTTP。为了使事情更加复杂,我还想到了 SEO(搜索引擎优化),以便在安全和非安全页面之间重定向不会对网站的 SEO 产生负面影响。

本文提出了一种解决上述问题的方法。为了简洁起见,我们将缩写短语:“在 HTTP 和 HTTPS 之间切换”为“HTTP <=> HTTPS”。

背景研究

解决方案/提案 1

在研究“HTTP <=> HTTPS”问题的潜在解决方案时,我偶然发现了一篇 Matt Sollars 的文章:自动切换 HTTP 和 HTTPS:版本 2。它是一个关于上述问题的精心编写的解决方案。我喜欢可以强制整个目录使用 SSL,以及强制单个页面使用 SSL。我还喜欢基于 web.config 的方法来指定哪些文件应使用 SSL。另一方面,这个解决方案比我需要的要复杂。此外,通过调用 Response.Redirect([path], true) 并结束 Response 来“HTTP <=> HTTPS”对 SEO 并不友好。

解决方案/提案 2

我遇到的另一个“HTTP <=> HTTPS”的解决方案是 Yohan B 的:ASP.NET 的 RequireSSL 属性。我喜欢基于 Attribute 的方法来指定某些页面必须使用 SSL。我还喜欢使用 #if DEBUG 指令告诉编译器,在 Debug 模式下运行时不要“HTTP <=> HTTPS”(因为 ASP.NET 开发服务器反正也不支持 SSL)。然而,我不喜欢的是使用一个基类 Page,所有其他 Page 都继承它来调用 Validate() 方法并控制“HTTP <=> HTTPS”。另外,与上述文章一样,通过调用 Response.Redirect([path], true) 并结束 Response 来“HTTP <=> HTTPS”对 SEO 并不友好。

我们的策略

下一节我们将探讨另一种“HTTP <=> HTTPS”的方法。我们将使用 Attribute 来标记需要 SSL 的 Page,并将实现一个自定义的 HTTP 模块,该模块负责拦截对我们 ASPX 页面的请求,并在必要时执行“HTTP <=> HTTPS”。我们还将探讨如何以 SEO 友好的方式实现这一点。

代码

首先,我们需要定义一个 Attribute,以便我们可以用该 Attribute 来装饰需要 SSL 的 Page。让我们定义一个名为 RequireSSLAttribute

/// <summary>
/// Attribute decorated on classes that use SSL
/// <summary>
[AttributeUsage(AttributeTargets.Class)]
sealed public class RequireSSL : Attribute
{
}

在我们的示例项目中,login.aspxsignup.aspx 页面需要 SSL。我们将相应地标记它们(注意 RequireSSL attribute)。

/// <summary>
/// The Login Page
/// </summary>
[RequireSSL]
public partial class login : System.Web.UI.Page
{
    ...
}

/// <summary>
/// The SignUp Page
/// </summary>    
[RequireSSL]
public partial class signup : System.Web.UI.Page
{
    ...
}

接下来,我们将实现我们的自定义 HTTP 模块。它将被配置为仅在 Release 模式下编译时运行“HTTP <=> HTTPS”的代码。

/// <summary>
/// HttpModule for switching between HTTP and HTTPS (HTTP <=> HTTPS)
/// </summary>
public class RequireSSLModule : IHttpModule
{
    public void Init(HttpApplication context)
    {
// only attach the event if the build is not set to Debug
#if !DEBUG
        // The PreRequestHandlerExecute event occurs just
        // before ASP.NET begins executing a handler such as a Page
        // In here we can acquire a reference
        // to the currently executing ASPX Page
        context.PreRequestHandlerExecute += 
           new EventHandler(OnPreRequestHandlerExecute);
#endif
    }
    
    ...
}

让我们更仔细地看一下 PreRequestHandlerExecute 事件。

/// <summary>
/// Handle switching between HTTP and HTTPS.
/// It only switches the scheme when necessary.
/// Note: By scheme we mean HTTP scheme or HTTPS scheme.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void OnPreRequestHandlerExecute(object sender, EventArgs e)
{
    // obtain a reference to the ASPX Page 
    System.Web.UI.Page Page = 
       HttpContext.Current.Handler as System.Web.UI.Page;

    // if a valid Page was not found, exit
    if (Page == null)
    {
        return;
    }

    // check if the Page is decorated with the RequireSSL attribute
    bool requireSSL = (Page.GetType().GetCustomAttributes(
                         typeof(RequireSSL), true).Length > 0);

    // check if the Page is currently using the HTTPS scheme
    bool isSecureConnection = 
      HttpContext.Current.ApplicationInstance.Request.IsSecureConnection;

    // acquire the URI (eg~ https:///default.aspx)
    Uri baseUri = HttpContext.Current.ApplicationInstance.Request.Url;

    // if the Page requires SSL and it is not currently
    // using HTTPS, switch out the scheme
    if (requireSSL && !isSecureConnection)
    {
        // switch the HTTP scheme to the HTTPS scheme
        string url = baseUri.ToString().Replace(
                         baseUri.Scheme, Uri.UriSchemeHttps);

        // perform a 301 redirect to the secure url
        PermanentRedirect(url);
    }
    // if the page does not require SSL and it is currently
    // using the HTTPS scheme, switch out the scheme
    else if (!requireSSL && isSecureConnection)
    {
        // switch the HTTPS scheme to the HTTP scheme
        string url = baseUri.ToString().Replace(baseUri.Scheme, 
                                                Uri.UriSchemeHttp);

        // perform a 301 redirect to the non-secure url
        PermanentRedirect(url);
    }
}

我已经对上面的代码进行了注释,它应该相当直观。有几点我想指出:

  1. 第一点是代码已经过优化,仅在必要时才执行“HTTP <=> HTTPS”。
  2. 第二,唯一允许使用 SSL 的页面是那些用 RequireSSL 模块标记的页面。这些页面将被重定向到 HTTPS,而所有其他页面将自动重定向到使用 HTTP。
  3. 我想指出的第三点是 PermanentRedirect([url]) 方法。此方法执行 301 重定向。这是将页面重定向到新位置的最搜索引擎友好的方式。301 状态码表示页面已永久移动到新位置。它的实现方式如下:
private void PermanentRedirect(string url)
{
    HttpContext.Current.Response.Status = "301 Moved Permanently";
    HttpContext.Current.Response.AddHeader("Location", url);
}

在即将推出的 ASP.NET 4.0 中,Microsoft 已将 PermanentRedirect 添加到 ASP.NET 框架本身。您可以在 此处阅读更多信息

现在唯一要做的事情是在 web.config 文件中注册 RequireSSL 模块注意:您可以安全地将 模块 注册到以下两个位置,这样您就不必担心您的网站是在 IIS6 还是 IIS7 上运行。

适用于 IIS6 或 IIS7 以经典模式运行

<configuration>
    <system.web>
        <httpModules>
            <add name="RequireSSL" type="Code.RequireSSLModule, Code" />
        </httpModules>
    </system.web>
</configuration>

适用于 IIS7 以集成模式运行

<configuration>
    <system.webServer>
        <modules>
            <add name="RequireSSL" 
               preCondition="managedHandler" 
               type="Code.RequireSSLModule, Code" />
        </modules>
    </system.webServer>
</configuration>

运行代码

从 Visual Studio 运行代码就像按键盘上的 F5 一样简单。但是,如果您想看到 RequireSSL 模块实际工作,您需要以 Release 模式编译项目,在 IIS 中配置网站,为其创建自签名 SSL 证书,并为其添加 HTTPS 绑定。如果您在设置 IIS7 中的 SSL 时需要帮助,可以参考这篇文章:使用自签名证书启用 IIS 7.0 上的 SSL

就是这样,这就是 Bigshot Hotshot 如何强制执行安全页面和“HTTP <=> HTTPS”。

祝您编码愉快!

历史

  • 2010/02/22:首次发布。
© . All rights reserved.