我正在 VS2010/ASP.NET 4.0 中编写一个用于 IIS 7 的 HttpModule。该模块将通过加密查询字符串来强制执行查询字符串的安全性。
我希望这个模块完全独立于网站并对网站透明,以便网站不知道正在使用查询字符串加密的事实。这将首先确保页面/控件不必关心这个问题。其次,它将为生产环境启用查询字符串加密,并为非生产环境禁用它(通过从 Web.config 中删除 HTTP 模块)。
我将 HttpModule 设计为通过 Web.config 通过以下方式插入 IIS:
<configuration>
<system.web>
<httpModules>
<add name="QueryStringSecurityModule" type="MyHttpModules.QueryStringSecurityModule"/>
</httpModules>
</system.web>
</configuration>
模块本身如下所示:
public class QueryStringSecurityModule : IHttpModule
{
public virtual void Init(HttpApplication application)
{
application.BeginRequest += HandleBeginRequest;
application.EndRequest += HandleEndRequest;
application.ReleaseRequestState += HandleReleaseRequestState;
}
public virtual void Dispose()
{
}
private void HandleBeginRequest(object sender, EventArgs e)
{
// TODO : Decrypt the query string here and pass it on to the application
}
private void HandleEndRequest(object sender, EventArgs e)
{
// TODO : Twiddle thumbs
}
private void HandleReleaseRequestState(object sender, EventArgs e)
{
var response = HttpContext.Current.Response;
if (response.ContentType == "text/html")
{
response.Filter = new QueryStringSecurityStream(response.Filter);
}
}
}
有一个类 QueryStringSecurityStream 用于处理响应中的 HTML 输出,并通过将其中的查询字符串替换为加密的字符串来保护所有标签。
public QueryStringSecurityStream : Stream
{
public QueryStringSecurityStream(Stream stream)
: base()
{
}
public override void Write(byte[] buffer, int offset, int count)
{
var html = Encoding.Default.GetString(buffer, offset, count).ReplaceHRefsWithSecureHRefs();
var bytes = Encoding.Default.GetBytes(html);
this.stream.Write(bytes, 0, bytes.Length);
}
}
魔术发生或应该发生在 ReplaceHRefsWithSecureHRefs() 扩展方法中。
此方法需要整个HTML。它将使用细齿梳(即使用正则表达式)进行检查,找到所有锚标记,取出它们的 href 属性,将 href 值中的任何查询字符串替换为加密版本并返回 HTML。然后,此 HTML 将被写入响应流。
到目前为止,一切都很好。所有这一切都失败了,因为我怀疑 ReleaseRequestState 会针对单个请求多次引发。也就是说,对于一次 BeginRequest 的调用,会导致多次调用 ReleaseRequestState。
我正在寻找的是:
确认我的预感是正确的。我问过 Google 先生和 MSDN 先生,但没有找到任何确定的答案。我似乎记得在从 IIS 6 中运行的 ASMX Web 服务调试 WSDL 时遇到了类似的问题。在这种情况下,我通过缓存传入的字节流直到我拥有有效的 XML 并在修改后将其全部写出来解决了这个问题。
处理这种情况的正确方法。您可以将此具体表示单个 BeginRequest/多个 ReleaseRequestState 调用问题或查询字符串加密。
女士们先生们。启动你的引擎。让答案滚滚而来。
更新:
通过创建一个缓冲区来存储对 ReleaseRequestState 的多次调用的响应内容,我已经为自己解决了这个问题。在每次调用时,我都会检查标签是否存在,</html>
并在修改后写出缓冲到该点的内容(在我的情况下,加密<a>
标签中的查询字符串)。
所以:
- 将 StringBuilder 声明为 QueryStringSecurityModule 类中的私有字段成员(在我的例子中, StringBuilder 用作响应内容的缓冲区)。
- 在 BeginRequest 处初始化字段(在我的例子中,分配一个 StringBuilder)。
- 最终确定 EndRequest 的字段(在我的情况下将其设置为 null,尽管我已经读过 EndRequest 并不总是触发)
- 缓冲区字节被发送到自定义过滤器中的写入,直到我们找到一个关闭的 html 标记,此时我们修改缓冲区内容并将它们写入输出流。
有人想评论这种方法吗?