E cá vamos nós de novo. A demanda desta semana (curta, diga-se de passagem) foi impedir que fosse possível um usuário efetuar login quando ele "já" estivesse logado. Por exemplo, se um usuário efetuar login no navegador A, que não fosse possível efetuar o login (com o mesmo usuário e senha) no navegador B da mesma máquina. Ou ainda, se um usuário fizer login numa máquina (IP) A, que não fosse possível efetuar login (com o mesmo usuário e senha) numa máquina (IP) B.
No post anterior, vimos como foi possível implementar o Single Sign On (SSO) numa aplicação web java usando o JaSig CAS e o Spring Security.
No post anterior, vimos como foi possível implementar o Single Sign On (SSO) numa aplicação web java usando o JaSig CAS e o Spring Security.
Configurando
Lendo a sessão Concurrent Session Control da documentação do Spring Security, iremos ver que, antes de qualquer coisa, precisamos configurar um novo listener no web.xml
<listener> <listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class> </listener>
O uso deste listener faz com que um ApplicationEvent seja publicado no contexto do spring sempre que uma sessão de usuário inicie ou termine.
Após a configuração do listener, também é necessário criar um ConcurrencySessionFilter e adicioná-lo à cadeia de filtros de segurança do spring.
<http entry-point-ref="casAuthenticationEntryPoint" use-expressions="true" > <intercept-url pattern="/javax.faces.resource/**" filters="none" /> <intercept-url pattern="/resources/**" filters="none" /> <intercept-url pattern="/**" access="hasRole('ROLE_USER')" /> <intercept-url pattern="/secure/**" access="hasRole('ROLE_ADMIN')" requires-channel="https" /> <logout logout-success-url="${logout.success.url}" /> <custom-filter ref="casAuthenticationFilter" after="CAS_FILTER"/> <custom-filter ref="concurrencyFilter" position="CONCURRENT_SESSION_FILTER" /> <session-management session-authentication-strategy-ref="sas" /> </http>
Criaremos um novo ConcurrentSessionFilter, referenciando-o por concurrencyFilter. Adicionamos este novo filtro na cadeia de fitlros do spring através da tag custom-filter. Na tag session-management, definimos qual o bean responsável pelo controle customizado de sessão. É importante que criemos um novo bean, ao invés do bean default (criado quando utilizamos a tag concurrency-control dentro da tag session-management), porque precisaremos integrá-lo à configuração do SSO.
Configuramos no CasAuthenticationFilter a propriedade sessionAuthenticationStrategy, com o mesmo bean que utilizamos para configurar a tag session-management:
<beans:bean id="casAuthenticationFilter" class="org.springframework.security.cas.web.CasAuthenticationFilter"> ... <beans:property name="sessionAuthenticationStrategy" ref="sas" /> ... </beans:bean>
Agora, criaremos os novos beans necessários para o controle de sessões concorrentes.
<beans:bean id="concurrencyFilter" class="org.springframework.security.web.session.ConcurrentSessionFilter"> <beans:property name="sessionRegistry" ref="sessionRegistry" /> <beans:property name="expiredUrl" value="/sessionExpired.jsf" /> </beans:bean> <beans:bean id="sas" class="org.springframework.security.web.authentication.session.ConcurrentSessionControlStrategy"> <beans:constructor-arg name="sessionRegistry" ref="sessionRegistry" /> <beans:property name="maximumSessions" value="1" /> <beans:property name="exceptionIfMaximumExceeded" value="true" /> </beans:bean> <beans:bean id="sessionRegistry" class="org.springframework.security.core.session.SessionRegistryImpl" />
Como mencionamos anteriormente, o novo listener que configuramos lança um ApplicaitonEvent no contexto do spring quando uma sessão de usuário se inicia ou termina. Isso permite que o SessionRegistry seja notificado quando uma sessão de usuário termina. O SessionRegistry permite que se conheça todas as sessões abertas para determinado usuário, informação esta que é utilizada pelo ConcurrentSessionControlStrategy para barrar novos logins.
Testando
Os testes que eu fiz se resumem a logar em navegadores diferentes, e em máquinas diferentes. Pra ambos os casos, o segundo login foi barrado, de forma que o usuário é redirecionado para a página de acesso não autorizado, configurada anteriormente no CasAuthenticationFilter.