Статьи

Поймать System.Web / Owin Cookie Monster

Коржик-SittingФайлы 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.
  }
}

Ewsleep-печеньяВот как в коде выглядит спящий монстр 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 в коллекцию. Это должно быть гораздо более стабильное решение, поскольку оно предотвращает проблему, а не пытается ее исправить впоследствии.