javajsfjakarta-eecdijsf-2.2

CDI/JSF How to handle session timeout


I have a Jakarta EE8 ( CDI 2 / Weld / JSF 2.3 / Wildfly ) app where I need to execute some cleanup code when the user logs out, manual logout are fine however I now need to fire an event when logout happens automatically due to session timeout, to handle this I have tried the following two methods already...

@Named
@SessionScoped
public class HttpSessionObservers implements Serializable {

    /**
     * 
     */
    private static final long serialVersionUID = 1L;
    
    @Inject private Logger log;
    @Inject SecurityContext securityContext;

    @PreDestroy
    public void processSessionScopedDestroying() {
        
        log.debug("Http Session predestroy");   
        Principal principal = securityContext.getCallerPrincipal(); //<----is null
        //...do some cleanup/logging operations on user account
    }

}

The above @PreDestroy callback fires when the session times out but the logged in user (principal) is always null so it seems they have already been logged out so I cannot obtain the user.

@Named
public class HttpSessionObservers implements Serializable {

    /**
     * 
     */
    private static final long serialVersionUID = 1L;
    
    @Inject private Logger log;
    @Inject private Event<UserLogoutEvent> userLogoutEvent;
    
    public void processSessionScopedInit(@Observes @Initialized(SessionScoped.class) HttpSession payload) {
        log.debug("Http Session initialised");
    }

    public void processSessionScopedDestroying(@Observes @BeforeDestroyed(SessionScoped.class) HttpSession payload) {
        //Never fires
        log.debug("Http Session predestroy");   
        Principal principal = securityContext.getCallerPrincipal();
        //...do some cleanup/logging operations on user account
    }

    public void processSessionScopedDestroyed(@Observes @Destroyed(SessionScoped.class) HttpSession payload) {
        log.debug("Http Session destroyed");    
    }
}

Here, the @BeforeDestroyed event is never fired, cannot seem to find any examples of this working, it does work ok for other scopes.


Solution

  • Implement your own HttpSessionListener and annotate it with @WebListener The container should recognize this and will call the the sessionDestroyed() method in your implementation. Your class is eligible for CDI Injection so in sessionDestroyed(), you can fire an event and listen for it with a CDI Observer.

    Ref: https://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpSessionListener.html

    EDIT:

    Try this then: Scope your HttpSessionListener impl to @ApplicationScoped. Have a synchronized Map of sessionId/username. In sessionCreated() insert into the map, and on sessionDestroyed() remove it in a finally block.

    The container will also have several implicit HttpSessionListeners, so there's a chance yours is being called last long after the session is invalidated. If thats the case, there's another technique you can use to insert your HttpSessionListener impl first. Try the above technique first then we can dive into this.