像大腕一样切换 HTTP 和 HTTPS






4.96/5 (24投票s)
如何使用搜索引擎友好的重定向,自动强制执行和切换安全(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
。让我们定义一个名为 RequireSSL
的 Attribute
。
/// <summary>
/// Attribute decorated on classes that use SSL
/// <summary>
[AttributeUsage(AttributeTargets.Class)]
sealed public class RequireSSL : Attribute
{
}
在我们的示例项目中,login.aspx 和 signup.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);
}
}
我已经对上面的代码进行了注释,它应该相当直观。有几点我想指出:
- 第一点是代码已经过优化,仅在必要时才执行“HTTP <=> HTTPS”。
- 第二,唯一允许使用 SSL 的页面是那些用
RequireSSL
模块标记的页面。这些页面将被重定向到 HTTPS,而所有其他页面将自动重定向到使用 HTTP。 - 我想指出的第三点是
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:首次发布。