FormsAuthentication.SignOut () não efetua logoff do usuário

143

Bati minha cabeça contra isso um pouco demais. Como impeço que um usuário navegue nas páginas de um site depois de desconectado usando FormsAuthentication.SignOut? Eu esperava que isso acontecesse:

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

Mas isso não acontece. Se eu digitar um URL diretamente, ainda posso navegar para a página. Eu não uso a segurança de roll-your-own há algum tempo, então esqueço por que isso não funciona.

Jason
fonte
Esse código é bom como está ... clicar novamente no navegador não revisita a página no servidor, simplesmente recarrega a versão em cache local da página. Todas as soluções abaixo parecem ignorar esse fato e, na verdade, não fazem nada mais do que você está fazendo aqui. Resumindo ... não há resposta para essa pergunta que resolverá o usuário que está olhando para o cache até o momento. Não acredito que haja uma maneira de limpar o cache, digamos ... js, ou com uma instrução do servidor.
guerra
Esta resposta oferece algumas maneiras de verificar, especialmente se o site estiver com falha nos testes PEN: stackoverflow.com/questions/31565632/…
Tyler S. Loeper

Respostas:

211

Os usuários ainda podem navegar no seu site porque os cookies não são limpos quando você liga FormsAuthentication.SignOut()e são autenticados a cada nova solicitação. Na documentação da Microsoft diz que o cookie será limpo, mas não, bug? É exatamente o mesmo com Session.Abandon(), o cookie ainda está lá.

Você deve alterar seu código para isso:

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();

HttpCookieestá no System.Webespaço para nome. Referência do MSDN .

Igor Jerosimić
fonte
18
Isso funciona para mim. No entanto, é importante notar que se a propriedade de domínio foi definido no cookie FormsAuthentication quando efetuar o login, ele também terá de ser definido quando expirying o cookie quando você sair
Phil Hale
8
Também não se esqueça de cookie1.HttpOnly = true;
Dmitry Zaets
6
Parece-me uma solução melhor: Response.Cookies [FormsAuthentication.FormsCookieName] .Expires = DateTime.Now.AddDays (-1);
Randy H.
7
@RandyH. A substituição do cookie FormsAuthentication existente por um novo cookie vazio garante que, mesmo que o cliente diminua o relógio do sistema, ele ainda não conseguirá recuperar nenhum dado do usuário do cookie.
Tri Q Tran
9
Alguém pode combinar todos esses comentários na resposta?
David
22

Usando duas das postagens acima de x64igor e Phil Haselden resolveu isso:

1. x64igor deu o exemplo para fazer o Logout:

  • Primeiro, você precisa limpar o cookie de autenticação e o cookie de sessão , devolvendo os cookies vazios na resposta ao logout.

    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 deu o exemplo acima de como evitar o cache após o logout:

  • Você precisa invalidar o cache no lado do cliente por meio da resposta .

        // 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
fonte
1
Desperdiçado o dia inteiro no trabalho para resolver este problema. Uma vez logado, o botão logout começou a chamar uma ação incorreta no controlador (Login não Logout). Obrigado, isso resolveu o problema. Ambiente de desenvolvimento: ASP.NET 4.51 MVC 5.1
Ako
1
Boa resposta! Humilde sugestão: Use a forma de cookies de sessão de compensação x64igor utilizados: SessionStateSection sessionStateSection = (SessionStateSection)WebConfigurationManager.GetSection("system.web/sessionState"); HttpCookie sessionCookie = new HttpCookie(sessionStateSection.CookieName, "");. Em geral, o nome do cookie da sessão não é "ASP.NET_SessionId".
seebiscuit
20

Parece-me que você não tem sua seção de autorização web.config configurada corretamente. Veja abaixo um exemplo.

<authentication mode="Forms">
  <forms name="MyCookie" loginUrl="Login.aspx" protection="All" timeout="90" slidingExpiration="true"></forms>
</authentication>
<authorization>
  <deny users="?" />
</authorization>
jwalkerjr
fonte
Esta é uma solução muito mais simples, eu marcaria isso como uma resposta. Como eu tenho uma versão do código trabalhando em servidores diferentes em um, eu não precisava definir propriedades adicionais adicionadas aqui e em outras que eu fiz. Portanto, modificar o código não deve ser a solução correta, é melhor modificar a configuração.
Vladimir Bozic
Por padrão, o slideExpiration é definido como true ( msdn.microsoft.com/library/1d3t3c61(v=vs.100).aspx ). E isso finalmente resultará na invalidação do cookie após x minutos, conforme definido no tempo limite - e não quando o usuário for desconectado via SignOut (). Portanto, isso não resultará no comportamento desejado para desconectar um usuário usando o FormsAuthentication. Por favor corrija-me se eu estiver errado.
OlafW
12

A chave aqui é que você diz "Se eu digitar um URL diretamente ...".

Por padrão, sob autenticação de formulários, o navegador armazena em cache as páginas do usuário. Portanto, selecionando um URL diretamente no menu suspenso da caixa de endereços do navegador ou digitando-o, PODE obter a página no cache do navegador e nunca voltar ao servidor para verificar a autenticação / autorização. A solução para isso é evitar o cache do lado do cliente no evento Page_Load de cada página ou no OnLoad () da sua página base:

Response.Cache.SetCacheability(HttpCacheability.NoCache);

Você também pode ligar para:

Response.Cache.SetNoStore();
Phil Haselden
fonte
11

Eu já lutei com isso antes também.

Aqui está uma analogia para o que parece estar acontecendo ... Um novo visitante, Joe, chega ao site e faz login através da página de login usando o FormsAuthentication. O ASP.NET gera uma nova identidade para Joe e fornece a ele um cookie. Esse cookie é como a chave da casa e, desde que Joe retorne com essa chave, ele poderá abrir a fechadura. Cada visitante recebe uma nova chave e um novo bloqueio para usar.

Quando FormsAuthentication.SignOut()é chamado, o sistema diz a Joe para perder a chave. Normalmente, isso funciona, já que Joe não tem mais a chave, ele não pode entrar.

No entanto, se Joe sempre volta, e faz ter essa chave perdida, ele é deixar de volta!

Pelo que sei, não há como dizer ao ASP.NET para alterar a fechadura da porta!

A maneira de viver com isso é lembrar o nome de Joe em uma variável Session. Quando ele sai, abandono a sessão para não ter mais o nome dele. Mais tarde, para verificar se ele tem permissão para entrar, simplesmente comparo o Identity.Name com o que a sessão atual possui e, se não corresponderem, ele não é um visitante válido.

Em resumo, para um site, NÃO confie User.Identity.IsAuthenticatedsem também verificar suas variáveis ​​de sessão!

Glen Little
fonte
8
+ 1, acho que isso é chamado de 'ataque de repetição de cookies'. Há um artigo sobre as limitações do FormsAuthentication.SignOut: support.microsoft.com/kb/900111
Dmitry
3
Para quem quer seguir o link acima, está morto. Você pode tentar usar o WaybackMachine para obter uma cópia desta página aqui, mas ele IMEDIATAMENTE tenta redirecionar o usuário. web.archive.org/web/20171128133421/https://…
killa-byte
7

Depois de muita pesquisa, finalmente funcionou para mim. Espero que ajude.

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
fonte
Desenvolvo aplicativos web há anos em PHP. Então, eu sou novo no MVC ... Admito que adoro, mas quem teria pensado que algo tão simples como desconectar alguém seria tão difícil? Eu tentei todos os outros scripts nesta página abaixo da linha e este é o único que funcionou. Obrigado por publicar!
Anthony Griggs
6

Isso funciona para mim

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
fonte
3

O código que você postou parece remover corretamente o token de autenticação de formulários, portanto, é possível que as pastas / páginas em questão não estejam realmente protegidas.

Você confirmou que as páginas não podem ser acessadas antes que um login ocorra?

Você pode postar as configurações do web.config e o código de login que você está usando?

Abram Simon
fonte
3

Escrevi uma classe base para todas as minhas páginas e cheguei ao mesmo problema. Eu tinha um código como o seguinte e não funcionou. Ao rastrear, o controle passa da instrução RedirectToLoginPage () para a próxima linha sem ser redirecionada.

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

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

Eu descobri que existem duas soluções. Para modificar FormsAuthentication.RedirectToLoginPage (); ser estar

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

OU para modificar o web.config adicionando

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

No segundo caso, durante o rastreamento, o controle não alcançou a página solicitada. Ele foi redirecionado imediatamente para o URL de login antes de atingir o ponto de interrupção. Portanto, o método SignOut () não é o problema, é o método de redirecionamento.

Espero que ajude alguém

Saudações

Wahid Shalaly
fonte
2
Além disso, você poderia chamar Response.End () apenas depois de chamar FormsAuthentication.RedirectToLoginPage ()
murki
Eu acho que há um pouco de falta de comunicação por parte da MS. Você precisa bloquear as pessoas se quiser que elas retornem à página de login. Caso contrário, a estrutura permitirá o acesso com prazer. Então você tem que dizer a solução # 2 neste post.
Josh Robinson
3

Eu apenas tentei algumas das sugestões aqui e, enquanto eu era capaz de usar o botão Voltar do navegador, quando clicou em uma seleção de menu, o token [Autorizar] para esse [ActionResult] me enviou de volta à tela de login.

Aqui está o meu código de logout:

        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);
        }

Embora a função de retorno no navegador tenha me levado de volta e exibido o menu protegido (ainda estou trabalhando nisso), não pude fazer nada que estivesse protegido no aplicativo.

Espero que isto ajude

DonH
fonte
Obrigado. Esta é a solução que funcionou para mim (sem necessidade de <deny users="?" />no web.config)
Alexei
3

Eu tentei a maioria das respostas neste tópico, sem sorte. Terminou com isso:

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();
}

Encontre-o aqui: http://forums.asp.net/t/1306526.aspx/1

stoffen
fonte
3

Esta resposta é tecnicamente idêntica à do Khosro.Pakmanesh. Estou postando para esclarecer como a resposta dele difere de outras respostas neste segmento e, nesse caso, pode ser usada.

Em geral, para limpar uma sessão do usuário, fazer

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

efetivamente desconectará o usuário. No entanto , se na mesma solicitação você precisar verificar Request.isAuthenticated(como pode acontecer em um filtro de autorização, por exemplo), você verá que

Request.isAuthenticated == true

mesmo _depois que você fez HttpContext.Session.Abandon()e FormsAuthentication.SignOut().

A única coisa que funcionou foi fazer

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

Isso efetivamente define Request.isAuthenticated = false.

seebiscuit
fonte
2

Isso começou a acontecer comigo quando eu definir a autenticação> formas> propriedade Path no Web.config. A remoção corrigiu o problema e um simples FormsAuthentication.SignOut();removeu novamente o cookie.

BPM
fonte
1

Pode ser que você esteja efetuando login em um subdomínio (sub1.domínio.com) e tentando sair de outro subdomínio (www.domínio.com).

jorsh1
fonte
1

Eu apenas tive o mesmo problema, onde o SignOut () aparentemente não conseguiu remover corretamente o ticket. Mas apenas em um caso específico, em que alguma outra lógica causou um redirecionamento. Depois de remover esse segundo redirecionamento (substituí-lo por uma mensagem de erro), o problema desapareceu.

O problema deve ter sido o redirecionamento da página na hora errada, portanto, não acionando a autenticação.

Peder Skou
fonte
1

Estou tendo um problema semelhante agora e acredito que o problema no meu caso e o pôster original se devem ao redirecionamento. Por padrão, um Response.Redirect causa uma exceção que borbulha imediatamente até ser capturada e o redirecionamento é executado imediatamente. Suponho que isso esteja impedindo que a coleção de cookies modificada seja transmitida ao cliente. Se você modificar seu código para usar:

Response.Redirect("url", false);

Isso evita a exceção e parece permitir que o cookie seja enviado corretamente de volta ao cliente.

lostatredrock
fonte
1

Apenas tente enviar uma variável de sessão ao pressionar logon. E na página de boas-vindas, verifique primeiro se a sessão está vazia assim no carregamento da página ou no evento Init:

if(Session["UserID"] == null || Session["UserID"] == "")
{
    Response.Redirect("Login.aspx");
}
Devrishi
fonte
1

Para mim, a seguinte abordagem funciona. Acho que, se houver algum erro após a instrução "FormsAuthentication.SignOut ()", o SingOut não funcionará.

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

            return Redirect("~/");
        }
        return View();
     }
Aji
fonte
0

Você está testando / vendo esse comportamento usando o IE? É possível que o IE esteja servindo essas páginas do cache. É notoriamente difícil fazer o IE liberar seu cache e, portanto, em muitas ocasiões, mesmo após o logout, digitar o URL de uma das páginas "seguras" mostraria o conteúdo em cache de antes.

(Vi esse comportamento mesmo quando você faz logon como um usuário diferente, e o IE mostra a barra "Bem-vindo" na parte superior da sua página, com o nome de usuário do usuário antigo. Atualmente, geralmente uma atualização o atualiza, mas se persistir , ainda pode ser um problema de armazenamento em cache.)

Stobor
fonte
0

Fazer Session.abandon () e destruir o cookie funciona muito bem. Estou usando o mvc3 e parece que o problema ocorre se você acessar uma página protegida, sair e acessar o histórico do navegador. Não é grande coisa, mas ainda meio chato.

Tentar acessar os links no meu aplicativo da web funciona da maneira certa.

Configurá-lo para não fazer cache do navegador pode ser o caminho a percorrer.

James
fonte
0

Para o MVC, isso funciona para mim:

        public ActionResult LogOff()
        {
            FormsAuthentication.SignOut();
            return Redirect(FormsAuthentication.GetRedirectUrl(User.Identity.Name, true));
        }
anovo
fonte
0

Eu queria adicionar algumas informações para ajudar a entender o problema. A autenticação de formulários permite armazenar dados do usuário em um cookie ou na string de consulta do URL. O método suportado pelo site pode ser configurado no arquivo web.config.

De acordo com a Microsoft :

O método SignOut remove as informações do ticket de autenticação de formulários do cookie ou da URL se CookiesSupported for false .

Ao mesmo tempo, eles dizem :

Um dos valores HttpCookieMode que indica se o aplicativo está configurado para autenticação de formulários sem cookies. O padrão é UseDeviceProfile .

Por fim, em relação ao UseDeviceProfile, eles dizem :

Se a propriedade CookieMode estiver definida como UseDeviceProfile, a propriedade CookiesSupported retornará true se o Navegador da Solicitação atual suportar ambos os cookies e redirecionar com cookies ; caso contrário, a propriedade CookiesSupported retornará false.

Reunindo tudo isso, dependendo do navegador do usuário, a configuração padrão pode resultar em CookiesSupported sendo true , o que significa que o método SignOut não limpa o ticket do cookie. Isso parece contra-intuitivo e não sei por que funciona dessa maneira - eu esperaria que o SignOut realmente desconectasse o usuário em qualquer circunstância.

Uma maneira de fazer o SignOut funcionar por si só é alterar o modo de cookie para "UseCookies" (ou seja, são necessários cookies) no arquivo web.config:

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

De acordo com meus testes, isso faz com que o SignOut funcione por si só, ao custo do seu site, agora exigindo que os cookies funcionem corretamente.

RogerMKE
fonte
Eu acho que você está lendo isso errado. Em relação ao SignOut (), tenho certeza de que o que eles querem dizer é que será excluído do URL se CookiesSupported for falso, caso contrário, o cookie. Ou seja, eles deveriam ter escrito "O método SignOut remove as informações do ticket de autenticação de formulários do cookie ou, se CookiesSupported for falso, do URL".
Oskar Berggren
-1

Esteja ciente de que o WIF se recusa a informar o navegador para limpar os cookies se a mensagem wsignoutcleanup do STS não corresponder ao URL com o nome do aplicativo do IIS e quero dizer CASE SENSITIVE . O WIF responde com a verificação verde OK, mas não envia o comando para excluir os cookies do navegador.

Portanto, você precisa prestar atenção à distinção entre maiúsculas e minúsculas dos seus URLs.

Por exemplo, o ThinkTecture Identity Server salva os URLs dos RPs visitantes em um cookie, mas torna todos eles em letras minúsculas. O WIF receberá a mensagem wsignoutcleanup em letras minúsculas e a comparará com o nome do aplicativo no IIS. Se não corresponder, ele não excluirá cookies, mas informará OK ao navegador. Portanto, para este Identity Server, eu precisava escrever todas as URLs no web.config e todos os nomes de aplicativos no IIS em letras minúsculas, a fim de evitar esses problemas.

Além disso, não se esqueça de permitir cookies de terceiros no navegador se você tiver aplicativos fora do subdomínio do STS; caso contrário, o navegador não excluirá os cookies, mesmo que o WIF o informe.

Stefan
fonte
1
WIF? STS? Servidor de identidade do ThinkTecture? O que são todas essas coisas e como elas se relacionam com essa pergunta?
Oskar Berggren