Mam dziwny problem z uwierzytelnianiem cookie Owin.
Kiedy uruchamiam, uwierzytelnianie serwera IIS działa doskonale w IE / Firefox i Chrome.
Zacząłem testować z uwierzytelnianiem i logować się na różnych platformach i napotkałem dziwny błąd. Sporadycznie framework / IIS Owin po prostu nie wysyła żadnych plików cookie do przeglądarek. Wpiszę nazwę użytkownika i hasło, które są poprawne, kod działa, ale żaden plik cookie nie jest w ogóle dostarczany do przeglądarki. Jeśli zrestartuję serwer, zacznie działać, to w pewnym momencie spróbuję się zalogować i ponownie pliki cookie przestają być dostarczane. Przechodzenie przez kod nic nie robi i nie powoduje żadnych błędów.
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
CookieHttpOnly = true,
AuthenticationType = "ABC",
LoginPath = new PathString("/Account/Login"),
CookiePath = "/",
CookieName = "ABC",
Provider = new CookieAuthenticationProvider
{
OnApplyRedirect = ctx =>
{
if (!IsAjaxRequest(ctx.Request))
{
ctx.Response.Redirect(ctx.RedirectUri);
}
}
}
});
W ramach procedury logowania mam następujący kod:
IAuthenticationManager authenticationManager = HttpContext.Current.GetOwinContext().Authentication;
authenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
var authentication = HttpContext.Current.GetOwinContext().Authentication;
var identity = new ClaimsIdentity("ABC");
identity.AddClaim(new Claim(ClaimTypes.Name, user.Username));
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.User_ID.ToString()));
identity.AddClaim(new Claim(ClaimTypes.Role, role.myRole.ToString()));
authentication.AuthenticationResponseGrant =
new AuthenticationResponseGrant(identity, new AuthenticationProperties()
{
IsPersistent = isPersistent
});
authenticationManager.SignIn(new AuthenticationProperties() {IsPersistent = isPersistent}, identity);
Aktualizacja 1: Wygląda na to, że jedną z przyczyn problemu jest to, że dodając elementy do sesji, zaczynają się problemy. Session.Content["ABC"]= 123
Wydaje się, że dodanie czegoś prostego, na przykład, powoduje problem.
Oto co mogę zobaczyć: 1) (Chrome) Kiedy loguję się, otrzymuję identyfikator ASP.NET_SessionId + mój plik cookie uwierzytelniania. 2) Przechodzę do strony, która ustawia sesję. Zawartość ... 3) Otwórz nową przeglądarkę (Firefox) i spróbuj się zalogować, ale nie otrzymuje ona identyfikatora ASP.NET_SessionId ani pliku cookie uwierzytelniania 4) Podczas gdy pierwsza przeglądarka ma ASP.NET_SessionId i nadal działa. W chwili, gdy usuwam ten plik cookie, ma ten sam problem, co wszystkie inne przeglądarki, na których pracuję, na adresie IP (10.xxx) i localhost.
Aktualizacja 2: Wymuś utworzenie ASPNET_SessionId
pierwszego na mojej stronie login_load przed uwierzytelnieniem za pomocą OWIN.
1) zanim uwierzytelnię się w OWIN, tworzę losową Session.Content
wartość na mojej stronie logowania, aby uruchomić ASP.NET_SessionId 2) następnie uwierzytelniam się i wykonuję kolejne sesje 3) Inne przeglądarki wydają się teraz działać
To jest dziwne. Mogę tylko stwierdzić, że ma to coś wspólnego z ASP i OWIN myślącymi, że są w różnych domenach lub czymś w tym rodzaju.
Aktualizacja 3 - Dziwne zachowanie między nimi.
Zidentyfikowano dodatkowe dziwne zachowanie - limit czasu sesji Owin i ASP jest inny. Widzę, że moje sesje Owin są dłużej żywe niż moje sesje ASP dzięki jakimś mechanizmom. Tak więc podczas logowania: 1.) Mam sesję uwierzytelniania opartą na plikach cookie 2.) Ustawiłem kilka zmiennych sesji
Moje zmienne sesji (2) „umierają”, zanim zmienna sesji pliku cookie owin wymusza ponowne zalogowanie, co powoduje nieoczekiwane zachowanie w całej mojej aplikacji. (Osoba jest zalogowana, ale tak naprawdę nie jest zalogowana)
Zaktualizuj 3B
Po pewnym czasie zobaczyłem na stronie kilka komentarzy, w których stwierdzono, że limit czasu uwierzytelniania „formularzy” i limit czasu sesji muszą się zgadzać. Myślę, że normalnie te dwa są zsynchronizowane, ale z jakiegoś powodu nie są zsynchronizowane.
Podsumowanie obejść
1) Zawsze najpierw utwórz sesję przed uwierzytelnieniem. Zasadniczo utwórz sesję podczas uruchamiania aplikacjiSession["Workaround"] = 0;
2) [Eksperymentalne], jeśli utrzymujesz pliki cookie, upewnij się, że limit czasu / długość OWIN jest dłuższy niż sessionTimeout w pliku web.config (w testowaniu)
źródło
Odpowiedzi:
Napotkałem ten sam problem i prześledziłem przyczynę implementacji hostingu OWIN ASP.NET. Powiedziałbym, że to błąd.
Jakieś tło
Moje ustalenia są oparte na tych wersjach zestawu:
OWIN używa własnej abstrakcji do pracy z odpowiedziowymi plikami cookie ( Microsoft.Owin.ResponseCookieCollection ). Ta implementacja bezpośrednio otacza kolekcję nagłówków odpowiedzi i odpowiednio aktualizuje nagłówek Set-Cookie . Host OWIN ASP.NET ( Microsoft.Owin.Host.SystemWeb ) po prostu opakowuje System.Web.HttpResponse i jego kolekcję nagłówków. Więc kiedy nowy plik cookie jest tworzony przez OWIN, nagłówek odpowiedzi Set-Cookie jest zmieniany bezpośrednio.
Ale ASP.NET używa również własnej abstrakcji do pracy z plikami cookie odpowiedzi. Jest to widoczne dla nas jako właściwość System.Web.HttpResponse.Cookies i implementowane przez zapieczętowaną klasę System.Web.HttpCookieCollection . Ta implementacja nie zawija bezpośrednio nagłówka odpowiedzi Set-Cookie, ale wykorzystuje pewne optymalizacje i kilka wewnętrznych powiadomień, aby zamanifestować swój zmieniony stan w obiekcie odpowiedzi.
Następnie występuje punkt późny w okresie istnienia żądania, w którym HttpCookieCollection zmieniony stan jest testowany ( System.Web.HttpResponse.GenerateResponseHeadersForCookies () ), a pliki cookie są serializowane do nagłówka Set-Cookie . Jeśli ta kolekcja jest w określonym stanie, cały nagłówek Set-Cookie jest najpierw czyszczony i odtwarzany z plików cookie przechowywanych w kolekcji.
Implementacja sesji ASP.NET wykorzystuje właściwość System.Web.HttpResponse.Cookies do przechowywania swojego pliku cookie ASP.NET_SessionId. Istnieje również pewna podstawowa optymalizacja w module stanu sesji ASP.NET ( System.Web.SessionState.SessionStateModule ) zaimplementowana za pomocą statycznej właściwości o nazwie s_sessionEverSet, co jest dość oczywiste. Jeśli kiedykolwiek zapiszesz coś do stanu sesji w swojej aplikacji, ten moduł wykona trochę więcej pracy dla każdego żądania.
Wróćmy do naszego problemu z logowaniem
Dzięki tym wszystkim elementom można wyjaśnić swoje scenariusze.
Przypadek 1 - Sesja nigdy nie została ustawiona
Właściwość System.Web.SessionState.SessionStateModule , s_sessionEverSet ma wartość false. Identyfikatory sesji nie są generowane przez moduł stanu sesji, a stan kolekcji System.Web.HttpResponse.Cookies nie jest wykrywany jako zmieniony . W takim przypadku ciasteczka OWIN są poprawnie wysyłane do przeglądarki i logowanie działa.
Przypadek 2 - sesja została użyta gdzieś w aplikacji, ale nie zanim użytkownik spróbuje się uwierzytelnić
Właściwość System.Web.SessionState.SessionStateModule , s_sessionEverSet ma wartość true. Identyfikatory sesji są generowane przez SessionStateModule , ASP.NET_SessionId jest dodawany do kolekcji System.Web.HttpResponse.Cookies, ale jest usuwany później w czasie życia żądania, ponieważ sesja użytkownika jest w rzeczywistości pusta. W tym przypadku stan kolekcji System.Web.HttpResponse.Cookies jest wykrywany jako zmieniony, a nagłówek Set-Cookie jest najpierw czyszczony, zanim pliki cookie zostaną serializowane do wartości nagłówka.
W tym przypadku pliki cookie odpowiedzi OWIN są „tracone”, a użytkownik nie jest uwierzytelniany i zostaje przekierowany z powrotem na stronę logowania.
Przypadek 3 - sesja jest używana przed próbą uwierzytelnienia przez użytkownika
Właściwość System.Web.SessionState.SessionStateModule , s_sessionEverSet ma wartość true. Identyfikatory sesji są generowane przez SessionStateModule , ASP.NET_SessionId jest dodawany do System.Web.HttpResponse.Cookies . Ze względu na wewnętrzną optymalizację w System.Web.HttpCookieCollection i System.Web.HttpResponse.GenerateResponseHeadersForCookies () nagłówek Set-Cookie NIE jest najpierw usuwany, ale tylko aktualizowany.
W tym przypadku zarówno pliki cookie uwierzytelniania OWIN, jak i pliki cookie ASP.NET_SessionId są wysyłane w odpowiedzi i logowanie działa.
Bardziej ogólny problem z plikami cookie
Jak widać problem jest bardziej ogólny i nie ogranicza się do sesji ASP.NET. Jeśli hostujesz OWIN za pośrednictwem Microsoft.Owin.Host.SystemWeb i Ty / coś bezpośrednio korzysta z kolekcji System.Web.HttpResponse.Cookies, jesteś zagrożony.
Na przykład to działa i oba pliki cookie są poprawnie wysyłane do przeglądarki ...
Ale tak się nie dzieje i OwinCookie jest „zagubione” ...
Oba przetestowano z VS2013, IISExpress i domyślnego szablonu projektu MVC.
źródło
Rozpoczynając od świetnej analizy autorstwa @TomasDolezal, przyjrzałem się zarówno źródłu Owin, jak i System.Web.
Problem polega na tym, że System.Web ma własne główne źródło informacji o plikach cookie, a nie jest to nagłówek Set-Cookie. Owin wie tylko o nagłówku Set-Cookie. Obejściem problemu jest upewnienie się, że wszystkie pliki cookie ustawione przez Owin są również ustawione w
HttpContext.Current.Response.Cookies
kolekcji.Stworzyłem małe oprogramowanie pośredniczące ( źródło , nuget ), które robi dokładnie to, które ma być umieszczone bezpośrednio nad rejestracją oprogramowania pośredniczącego plików cookie.
źródło
app.UseKentorCookieMiddlewareSaver();
w Startup.Auth.cs. Powinien również obsługiwać usuwanie plików cookie podczas wylogowywania.app.UseKentorOwinCookieSaver()
i być może zawarty w oryginalnej odpowiedzi, jak na stronie GitHub pakietu .Krótko mówiąc, menedżer plików cookie .NET wygra z menedżerem plików cookie OWIN i nadpisze pliki cookie ustawione w warstwie OWIN . Poprawka polega na użyciu klasy SystemWebCookieManager, dostarczonej jako rozwiązanie w projekcie Katana tutaj . Musisz użyć tej lub podobnej klasy, co zmusi OWIN do korzystania z menedżera plików cookie .NET, aby nie było niespójności :
Podczas uruchamiania aplikacji po prostu przypisz ją podczas tworzenia zależności OWIN:
Podano podobną odpowiedź, ale nie obejmuje ona całej bazy kodu wymaganej do rozwiązania problemu, więc widzę potrzebę dodania jej tutaj, ponieważ zewnętrzny link do projektu Katana może zostać uszkodzony i powinno to być w pełni opisane jako rozwiązanie również tutaj.
źródło
ASP.NET Webforms, OWIN, ADFS
Startup.ConfigureAuth
mamyapp.UseCookieAuthentication
iapp.UseWsFederationAuthentication
wreszcieapp.UseStageMarker
Zespół Katana odpowiedział na problem podniesiony przez Tomasa Dolezara i opublikował dokumentację dotyczącą obejść :
Zobacz implementację SystemWebCookieManager z dokumentacji (link powyżej)
Więcej informacji tutaj
Edytować
Poniżej czynności, które podjęliśmy, aby rozwiązać problem. Zarówno 1., jak i 2. rozwiązały problem również osobno, ale na wszelki wypadek zdecydowaliśmy się zastosować oba:
1. Użyj SystemWebCookieManager
2. Ustaw zmienną sesji:
(uwaga na marginesie: powyższa metoda Initialize jest logicznym miejscem dla poprawki, ponieważ base.Initialize udostępnia sesję. Jednak poprawkę można zastosować później, ponieważ w OpenId najpierw jest anonimowe żądanie, a następnie przekierowanie do dostawcy OpenId, a następnie z powrotem do aplikacji. Problemy wystąpiłyby po przekierowaniu z powrotem do aplikacji, podczas gdy poprawka ustawia zmienną sesji już podczas pierwszego anonimowego żądania, rozwiązując w ten sposób problem, zanim jeszcze nastąpi jakiekolwiek przekierowanie)
Edytuj 2
Kopiuj i wklej z projektu Katana 14.05.2016:
Dodaj:
...i to:
źródło
ControllerContext.HttpContext.Session.RemoveAll();
doExternalLogin()
działania, zanim zadzwoniszChallengeResult()
. Nie wiem, czy to najlepsze rozwiązanie, ale jest najprostsze.?.
(operator warunkowy zerowy) działa tylko w C # 6.Odpowiedzi zostały już dostarczone, ale w owinie 3.1.0 istnieje klasa SystemWebChunkingCookieManager, której można użyć.
https://github.com/aspnet/AspNetKatana/blob/dev/src/Microsoft.Owin.Host.SystemWeb/SystemWebChunkingCookieManager.cs
https://raw.githubusercontent.com/aspnet/AspNetKatana/c33569969e79afd9fb4ec2d6bdff877e376821b2/src/Microsoft.Owin.Host.SystemWeb/SystemWebChunkingCookieManager.cs
źródło
Jeśli sam ustawiasz pliki cookie w oprogramowaniu pośredniczącym OWIN, użycie
OnSendingHeaders
wydaje się rozwiązać problem.Na przykład użycie poniższego kodu
owinResponseCookie2
zostanie ustawione, mimo żeowinResponseCookie1
nie jest:źródło
Podobny problem napotkałem w przypadku programu Visual Studio 2017 i .net MVC 5.2.4 , aktualizacja Nuget Microsoft.Owin.Security.Google do najnowszej wersji, która obecnie jest 4.0.1, zadziałała! Mam nadzieję, że to komuś pomoże!
źródło
Najszybsze rozwiązanie kodu jednowierszowego:
Po prostu dodaj tę linię przed metodą CreateIdentity:
źródło
HttpContext.Current.Session["RunSession"] = "1";
? w Globa.asaxSession_Start
?Miałem ten sam objaw, że nagłówek Set-Cookie nie był wysyłany, ale żadna z tych odpowiedzi nie pomogła. Wszystko działało na moim komputerze lokalnym, ale po wdrożeniu do produkcji nagłówki set-cookie nigdy nie zostały ustawione.
Okazuje się, że było to połączenie używania niestandardowego
CookieAuthenticationMiddleware
z WebApi wraz z obsługą kompresji WebApiNa szczęście korzystałem z ELMAH w moim projekcie, co pozwoliło mi zalogować się do tego wyjątku:
Co mnie do tego doprowadziło problemu z GitHubem
Zasadniczo, jeśli masz dziwną konfigurację, taką jak moja, będziesz chciał wyłączyć kompresję dla kontrolerów / metod WebApi, które ustawiają pliki cookie, lub wypróbować
OwinServerCompressionHandler
.źródło