SharePoint 4 Developers

Guia de referência adicional em desenvolvimento .NET / SharePoint

Limitando sessões a uma sessão por conta de usuário

Restrinja múltiplas sessões de usuários concorrentes. Applicavel a SharePoint/ASP.NET web sites. Limite a apenas uma única sessão de usuário por conta.

Oi pessoal,

Estou de volta, novamente em 2012. Hoje quero mostrar como restringir múltiplas sessões de usuários concorrentes.

Minha abordagem serve tanto para SharePoint quanto para aplicações ASP.NET.

Cenário

Em sites SharePoint/ASP.NET que utilizam Forms Authentication e Session State, quando logados, o ASP.NET garante sessões a usuários.

Se você não fez nenhuma alteração na solução, provavelmente o mesmo usuário pode se logar na aplicação simultaneamente de diferentes browsers/desktops.

Este comportamento traz alguns problemas de segurança que você pode evitar por limitar a sessões a um usuário por conta.

Solução

Quando o usuário se logar, mapeie o ID de sua sessão e usuário utilizando o Application State, e quando o mesmo fizer o logout remova a sessão do mapeamento. Então em cada requisição você precisa interceptá-la e validá-la utilizando um Módulo.

Diagrama

Para ficar mais claro, verifique o Diagram de Sequência para esta solução:

diagram
Figura 1 – Diagrama de Sequência

Neste diagram os seguintes usuários e objetos são descritos:

  • Browser 1 e 2 – Representam os diferentes browsers utilizados para visualizar páginas.
  • Page – Objeto requisitado pelo browser.
  • httpApplication – Application State cujo escopo é global, do nível da aplicação.
  • httpModule – Módulo que intercepta as requisições das páginas.

OBS: Em todos os casos apenas um usuário (User A) faz as requisições de páginas em browsers diferentes.

Aqui estão os passos que descrevem o diagrama acima:

1 – Primeira requisição (Browser 1)

Na primeira requisição o SharePoint/ASP.NET Application se inicia, carregando o Application State (httpApplication).

O httpModule (a ser desenvolvido) é carregado pelo httpApplication.

Então a inicialização do módulo (Init) cria uma variável para armazenar as sessões de usuário no Application State e adiciona um event handler ao evento PostAcquireRequestState para ser ativado em cada requisição.

2 – Usuário A se loga (Browser 1)

O usuário A se loga ao web site, e a sessão do usuário (SessionContext) é mapeada pela variável armazenada no httpApplication.

3 – Usuário A se loga (Browser 2)

O usuário A se loga ao web site, e a sessão do usuário (SessionContext) é mapeada pela variável armazenada no httpApplication, que sobrescreve a sessão do usuário anteriormente criada.

4 – Requisição à qualquer página (Browser 1)

O usuário A tenta obter uma página diferente, mas agora sua sessão não está mais disponível no httpApplication. O SessionID armazenado difere do SessionID atual, então o módulo força a sessão a ser abandonada, redirecionando o usuário para a página de Login.

5 – Usuário faz logout (Browser 2)

O usuário A faz o logout e a sua sessão (SessionContext) é removida da variável armazenada no httpApplication. Sua sessão é abandonada e é redirecionado à página de Login.

Caso alternativo

Se o usuário fechar o browser a variável permanece no httpApplication e a sessão permanece ativa até que se expire. Quando o usuário se loga novamente, a variável é sobrescrita (caso 3 acima).

Código

Agora que está mais claro o propósito da solução, vamos dar uma olhada no código.

Uma classe é criada para armazenar o Session Context (contexto da sessão) do usuário:

Code Snippet
  1. using System;
  2.  
  3. namespace Core
  4. {
  5.     public class SessionContext
  6.     {
  7.         public string UserName { get; set; }
  8.         public string SessionID { get; set; }
  9.     }
  10. }

 

A parte principal da solução é o httpModule que intercepta as requisições:

Code Snippet
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Web;
  4. using System.Web.Security;
  5.  
  6. namespace Core
  7. {
  8.     public class SessionManagerModule :IHttpModule
  9.     {
  10.         public SortedDictionary<string, SessionContext> ASPNETContext { get; set; }
  11.  
  12.         #region IHttpModule Members
  13.  
  14.         public void Init(HttpApplication context)
  15.         {
  16.             // Initializes the Application variable
  17.             if (context.Application["sessionSortedList"] == null)
  18.             {
  19.                 ASPNETContext = new System.Collections.Generic.SortedDictionary<string, SessionContext>();
  20.                 context.Application["sessionSortedList"] = ASPNETContext;
  21.             }
  22.  
  23.             context.PostAcquireRequestState += new EventHandler(context_PostAcquireRequestState);
  24.         }
  25.  
  26.         void context_PostAcquireRequestState(object sender, EventArgs e)
  27.         {
  28.             HttpApplication application = (HttpApplication)sender;
  29.  
  30.             // Get the Application Context variable
  31.             var ASPNETContext = (SortedDictionary<string, SessionContext>)application.Application["sessionSortedList"];
  32.             HttpContext context = application.Context;
  33.             string filePath = context.Request.FilePath;
  34.             string fileExtension = VirtualPathUtility.GetExtension(filePath);
  35.  
  36.             if (fileExtension.Equals(".aspx"))
  37.             {
  38.                 if (application.Context.Session != null)
  39.                 {
  40.                     // Get the User Name
  41.                     string userName = (application.Session != null) ? (string)application.Session["userName"] : string.Empty;
  42.                     userName = userName ?? string.Empty;
  43.  
  44.                     //Try to get the current session
  45.                     SessionContext currentSessionContext = null;
  46.                     ASPNETContext.TryGetValue(userName, out currentSessionContext);
  47.  
  48.                     if (currentSessionContext != null)
  49.                     {
  50.                         // Validates old sessions
  51.                         bool session = currentSessionContext.SessionID == application.Session.SessionID;
  52.  
  53.                         if (!session)
  54.                         {
  55.                             // Sing out
  56.                             FormsAuthentication.SignOut();
  57.  
  58.                             // Remove from Session
  59.                             application.Session.Clear();
  60.                             application.Session.Abandon();
  61.                             application.Context.Response.Cookies["ASP.NET_SessionId"].Value = "";
  62.  
  63.                             // Redirect
  64.                             FormsAuthentication.RedirectToLoginPage();
  65.                         }
  66.                     }
  67.                 }
  68.             }
  69.         }
  70.  
  71.         public void Dispose() { }
  72.  
  73.         #endregion
  74.     }
  75. }

 

Quando o usuário se logar, adicione o seguinte código ao evento LoggedIn do controle Login:

Code Snippet
  1. void Login1_LoggedIn(object sender, EventArgs e)
  2. {
  3.     if (HttpContext.Current.Session != null)
  4.     {
  5.         string sessionID = Session.SessionID;
  6.         string userName = Encoder.HtmlEncode(Login1.UserName);
  7.         DateTime dateStarted = DateTime.Now;
  8.  
  9.         Session["userName"] = userName;
  10.  
  11.         // Get the Application Context variable
  12.         var ASPNETContext = (SortedDictionary<string, SessionContext>)Application["sessionSortedList"];
  13.  
  14.         // Create a new SessionContext variable
  15.         var sContext = new SessionContext() { SessionID = sessionID, UserName = userName };
  16.  
  17.         // Refresh the object to the Application
  18.         if (ASPNETContext != null)
  19.         {
  20.             ASPNETContext[userName] = sContext;
  21.             Application["sessionSortedList"] = ASPNETContext;
  22.         }
  23.     }
  24. }

 

Quando o usuário fizer o logout, adicione o seguinte código ao evento LoggingOut do controle LoginStatus:

Code Snippet
  1. void LoginStats_LoggingOut(object sender, LoginCancelEventArgs e)
  2. {
  3.     string userName = (string)Session["userName"];
  4.     userName = userName ?? string.Empty;
  5.  
  6.     // Get the Application Context variable
  7.     var ASPNETContext = (SortedDictionary<string, SessionContext>)Application["sessionSortedList"];
  8.  
  9.     //Try to get the current list
  10.     SessionContext currentSessionContext = null;
  11.     if (ASPNETContext != null)
  12.     {
  13.         ASPNETContext.TryGetValue(userName, out currentSessionContext);
  14.  
  15.         // Refresh the object to the Application
  16.         if (currentSessionContext != null)
  17.         {
  18.             ASPNETContext.Remove(userName);
  19.             Application["sessionSortedList"] = ASPNETContext;
  20.         }
  21.     }
  22.  
  23.     FormsAuthentication.SignOut();
  24.     Session.Clear();
  25.     Session.Abandon();
  26.     HttpContext.Current.Response.Cookies["ASP.NET_SessionId"].Value = "";
  27. }

 

Outra tarefa necessária para o módulo funcionar é sua adição ao web.config.

Se você utilizar IIS7 em modo clássico (classic mode) ou utilizar versões anteriores do IIS:

Code Snippet
  1. <system.web>
  2.   <httpModules>
  3.     <!-- any other modules above -->
  4.     <add name="SessionManagerModule" type="Core.SessionManagerModule, Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=TYPEYOURKEYHERE" />
  5.   </httpModules>
  6. </system.web>

 

Se você utilizar IIS7 em modo integrado (integrated mode):

Code Snippet
  1. <system.web>
  2.   <httpModules>
  3.     <!-- any other modules above -->
  4.     <add name="SessionManagerModule" type="Core.SessionManagerModule, Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=TYPEYOURKEYHERE" />
  5.   </httpModules>
  6. </system.web>
  7. <system.webServer>
  8.   <modules runAllManagedModulesForAllRequests="true">
  9.     <!-- any other modules above -->
  10.     <add name="SessionManagerModule" type="Core.SessionManagerModule, Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=TYPEYOURKEYHERE" />
  11.   </modules>
  12. </system.webServer>

 

OBS: Como boa prática, no SharePoint você precisa criar uma feature para aplicar quaisquer mudanças ao web.config.

Espero que esta solução te ajude.

Dê uma olhada nas referências caso você queira mais detalhes.

Referências:
http://msdn.microsoft.com/en-us/library/ms178329.aspx
http://msdn.microsoft.com/en-us/library/system.web.httpapplication.aspx

[]’s,

Marcel Medina

Clique aqui para ler o mesmo conteúdo em Inglês.

blog comments powered by Disqus