FormsAuthentication.SignOut () nie wylogowuje użytkownika

143

Roztrzaskałem się o to trochę za długo. Jak uniemożliwić użytkownikowi przeglądanie stron witryny po wylogowaniu za pomocą FormsAuthentication.SignOut? Spodziewałbym się, że to zrobi:

FormsAuthentication.SignOut();
Session.Abandon();
FormsAuthentication.RedirectToLoginPage();

Ale tak nie jest. Jeśli wpiszę adres URL bezpośrednio, mogę nadal przeglądać tę stronę. Od jakiegoś czasu nie używałem własnych zabezpieczeń, więc zapominam, dlaczego to nie działa.

Jason
źródło
Ten kod jest w porządku, tak jak ... kliknięcie z powrotem w przeglądarce nie powoduje ponownego odwiedzenia strony na serwerze, po prostu przeładowuje lokalną wersję strony z pamięci podręcznej. Wszystkie poniższe rozwiązania wydają się ignorować ten fakt i nie robią nic więcej, niż robisz tutaj. Krótko mówiąc ... nie ma odpowiedzi na to pytanie, która rozwiązałaby problem użytkownika przeglądającego pamięć podręczną, ponieważ do tej pory nie wierzę, że istnieje sposób na wyczyszczenie pamięci podręcznej w powiedzmy ... js lub za pomocą instrukcji po stronie serwera.
Wojna
Ta odpowiedź oferuje kilka sposobów sprawdzenia, zwłaszcza jeśli Twoja witryna nie przechodzi testów PEN: stackoverflow.com/questions/31565632/ ...
Tyler S. Loeper

Odpowiedzi:

211

Użytkownicy mogą nadal przeglądać Twoją witrynę, ponieważ pliki cookie nie są usuwane podczas połączenia FormsAuthentication.SignOut()i są uwierzytelniane przy każdym nowym żądaniu. W dokumentacji MS jest napisane, że ciasteczko zostanie wyczyszczone, ale tak nie jest, błąd? Dokładnie tak samo jest z Session.Abandon()ciasteczkiem.

Powinieneś zmienić swój kod na ten:

FormsAuthentication.SignOut();
Session.Abandon();

// clear authentication cookie
HttpCookie cookie1 = new HttpCookie(FormsAuthentication.FormsCookieName, "");
cookie1.Expires = DateTime.Now.AddYears(-1);
Response.Cookies.Add(cookie1);

// clear session cookie (not necessary for your current problem but i would recommend you do it anyway)
SessionStateSection sessionStateSection = (SessionStateSection)WebConfigurationManager.GetSection("system.web/sessionState");
HttpCookie cookie2 = new HttpCookie(sessionStateSection.CookieName, "");
cookie2.Expires = DateTime.Now.AddYears(-1);
Response.Cookies.Add(cookie2);

FormsAuthentication.RedirectToLoginPage();

HttpCookieznajduje się w System.Webprzestrzeni nazw. Dokumentacja MSDN .

Igor Jerosimić
źródło
18
To działa dla mnie. Warto jednak zauważyć, że jeśli właściwość domeny została ustawiona w pliku cookie FormsAuthentication podczas logowania, należy ją również ustawić podczas wygaśnięcia pliku cookie podczas wylogowywania
Phil Hale
8
Nie zapomnij również o cookie1.HttpOnly = true;
Dmitry Zaets
6
Wydaje mi się, że to lepsze rozwiązanie: Response.Cookies [FormsAuthentication.FormsCookieName] .Expires = DateTime.Now.AddDays (-1);
Randy H.
7
@RandyH. Zastąpienie istniejącego pliku cookie FormsAuthentication nowym pustym plikiem cookie gwarantuje, że nawet jeśli klient cofnie zegar systemowy, nadal nie będzie mógł pobrać żadnych danych użytkownika z pliku cookie.
Tri Q Tran
9
Czy ktoś może połączyć wszystkie te komentarze w odpowiedzi?
David
22

Wykorzystanie dwóch z powyższych postów autorstwa x64igor i Phila Haseldena rozwiązało ten problem:

1. x64igor podał przykład wykonania wylogowania:

  • Najpierw musisz wyczyścić plik cookie uwierzytelniania i plik cookie sesji , przekazując z powrotem puste pliki cookie w odpowiedzi na wylogowanie.

    public ActionResult LogOff()
    {
        FormsAuthentication.SignOut();
        Session.Clear();  // This may not be needed -- but can't hurt
        Session.Abandon();
    
        // Clear authentication cookie
        HttpCookie rFormsCookie = new HttpCookie( FormsAuthentication.FormsCookieName, "" );
        rFormsCookie.Expires = DateTime.Now.AddYears( -1 );
        Response.Cookies.Add( rFormsCookie );
    
        // Clear session cookie 
        HttpCookie rSessionCookie = new HttpCookie( "ASP.NET_SessionId", "" );
        rSessionCookie.Expires = DateTime.Now.AddYears( -1 );
        Response.Cookies.Add( rSessionCookie );

2. Phil Haselden podał powyższy przykład, jak zapobiegać buforowaniu po wylogowaniu:

  • Musisz unieważnić pamięć podręczną po stronie klienta za pośrednictwem odpowiedzi .

        // Invalidate the Cache on the Client Side
        Response.Cache.SetCacheability( HttpCacheability.NoCache );
        Response.Cache.SetNoStore();
    
        // Redirect to the Home Page (that should be intercepted and redirected to the Login Page first)
        return RedirectToAction( "Index", "Home" ); 
    }
justdan23
źródło
1
Zmarnowany cały dzień w pracy, aby rozwiązać ten problem. Po zalogowaniu się przycisk wylogowania zaczął wywoływać niewłaściwe działanie w kontrolerze (Zaloguj się, a nie wyloguj). Dziękuję, to rozwiązało problem. Środowisko programistyczne: ASP.NET 4.51 MVC 5.1
Ako
1
Dobra odpowiedź! Humble sugestia: Użyj forma dla rozliczających sesyjnych ciasteczek x64igor stosowane: SessionStateSection sessionStateSection = (SessionStateSection)WebConfigurationManager.GetSection("system.web/sessionState"); HttpCookie sessionCookie = new HttpCookie(sessionStateSection.CookieName, "");. Ogólnie nazwa pliku cookie sesji to nie "ASP.NET_SessionId".
seebiscuit
20

Wydaje mi się, że nie masz poprawnie skonfigurowanej sekcji autoryzacji web.config w programie. Poniżej przykład.

<authentication mode="Forms">
  <forms name="MyCookie" loginUrl="Login.aspx" protection="All" timeout="90" slidingExpiration="true"></forms>
</authentication>
<authorization>
  <deny users="?" />
</authorization>
jwalkerjr
źródło
To znacznie prostsze rozwiązanie, oznaczyłbym to jako odpowiedź. Ponieważ mam jedną wersję kodu działającą na różnych serwerach na jednym, nie musiałem ustawiać dodatkowych właściwości, które dodałeś tutaj, a na innych zrobiłem. Więc modyfikowanie kodu nie powinno być poprawnym rozwiązaniem, modyfikacja konfiguracji jest lepsza.
Vladimir Bozic
Domyślnie slideExpiration ma wartość true ( msdn.microsoft.com/library/1d3t3c61(v=vs.100).aspx ). I to ostatecznie spowoduje, że plik cookie stanie się nieważny po x minutach, zgodnie z limitem czasu - a nie, gdy użytkownik zostanie wylogowany za pomocą SignOut (). Więc nie spowoduje to pożądanego zachowania, aby wylogować użytkownika przy użyciu FormsAuthentication. Proszę, popraw mnie jeśli się mylę.
OlafW,
12

Najważniejsze jest to, że mówisz „Jeśli wpiszę adres URL bezpośrednio ...”.

Domyślnie w ramach uwierzytelniania formularzy przeglądarka buforuje strony dla użytkownika. Tak więc, wybierając adres URL bezpośrednio z listy rozwijanej w polu adresu przeglądarki lub wpisując go, MOŻE uzyskać stronę z pamięci podręcznej przeglądarki i nigdy nie wracać do serwera w celu sprawdzenia uwierzytelnienia / autoryzacji. Rozwiązaniem tego problemu jest zapobieganie buforowaniu po stronie klienta w zdarzeniu Page_Load każdej strony lub w funkcji OnLoad () strony bazowej:

Response.Cache.SetCacheability(HttpCacheability.NoCache);

Możesz także zadzwonić:

Response.Cache.SetNoStore();
Phil Haselden
źródło
11

Już wcześniej z tym walczyłem.

Oto analogia do tego, co się wydaje ... Nowy użytkownik, Joe, przychodzi do witryny i loguje się za pośrednictwem strony logowania przy użyciu uwierzytelniania za pomocą formularzy. ASP.NET generuje nową tożsamość dla Joe i przekazuje mu plik cookie. To ciasteczko jest jak klucz do domu i dopóki Joe wróci z tym kluczem, może otworzyć zamek. Każdy gość otrzymuje nowy klucz i nowy zamek do użycia.

Kiedy FormsAuthentication.SignOut() wywołaniu system każe Joe zgubić klucz. Zwykle to działa, ponieważ Joe nie ma już klucza, nie może wejść.

Jednak jeśli Joe kiedykolwiek wróci i to zrobi ma tego utraconego klucza, jest on niech widok!

Z tego, co wiem, nie ma sposobu, aby powiedzieć ASP.NET, aby zmienił zamek w drzwiach!

Sposób, w jaki mogę z tym żyć, polega na zapamiętywaniu imienia Joe w zmiennej sesji. Kiedy się wylogowuje, opuszczam sesję, więc nie mam już jego imienia. Później, aby sprawdzić, czy może wejść, po prostu porównuję jego Identity.Nazwa z tym, co ma bieżąca sesja, a jeśli się nie zgadzają, nie jest on prawidłowym gościem.

Krótko mówiąc, w przypadku witryny internetowej NIE polegaj na niej User.Identity.IsAuthenticatedbez sprawdzania zmiennych sesji!

Glen Little
źródło
8
+ 1, myślę, że nazywa się to „atakiem powtórki plików cookie”. Jest artykuł na temat ograniczeń FormsAuthentication.SignOut: support.microsoft.com/kb/900111
Dmitry,
3
Dla każdego, kto chce kliknąć powyższy link, jest martwy. Możesz spróbować użyć WaybackMachine, aby uzyskać kopię tej strony tutaj, ale NATYCHMIAST próbuje przekierować użytkownika. web.archive.org/web/20171128133421/https://…
killa-byte
7

Po wielu poszukiwaniach w końcu to zadziałało. Mam nadzieję, że to pomoże.

public ActionResult LogOff()
{
    AuthenticationManager.SignOut();
    HttpContext.User = new GenericPrincipal(new GenericIdentity(string.Empty), null);
    return RedirectToAction("Index", "Home");
}

<li class="page-scroll">@Html.ActionLink("Log off", "LogOff", "Account")</li>
Khosro.Pakmanesh
źródło
Od lat tworzę aplikacje internetowe w PHP. Więc jestem nowy w MVC ... Przyznaję, że to uwielbiam, ALE kto by pomyślał, że coś tak prostego, jak wylogowanie kogoś byłoby tak trudne? Wypróbowałem wszystkie inne skrypty na tej stronie i jest to jedyny, który działał. Dzięki za wiadomość!
Anthony Griggs,
6

To działa dla mnie

public virtual ActionResult LogOff()
    {
        FormsAuthentication.SignOut();
        foreach (var cookie in Request.Cookies.AllKeys)
        {
            Request.Cookies.Remove(cookie);
        }
        foreach (var cookie in Response.Cookies.AllKeys)
        {
            Response.Cookies.Remove(cookie);
        }
        return RedirectToAction(MVC.Home.Index());
    }
Korayem
źródło
3

Wydaje się, że opublikowany kod powinien poprawnie usuwać token uwierzytelniania formularzy, więc możliwe jest, że dane foldery / strony nie są w rzeczywistości chronione.

Czy potwierdziłeś, że strony nie są dostępne przed zalogowaniem?

Czy możesz opublikować ustawienia web.config i kod logowania, którego używasz?

Abram Simon
źródło
3

Pisałem klasę bazową dla wszystkich moich stron i doszedłem do tego samego problemu. Miałem kod podobny do następującego i nie działał. Poprzez śledzenie kontrola przechodzi z instrukcji RedirectToLoginPage () do następnego wiersza bez przekierowywania.

if (_requiresAuthentication)
{
    if (!User.Identity.IsAuthenticated)
        FormsAuthentication.RedirectToLoginPage();

    // check authorization for restricted pages only
    if (_isRestrictedPage) AuthorizePageAndButtons();
}

Dowiedziałem się, że są dwa rozwiązania. Albo zmodyfikować FormsAuthentication.RedirectToLoginPage (); być

if (!User.Identity.IsAuthenticated)
    Response.Redirect(FormsAuthentication.LoginUrl);

LUB zmodyfikować plik web.config przez dodanie

<authorization>
  <deny users="?" />
</authorization>

W drugim przypadku podczas śledzenia formant nie dotarł do żądanej strony. Został natychmiast przekierowany do adresu URL logowania przed osiągnięciem punktu przerwania. Dlatego metoda SignOut () nie jest problemem, jest to metoda przekierowania.

Mam nadzieję, że to może komuś pomóc

pozdrowienia

Wahid Shalaly
źródło
2
Możesz również wywołać Response.End () zaraz po wywołaniu FormsAuthentication.RedirectToLoginPage ()
murki
Myślę, że ze strony stwardnienia rozsianego jest trochę nieporozumienia. Musisz zablokować ludzi, jeśli chcesz, aby wrócili na stronę logowania. W przeciwnym razie framework z radością pozwoli ci na dostęp. Więc musisz powiedzieć rozwiązanie nr 2 w tym poście.
Josh Robinson
3

Właśnie wypróbowałem niektóre z sugestii tutaj i chociaż mogłem użyć przycisku wstecz w przeglądarce, kiedy kliknąłem opcję menu, token [Autoryzuj] dla tego [ActionResult] odesłał mnie z powrotem do ekranu logowania.

Oto mój kod wylogowania:

        FormsAuthentication.SignOut();
        Response.Cookies.Remove(FormsAuthentication.FormsCookieName);
        Response.Cache.SetExpires(DateTime.Now.AddSeconds(-1));
        HttpCookie cookie = HttpContext.Request.Cookies[FormsAuthentication.FormsCookieName];
        if (cookie != null)
        {
            cookie.Expires = DateTime.Now.AddDays(-1);
            Response.Cookies.Add(cookie);
        }

Chociaż funkcja wstecz w przeglądarce cofnęła mnie i wyświetliła zabezpieczone menu (nadal nad tym pracuję), nie mogłem zrobić nic, co było zabezpieczone w aplikacji.

Mam nadzieję że to pomoże

DonH
źródło
Dzięki. To jest rozwiązanie, które zadziałało dla mnie (nie ma potrzeby <deny users="?" />w web.config)
Alexei
3

Próbowałem większości odpowiedzi w tym wątku, bez powodzenia. Skończyło się na tym:

protected void btnLogout_Click(object sender, EventArgs e)
{
    FormsAuthentication.Initialize();
    var fat = new FormsAuthenticationTicket(1, "", DateTime.Now, DateTime.Now.AddMinutes(-30), false, string.Empty, FormsAuthentication.FormsCookiePath);
    Response.Cookies.Add(new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(fat)));
    FormsAuthentication.RedirectToLoginPage();
}

Znalazłem go tutaj: http://forums.asp.net/t/1306526.aspx/1

stoffen
źródło
3

Ta odpowiedź jest technicznie identyczna z Khosro.Pakmanesh. Publikuję go, aby wyjaśnić, czym jego odpowiedź różni się od innych odpowiedzi w tym wątku iw jakim przypadku można jej użyć.

Ogólnie, aby wyczyścić sesję użytkownika, robi

HttpContext.Session.Abandon();
FormsAuthentication.SignOut();

skutecznie wyloguje użytkownika. Jeśli jednak w tym samym żądaniu musisz sprawdzić Request.isAuthenticated(co często może się zdarzyć na przykład w filtrze autoryzacji), zobaczysz, że

Request.isAuthenticated == true

nawet po tym, jak to zrobiłeś HttpContext.Session.Abandon()i FormsAuthentication.SignOut().

Jedyne, co działało, to robienie

AuthenticationManager.SignOut();
HttpContext.User = new GenericPrincipal(new GenericIdentity(string.Empty), null);

To skutecznie ustawia Request.isAuthenticated = false.

seebiscuit
źródło
2

Zaczęło się to dziać po ustawieniu właściwości uwierzytelniania> formularzy> ścieżki w Web.config. Usunięcie tego rozwiązało problem i proste FormsAuthentication.SignOut();ponowne usunięcie pliku cookie.

BPM
źródło
1

Może się zdarzyć, że logujesz się z jednej subdomeny (sub1.domain.com), a następnie próbujesz wylogować się z innej subdomeny (www.domain.com).

jorsh1
źródło
1

Po prostu miałem ten sam problem, gdzie SignOut () najwyraźniej nie zdołał poprawnie usunąć biletu. Ale tylko w konkretnym przypadku, gdy inna logika spowodowała przekierowanie. Po usunięciu tego drugiego przekierowania (zastąpieniu go komunikatem o błędzie) problem zniknął.

Problem musiał polegać na tym, że strona została przekierowana w złym czasie, a tym samym nie wyzwalała uwierzytelniania.

Peder Skou
źródło
1

Mam teraz podobny problem i uważam, że problem w moim przypadku, jak również w przypadku oryginalnego plakatu, wynika z przekierowania. Domyślnie Response.Redirect powoduje wyjątek, który natychmiast pojawia się, dopóki nie zostanie przechwycony, a przekierowanie zostanie natychmiast wykonane. Domyślam się, że zapobiega to przekazaniu zmodyfikowanej kolekcji plików cookie do klienta. Jeśli zmodyfikujesz swój kod, aby użyć:

Response.Redirect("url", false);

Zapobiega to wyjątkowi i wydaje się umożliwiać prawidłowe przesłanie pliku cookie z powrotem do klienta.

lostatredrock
źródło
1

Po prostu spróbuj wysłać zmienną sesji po naciśnięciu przycisku logowania. Na stronie powitalnej najpierw sprawdź, czy ta sesja jest pusta w ten sposób podczas ładowania strony lub zdarzenia inicjującego:

if(Session["UserID"] == null || Session["UserID"] == "")
{
    Response.Redirect("Login.aspx");
}
Devrishi
źródło
1

U mnie działa następujące podejście. Myślę, że jeśli po instrukcji „FormsAuthentication.SignOut ()” występuje błąd, SingOut nie działa.

public ActionResult SignOut()
    {
        if (Request.IsAuthenticated)
        {
            FormsAuthentication.SignOut();

            return Redirect("~/");
        }
        return View();
     }
Aji
źródło
0

Czy testujesz / widzisz to zachowanie przy użyciu IE? Możliwe, że IE udostępnia te strony z pamięci podręcznej. Niezwykle trudno jest zmusić IE do opróżnienia pamięci podręcznej, więc w wielu przypadkach, nawet po wylogowaniu się, wpisanie adresu URL jednej z „zabezpieczonych” stron spowoduje wyświetlenie zawartości pamięci podręcznej sprzed wcześniejszej.

(Widziałem to zachowanie nawet wtedy, gdy logujesz się jako inny użytkownik, a IE wyświetla pasek „Witamy” u góry strony, ze starą nazwą użytkownika. Obecnie zwykle przeładowanie go zaktualizuje, ale jeśli jest trwały , nadal może to być problem z pamięcią podręczną).

Stobor
źródło
0

Wykonanie Session.abandon () i zniszczenie ciasteczka działa całkiem nieźle. Używam mvc3 i wygląda na to, że problem występuje, gdy przechodzisz do chronionej strony, wylogowujesz się i przeglądasz historię przeglądarki. Nic wielkiego, ale wciąż trochę irytujące.

Jednak próba przeglądania linków w mojej aplikacji internetowej działa prawidłowo.

Ustawienie go tak, aby nie korzystało z pamięci podręcznej przeglądarki, może być dobrym rozwiązaniem.

James
źródło
0

W przypadku MVC działa to dla mnie:

        public ActionResult LogOff()
        {
            FormsAuthentication.SignOut();
            return Redirect(FormsAuthentication.GetRedirectUrl(User.Identity.Name, true));
        }
anovo
źródło
0

Chciałem dodać informacje, które pomogą zrozumieć problem. Uwierzytelnianie za pomocą formularzy umożliwia przechowywanie danych użytkownika w pliku cookie lub w ciągu zapytania adresu URL. Metodę obsługiwaną przez witrynę można skonfigurować w pliku web.config.

Według Microsoft :

Metoda SignOut usuwa informacje o biletach uwierzytelniania formularzy z pliku cookie lub adresu URL, jeśli wartość CookiesSupported ma wartość false .

Jednocześnie mówią :

Jedna z wartości HttpCookieMode, która wskazuje, czy aplikacja jest skonfigurowana do uwierzytelniania formularzy bez plików cookie. Wartość domyślna to UseDeviceProfile .

Na koniec, jeśli chodzi o UseDeviceProfile, mówią :

Jeśli właściwość CookieMode jest ustawiona na UseDeviceProfile, właściwość CookiesSupported zwróci wartość true, jeśli przeglądarka dla bieżącego żądania obsługuje zarówno pliki cookie, jak i przekierowanie z plikami cookie ; w przeciwnym razie właściwość CookiesSupported zwróci wartość false.

Składając to wszystko razem, w zależności od przeglądarki użytkownika, domyślna konfiguracja może spowodować, że pliki cookieSupported będą miały wartość true , co oznacza, że ​​metoda SignOut nie usuwa biletu z pliku cookie. Wydaje się to sprzeczne z intuicją i nie wiem, dlaczego działa w ten sposób - spodziewałbym się, że SignOut faktycznie wyloguje użytkownika w każdych okolicznościach.

Jednym ze sposobów, aby SignOut działał sam, jest zmiana trybu plików cookie na „UseCookies” (tzn. Pliki cookie są wymagane) w pliku web.config:

<authentication mode="Forms">
  <forms loginUrl="~/Account/SignIn" cookieless="UseCookies"/>
</authentication>

Zgodnie z moimi testami, wykonanie tego powoduje, że SignOut działa samoczynnie, kosztem Twojej witryny, która wymaga teraz plików cookie do prawidłowego działania.

RogerMKE
źródło
Myślę, że źle to czytasz. Jeśli chodzi o SignOut (), jestem prawie pewien, że mają na myśli to, że zostanie usunięty z adresu URL, jeśli CookiesSupported jest fałszywe, w przeciwnym razie z pliku cookie. To znaczy powinni byli napisać „Metoda SignOut usuwa informacje o biletach uwierzytelniania formularzy z pliku cookie lub, jeśli plik cookieSupported ma wartość false, z adresu URL”.
Oskar Berggren
-1

Należy pamiętać, że WIF odmawia nakazania przeglądarce wyczyszczenia plików cookie, jeśli komunikat wsignoutcleanup z usługi STS nie pasuje do adresu URL z nazwą aplikacji z IIS, a mam na myśli WRAŻLIWE NA WIELKOŚĆ LITER . WIF odpowiada zielonym czekiem OK, ale nie znacznikiem wyśle ​​polecenia usunięcia plików cookie do przeglądarki.

Musisz więc zwrócić uwagę na rozróżnianie wielkości liter w adresach URL.

Na przykład ThinkTecture Identity Server zapisuje adresy URL odwiedzających RP w jednym pliku cookie, ale wszystkie są pisane małymi literami. WIF otrzyma komunikat wsignoutcleanup zapisany małymi literami i porówna go z nazwą aplikacji w usługach IIS. Jeśli nie pasuje, nie usuwa żadnych plików cookie, ale zgłosi OK do przeglądarki. Tak więc w przypadku tego serwera tożsamości musiałem zapisać wszystkie adresy URL w pliku web.config i wszystkie nazwy aplikacji w usługach IIS małymi literami, aby uniknąć takich problemów.

Nie zapomnij również zezwolić na pliki cookie stron trzecich w przeglądarce, jeśli masz aplikacje poza subdomeną STS, w przeciwnym razie przeglądarka nie usunie plików cookie, nawet jeśli WIF mu to powie.

Stefan
źródło
1
WIF? STS? Serwer tożsamości ThinkTecture? Czym są te wszystkie rzeczy i jak odnoszą się do tego pytania?
Oskar Berggren