35

I'm using spring/spring-security 3.1 and want to take some action whenever the user logs out (or if the session is timed out). I managed to get the action done for logout but for session timeout, I can't get it working.

In web.xml I only have the ContextLoaderListener specified ( can this be the issue? ) and of course the DelegatingFilterProxy.

I use the auto config like this.

    <security:http auto-config="false" use-expressions="false">
    <security:intercept-url pattern="/dialog/*"
        access="ROLE_USERS" />
    <security:intercept-url pattern="/boa/*"
        access="ROLE-USERS" />
    <security:intercept-url pattern="/*.html"
        access="ROLE-USERS" />

    <security:form-login login-page="/auth/login.html"
        default-target-url="/index.html" />
    <security:logout logout-url="/logout"
         invalidate-session="true"
        delete-cookies="JSESSIONID" success-handler-ref="logoutHandler" />
</security:http>

<bean id="logoutHandler" class="com.bla.bla.bla.LogoutHandler">
    <property name="logoutUrl" value="/auth/logout.html"/>
</bean>

The logout handler is called when user clicks logout, which will make some calls to a database.

But how do I handle the session timeout ???

One way to handle it would be to inject the username into the session when user logs in and then use an ordinary httpsessionlistener and do the same thing on session timeout.

Is there a similar way with spring security, so that when spring discovers that the session is to timeout, I can hook in there, access the Authentication and get the UserDetails from there and do the clean up.

jasilva
  • 730
  • 3
  • 17
  • 45
Perre
  • 651
  • 1
  • 7
  • 13

4 Answers4

65

I've got a simpler solution. This works for both logout and session timeout.

@Component
public class LogoutListener implements ApplicationListener<SessionDestroyedEvent> {

    @Override
    public void onApplicationEvent(SessionDestroyedEvent event)
    {
        List<SecurityContext> lstSecurityContext = event.getSecurityContexts();
        UserDetails ud;
        for (SecurityContext securityContext : lstSecurityContext)
        {
            ud = (UserDetails) securityContext.getAuthentication().getPrincipal();
            // ...
        }
    }

}

web.xml:

<listener>
    <listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class>
</listener>
John29
  • 3,340
  • 3
  • 32
  • 50
  • 1
    From my pov this is the better solution. I strongly recommend using this, as I did. Thx John. Btw: This also handles "normal" log outs! – Tom Fink Feb 07 '14 at 12:53
  • The session mentioned about here while using 'Spring Security' , is it equivalent to the HTTPSession or is it a session of Spring Security ? On an alternative note, the new 'Spring Session' component seems to provide a solution http://docs.spring.io/spring-session/docs/current/reference/html5/#api-redisoperationssessionrepository-sessiondestroyedevent – yathirigan Mar 11 '15 at 04:59
  • 5
    @yathirigan Not sure what you mean by "session of Spring Security". Spring Security uses HTTP session, so basically it's the same thing. – John29 Mar 11 '15 at 14:15
  • 2
    >Spring Security uses HTTPSession< oh ok.. thanks for the clarification. few of the articles which i read (as a beginner to spring session) seemed to indicate that the sessions talked about in Spring Security is different from HTTPSession. that is the reason i had this confusion. – yathirigan Mar 11 '15 at 15:01
  • This solution doesn't work for me: the event.getSecurityContexts() returns an empty list and the event has no principal – Manticore Oct 10 '22 at 09:42
7

Ok, I got a solution working, it's not as good as I'd like, but it get's me to the result.

I create a bean from which I can get a hold of the ApplicationContext.

public class AppCtxProvider implements ApplicationContextAware {
private static WeakReference<ApplicationContext> APP_CTX;

@Override
public void setApplicationContext(ApplicationContext applicationContext)
        throws BeansException {
    APP_CTX = new WeakReference<ApplicationContext>(applicationContext);
}

public static ApplicationContext getAppCtx() {
    return APP_CTX.get();
}
}

I implement HttpSessionEventPublisher and on destroy, i get the UserDetails via sessionRegistry.getSessionInfo(sessionId)

Now I have the spring beans which I need to do the cleanup of the session and the user for whom the session timed out for.

public class SessionTimeoutHandler extends HttpSessionEventPublisher {
@Override
public void sessionCreated(HttpSessionEvent event) {
    super.sessionCreated(event);
}

@Override
public void sessionDestroyed(HttpSessionEvent event) {
    SessionRegistry sessionRegistry = getSessionRegistry();
    SessionInformation sessionInfo = (sessionRegistry != null ? sessionRegistry
            .getSessionInformation(event.getSession().getId()) : null);
    UserDetails ud = null;
    if (sessionInfo != null) {
        ud = (UserDetails) sessionInfo.getPrincipal();
    }
    if (ud != null) {
               // Do my stuff
    }
    super.sessionDestroyed(event);
}

private SessionRegistry getSessionRegistry() {
    ApplicationContext appCtx = AppCtxProvider.getAppCtx();
    return appCtx.getBean("sessionRegistry", SessionRegistry.class);
}
Perre
  • 651
  • 1
  • 7
  • 13
4

You can use SimpleRedirectInvalidSessionStrategy to redirect to a URL when an invalid requested session is detected by the SessionManagementFilter.

Sample applicationContext would be like this:

<http>
    <custom-filter ref="sessionManagementFilter" before="SESSION_MANAGEMENT_FILTER" />
<http>


<beans:bean id="sessionManagementFilter" class="org.springframework.security.web.session.SessionManagementFilter">
    <beans:constructor-arg name="securityContextRepository" ref="httpSessionSecurityContextRepository" />
    <beans:property name="invalidSessionStrategy" ref="simpleRedirectInvalidSessionStrategy " />
</beans:bean>

<beans:bean id="simpleRedirectInvalidSessionStrategy" class="org.springframework.security.web.session.SimpleRedirectInvalidSessionStrategy">
  <beans:constructor-arg name="invalidSessionUrl" value="/general/logins/sessionExpired.jsf" />
  <beans:property name="createNewSession" value="false" />
</beans:bean>
<beans:bean id="httpSessionSecurityContextRepository" class="org.springframework.security.web.context.HttpSessionSecurityContextRepository"/>

If you are using JSF, also refer to JSF 2, Spring Security 3.x and Richfaces 4 redirect to login page on session time out for ajax requests on how to handle Ajax requests as well.

UPDATE: In such a case, you can extend the HttpSessionEventPublisher and listen for sessionDestroyed events like this:

package com.examples;
import javax.servlet.http.HttpSessionEvent;

import org.springframework.security.web.session.HttpSessionEventPublisher;


public class MyHttpSessionEventPublisher extends HttpSessionEventPublisher {

   @Override
   public void sessionCreated(HttpSessionEvent event) {
      super.sessionCreated(event);
   }

   @Override
   public void sessionDestroyed(HttpSessionEvent event) {
      //do something
      super.sessionDestroyed(event);
   }

}

and then register this listener in your web.xml like this:

<listener>
    <listener-class>com.examples.MyHttpSessionEventPublisher</listener-class>
 </listener>
Community
  • 1
  • 1
Ravi Kadaboina
  • 8,494
  • 3
  • 30
  • 42
  • Thanks for the suggestion, but I don't think this will solve the issue. I need to catch the session timeout, I can't wait for the user to make a request (which he/she sometimes will not do, e.g. browser closed). I need to do some releasing of locks that the user may have taken which may be locking for some one else. No, not using JSF, Just spring and velocity. – Perre Aug 08 '12 at 09:48
  • Is there a way to get hold of the Authentication object. I tried it, but getting it via SecurityContextHolder.getContext().getAuthentication() returns null. I need to get the Authentication bound to the session right before timeout. I mean, this to me looks like the HttpSession expire event. How can I get the Authenticated UserDetails object out of this event ? – Perre Aug 08 '12 at 21:01
  • 1
    @Perre How about ((SecurityContext) session.getAttribute("SPRING_SECURITY_CONTEXT")).getAuthentication()? – Eyal Oct 23 '12 at 15:01
  • 1
    @Perre The SecurityContextHolder.getContext() is taking the context from ThreadLocal and it is the spring security filters stack that is responsible to set/remove the context from session to ThreadLocal. So in this case, the spring security filters stack is not invoked and therefore you can't get the SecurityContext from ThreadLocal – Wins Jul 24 '13 at 02:07
  • Hi @Ravi, I want to redirect the page to different urls according to the user's role. Your answer is very helpful. Could you tell me how to redirect in the `sessionDestroyed`? Is it similar as `redirectStrategy.sendRedirect(resquest, response, url)`? – Aiden Zhao Aug 03 '15 at 23:10
0

While session getting created at the time of one microservice application with setMaxInactive interval to 60 sec suppose . Session gets timedout after 60sec but after using your suggested change it should come to sessionDestroyed event but still it gets under session created and session again doesnot gets invalidated.