Файлы cookie, установленные через API-интерфейс Owin, иногда загадочно исчезают. Проблема в том, что в глубине System.Web
души с незапамятных времен спал монстр cookie (ну, по крайней мере, с тех пор, как System.Web
был выпущен .NET ). Монстр спал все это время, но теперь, с наступлением нового времени вместе с Оуэном, монстр не спит. Будучи голодным от долгого сна, он ест печенье, установленное через API Оуэна, на завтрак. Даже если куки установлены правильно, они съедаются монстром до того, как Set-Cookie
заголовки отправляются в браузер клиента. Обычно это приводит к тому, что heisenbugs влияют на функциональность входа и выхода.
TL; DR
Проблема в том, что у System.Web
него есть собственный основной источник информации о куки, а это не Set-Cookie
заголовок. Овин знает только про шапку Set-Cookie
. Обходной путь должен удостовериться, что любые куки, установленные Owin, также установлены в HttpContext.Current.Response.Cookies
коллекции.
Это именно то, что делает мое промежуточное ПО Kentor.OwinCookieSaver . Он должен быть добавлен в конвейер Оуэна (обычно в Startup.Auth.cs
) перед любым промежуточным программным обеспечением, которое обрабатывает куки.
app.UseKentorOwinCookieSaver();
Промежуточное ПО для сохранения cookie сохраняет файлы cookie, установленные другим промежуточным программным обеспечением. К сожалению, это не надежно для файлов cookie, установленных кодом приложения (например, в действиях MVC). Причина в том, что System.Web
код обработки куки может выполняться после кода приложения, но до промежуточного программного обеспечения. Для файлов cookie, установленных кодом приложения, обходной путь, заключающийся в сохранении фиктивного значения в сеансах, является более безопасным.
Причина
System.Web
API был вокруг с рассвета .NET. В то время он был тесно связан с IIS и единственным API для веб-приложений. Как единственный, он мог предположить, что он был хозяином всей информации. В HttpResponse.cs есть проверка, изменилась ли коллекция файлов cookie (добавление файлов cookie не считается изменением), и в этом случае она стирает существующий Set-Cookie
заголовок.
if (_cookies.Changed || needToReset) { // delete all set cookie headers headers.Remove("Set-Cookie"); // write all the cookies again for(int c = 0; c < _cookies.Count; c++) { // Write the cookies, code removed for brevity. } }
Вот как в коде выглядит спящий монстр cookie. Он спит, потому что ничто не ставит под сомнение то, что коллекция Кука — мастер.
Но это все меняется, когда Оуин был представлен. Оуэн API не знает ничего о System.Web.HttpContext
. На самом деле, с Оуэном все в порядке, чтобы разорвать зависимость между веб-приложениями .NET и IIS. В Katana файлы cookie (в большинстве случаев) добавляются при вызове, к Response.Cookies.Append()
которому добавляется новый Set-Cookie
заголовок.
По сути, у нас есть система с противоречивыми взглядами на то, где хранится основная информация. Овин считает, что фактический заголовок является главным, а System.Web
коллекцию cookie-ответов — главным. Наличие конфликтующих мастеров никогда не является хорошей идеей. Это известная проблема для Katana, классифицированная как «High Impact».
Промежуточное программное обеспечение обходного пути
Конфликт между двумя близкими родственниками System.Web
и Оуэном является типичным семейным конфликтом. Старший ошибается, но не изменит взглядов только потому, что кто-то молодой появляется с новыми фактами. При посредничестве такого конфликта обычно легче заставить молодое поколение работать вокруг старшего. Изменение System.Web
неосуществимо, поэтому необходимо сосредоточиться на Оуэне.
Обходной путь промежуточного слоя я создал проверяет этот Set-Cookie
заголовок и синхронизирует его содержимого обратно в коллекцию печенья. Помещая его перед любым промежуточным программным обеспечением для обработки файлов cookie, он может сохранить файлы cookie от монстра, прежде чем System.Web
удалить заголовок.
Основной функцией промежуточного программного обеспечения является Invoke
метод.
public async override Task Invoke(IOwinContext context) { await Next.Invoke(context); var setCookie = context.Response.Headers.GetValues("Set-Cookie"); if(setCookie != null) { var cookies = CookieParser.Parse(setCookie); foreach(var c in cookies) { if(!HttpContext.Current.Response.Cookies.AllKeys.Contains(c.Name)) { HttpContext.Current.Response.Cookies.Add(c); } } } }
Логика довольно проста. Разобрать каждый Set-Cookie
заголовок в HttpCookie
объект и убедиться, что он присутствует в коллекции файлов cookie ответов. Для приложений, которые мы протестировали, это работает, но это обходной путь, а не реальное исправление. Пожалуйста, оставьте комментарий ниже, если вы обнаружите ситуацию, когда этот обходной путь не работает. Это очень ценная информация для других, имеющих такую же проблему.
Постоянное исправление
Я также пытаюсь исправить это навсегда, внеся свой вклад в ведение System.Web
в Катане. Исправление будет заключаться в том, чтобы напрямую перехватывать любые вызовы, чтобы установить Set-Cookie
заголовок и добавить их в файл cookie в коллекцию. Это должно быть гораздо более стабильное решение, поскольку оно предотвращает проблему, а не пытается ее исправить впоследствии.