How to implement an automatic logout in a web application?

15,326

Solution 1

If you want to handle the ViewExpiredException, you could add the following setting to your web.xml

<error-page>
    <exception-type>javax.faces.application.ViewExpiredException</exception-type>
    <location>/login.jsf</location>
</error-page>

Better yet, use a PhaseListener tio check if the User is still available in the Session.

FacesContext.getCurrentInstance().getExternalContext().getSessionMap().get("User")

If not - navigate to the login page.

FacesContext.getCurrentInstance().getApplication().getNavigationHandler().handleNavigation(FacesContext.getCurrentInstance(), null, "login");

Solution 2

There are two ways that you can do this:

1 Use pull mechanism Every 5 seconds or so, keep asking server if session is valid or not, if server says yes valid, sleep for specified period of time, if server says invalid, show message to user, and logout.

2 Use push mechanism Use a4j:push, so in your server you have a TimerTask which runs says every 5 seconds to check if session is valid or not, if it sees session is valid, sleep, if invalid, send message to user and then logout.

Looks like RichFaces4 online demo is not working, though check this link

I am not sure if RichFaces has something like Primefaces Idle Monitor

Another solution is to use Custom HttpSessionListener

Solution 3

You have to configure the session timeout in your web.xml, the value is in minutes and can't be less than 1:

<session-config>
    <!-- The application will have 15 minutes for session timeout -->
    <session-timeout>15</session-timeout>
</session-config>

You should add a filter that will check if the session has expired:

<filter>
    <filter-name>SessionTimeoutFilter</filter-name>
    <filter-class>edu.home.TimeoutFilter</filter-class>
</filter>

The filter will look like this:

public class TimeoutFilter implements Filter {
    private String startPage = "index.xhtml";

    public void init(FilterConfig filterConfig) throws ServletException {}
    public void destroy() {}

    public void doFilter(ServletRequest request, ServletResponse response,
        FilterChain filterChain) throws IOException, ServletException {
        if ((request instanceof HttpServletRequest) &&
            (response instanceof HttpServletResponse)) {
            HttpServletRequest httpServletRequest = (HttpServletRequest) request;
            HttpServletResponse httpServletResponse = (HttpServletResponse) response;
            if (!isValidSession(httpServletRequest)) {
                String timeoutUrl = httpServletRequest.getContextPath() + "/"
                    + startPage;
                //redirects to the first page
                httpServletResponse.sendRedirect(timeoutUrl);
            }
            filterChain.doFilter(request, response);
        }
    }

    private boolean isValidSession(HttpServletRequest httpServletRequest) {
        return (httpServletRequest.getRequestedSessionId() != null) &&
               httpServletRequest.isRequestedSessionIdValid();
    }
}

Solution 4

Since it is web, you could use some javascript:

var timeout;
var timeoutTime = 294000;
$().ready(function() {
    $(".loading").bind("ajaxSend", function() {
        timeout = setTimeout("TimedOut()", timeoutTime);
    }
});

function TimedOut() {
    //here I do an async call to test if a user is timed out
    var isTimedOut = yourMethodToTest();

    if(isTimedOut) { //do whatever you need to do here;
        alert("session timed out");
    }
    else { //restart the checker
        timeout = setTimeout("TimedOut()",timeoutTime);
    }
}
Share:
15,326
Bevor
Author by

Bevor

Updated on July 14, 2022

Comments

  • Bevor
    Bevor over 1 year

    Our web application runs with JBoss 7.1.1 and Java (JPA2 and RichFaces4). At the moment the problem is that the application just gets stuck if the user is logged in but doesn't do anything inside the application for some time (probably caused due to session timeout). Then the user has to load the web application again, which doesn't look very professional.

    Can you give me a hint, how an automatic logout could be implemented using the mentioned technologies?

    [UPDATE]
    I tried a lot of possibilities but none of them work properly. My thought is to implement a SessionTimeoutListener which knows when the session gets expired:

    @Logged
    @WebListener
    @Named
    public class SessionTimeoutListener implements HttpSessionListener
    {
        @Inject
        private Logger logger;
    
        @Override
        public void sessionCreated(HttpSessionEvent event)
        {
            // not used.
        }
    
        @Override
        public void sessionDestroyed(HttpSessionEvent event)
        {
            logger.info("Session destroyed.");
    
            ApplicationContext.setAutoLoggedOut(true);
        }
    }
    

    This works. But then all the problems appear: I cannot redirect from it because FacesContext is null in there. I cannot fire push events because I get some exceptions like NameNotFoundException or similar (I tried a lot but it seemed that firing events doesn't work out of this too). Then I tried a4j:poll on ApplicationContext.isAutoLoggedOut() but this doesn't work either, because if I execute poll events, the session would never expire. I always come to a dead end. If I could redirect somehow from SessionTimeoutListener, this would be the solution.

    [POSSIBLE SOLUTION]
    I'm now satisfied with a logout which is executed when I click on any button inside the view after session is expired. This current solution is only rudimentary and not applicable for production yet, but this solution works and I will build on it: I use the upper SessionTimeoutListener. Moreover I use a PhaseListener which is being invoked after the SessionTimeoutListener, so if the session expires, the SessionTimeoutListener will be invoked and destroys the session but the SessionTimeoutPhaseListener still has the FacesContext. So I can redirect there to logout page:

    public class SessionTimeoutPhaseListener implements PhaseListener
    {
        private static final long serialVersionUID = -8603272654541248512L;
    
        @Override
        public void beforePhase(PhaseEvent event)
        {
            //not used.
        }
    
        @Override
        public void afterPhase(PhaseEvent event)
        {
            FacesContext facesContext = event.getFacesContext();
    
            if (ApplicationContext.isAutoLoggedOut())
            {
                ApplicationContext.setAutoLoggedOut(false);
    
                try
                {
                    facesContext.getExternalContext().redirect("./logout.xhtml");
                }
                catch (IOException e)
                {
                }
            }
        }
    
        @Override
        public PhaseId getPhaseId()
        {
            return PhaseId.RESTORE_VIEW;
        }
    }
    

    The ApplicationContext is a class with @ApplicationScoped which stores the boolean variables, but this has to be changed, because it would affect every user who currently works with the application. I think about some "ThreadLocal context" to solve that. I still have to distinguish between auto logout and manual logout. The listener is invoked in both cases. Although this solution works at the moment, the redirection in PhaseListener is also tricky, because it would be invoked over and over again by JSF (which causes redirection loop errors in browser), if I wouldn't set "autoLoggedOut" to false.... as I said, only rudimentary, but using the PhaseListener is probably the only suitable solution.