Я пишу HttpModule в VS2010 / ASP.NET 4.0 для использования с IIS 7. Модуль будет обеспечивать безопасность строки запроса путем шифрования строк запроса.
Я бы хотел, чтобы этот модуль был полностью независимым и прозрачным для веб-сайта, чтобы веб-сайт не знал о том, что используется шифрование строки запроса. Это будет гарантировать, во-первых, что страницы / элементы управления не должны заботиться об этой проблеме. Во-вторых, он включит шифрование строки запроса для производственных сред и отключит его для непроизводственных (путем удаления модуля HTTP из Web.config).
Я разработал HttpModule для подключения к IIS через Web.config:
<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 в Response и защиты всех тегов, заменяя строки запроса на зашифрованные.
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 вызывается несколько раз для отдельных запросов. То есть, существует несколько вызовов ReleaseRequestState в результате одного вызова BeginRequest.
Я ищу:
Подтверждение, что моя догадка верна. Я спросил мистера Google и мистера MSDN, но не нашел ничего определенного. Кажется, я помню, что сталкивался с чем-то похожим при отладке WSDL из веб-службы ASMX, работающей в IIS 6. В этом случае я решил проблему, кэшируя входящий поток байтов, пока у меня не будет действительный XML, а затем выписал все это после изменения.
Правильный способ справиться с подобным сценарием. Вы можете использовать это либо для обозначения конкретной проблемы BeginRequest / множественных вызовов ReleaseRequestState, либо для шифрования строки запроса в целом.
Дамы и господа. Запустите ваши двигатели. Пусть ответы накатятся.
Обновление:
Я прочитал эту статью о MSDN по запросу жизненного цикла
Я решил эту проблему для себя, создав буфер для хранения содержимого ответа при нескольких вызовах ReleaseRequestState. При каждом вызове я проверяю наличие тега </html>
и записываю содержимое, буферизованное до этого момента после модификации (в моем случае шифрование строк запроса в тегах <a>
).
Итак:
- Объявите StringBuilder как член частного поля в классе QueryStringSecurityModule (в моем случае StringBuilder в качестве буфера для содержимого ответа).
- Инициализируйте поле в BeginRequest (в моем случае выделите StringBuilder).
- Завершить поле в EndRequest (в моем случае установить его на ноль, хотя я читал, что EndRequest не всегда срабатывает)
- Буферные байты отправляются в Write в пользовательском фильтре, пока мы не найдем закрывающий HTML-тег, после чего мы изменяем содержимое буфера и записываем их в выходной поток.
Кто-нибудь хотел бы прокомментировать этот подход?