Maintain a user list with grails

Preface

This is the first part of a blog entry about the problems we had with grails 1.3.5 in conjunction with shiro to retrieve and maintain a simple list with the currently logged in users. The first part will explain how to keep the list up to date. The second part will describe a solution we use to interact with the grails-environment in case of an automatic logout to remove some rows from the database.

How to take care of automatic timeouts for a active user list

As already mentioned we have the following (as we thought very simple) use-case for our web-application: We want to have an overview-page containing a list of all currently logged in users. To accomplish this, it should be sufficient to catch the following events:

  • Whenever a user logs in, add him to a internal list of logged in users
  • Whenever a user logs out, remove him from the list and release all resources locked by him (in our case: remove a row from a table. This will be the topic of part two)

Of course, we want the same behaviour for an automatic logout (which should take place after some minutes of inactivity).

To secure our app we use the shiro-plugin based on the apache shiro project. In fact, shiro already offers an easy-to-implement interface to inform our app about login and logout events called “AuthenticationListener”. So, we started with the following straight-forward implementation (based on this discussion), which has to be registiered in the resources.groovy file:

public class RewooAuthenticationListener implements AuthenticationListener {
    private static Collection activeUsers = Collections.synchronizedList(new ArrayList());

    @Override
    public void onSuccess(AuthenticationToken authenticationToken, AuthenticationInfo authenticationInfo) {
        PrincipalCollection collection = authenticationInfo.getPrincipals();
        activeUsers.add((String) collection.getPrimaryPrincipal());
    }

    @Override
    public void onFailure(AuthenticationToken authenticationToken, AuthenticationException e) { }

    @Override
    public void onLogout(PrincipalCollection principalCollection) {
        PrincipalCollection collection = authenticationInfo.getPrincipals();
        activeUsers.remove((String) collection.getPrimaryPrincipal());
    }

    public static Collection getActiveUsers() {
        return Collections.unmodifiableList(activeUsers);
    }
}

In case of a “normal” logout this solution works pretty well. But whenever a user just forgets to logout properly (like it is usual for hard working days) and the timeout mechanism has to take place our methods aren’t called at all. After carefully reading the javadoc we recognized the following statement: “This method will only be triggered when a Subject explicitly logs-out of the session. It will not be triggered if their Session times out.”

Therefore, the automatic timeout is neither triggered by your grails app nor by grails at all. Instead, it is your servlet-container which recognizes the connection timeout and cleans up the session. So we’ve searched for another possibility to get informed about the timeout (in this case through the servlet-container itself). The standard way to achieve this is via an own implementation of an HttpSessionListener which has to be registered within web.xml like the following:

...
    <listener>
        <listener-class>com.rewoo.listeners.RewooHttpSessionListener</listener-class>
    </listener>
...

The registered RewooHttpSessionListener looks like this:

public class RewooHttpSessionListener implements HttpSessionListener {
    private static List activeUsers = Collections.synchronizedList(new ArrayList());

    @Override
    public void sessionCreated(HttpSessionEvent event) { }

    @Override
    public void sessionDestroyed(HttpSessionEvent event) {
        String userName = getCurrentUserName(event)
        if (userName == null) {
            return
        }
        RewooHttpSessionListener.userLoggedOut(userName)
    }

    public static void userLoggedIn(String userName) {
        activeUsers.add(userName)
    }

    public static void userLoggedOut(String userName) {
        activeUsers.remove(userName)
    }

    public static List getCurrentUserNames() {
        return Collections.unmodifiableList(activeUsers);
    }

    private String getCurrentUserName(HttpSessionEvent event) {
        PrincipalCollection currentUser = (PrincipalCollection) event.getSession().getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
        if (currentUser == null) {
            return null
        }
        return (String) collection.getPrimaryPrincipal();
    }
}

As you can see, we’ve moved the activeUsers list to the SessionListener and added public methods which can be used by the RewooAuthenticationListener to inform us about regular login / logout events recognized by shiro. We better don’t use the sessionCreated method here to update our user list, because it will be called each time some client connects to the server (and establishes a http session), no matter if the user has been logged in correctly or not. Thus, we better handle this within our RewooAuthenticationListener.

In case of a sessionDestroyed event, we have to differ between two situations:

  • a normal logout occurred: in this case shiro has already cleaned up the current session, so the HttpSessionEvent will not contain the name of the previously logged in user
  • a timeout occurred: in this case shiro hasn’t performed any clean up operation upon the session, so we can read out the name of the previously logged in user

This leads us to the following, new implementation of the RewooAuthenticationListener:

public class RewooAuthenticationListener implements AuthenticationListener {
    @Override
    public void onSuccess(AuthenticationToken authenticationToken, AuthenticationInfo authenticationInfo) {
        RewooHttpSessionListener.userLoggedIn((String) authenticationInfo.getPrincipals().getPrimaryPrincipal());
    }

    @Override
    public void onFailure(AuthenticationToken authenticationToken, AuthenticationException e) { }

    @Override
    public void onLogout(PrincipalCollection principalCollection) {
        RewooHttpSessionListener.userLoggedOut((String) principalCollection.getPrimaryPrincipal());
    }
}

Conclusion

To detect manual and automatic logouts, you have to combine the AuthenticationListener of shiro with the HttpSessionListener of your servlet api. Manual logouts will be handled by shiro, automatic logouts (due a timeout) by the session listener.

Advertisements

5 thoughts on “Maintain a user list with grails

  1. In getCurrentUserName there is :

    return (String) collection.getPrimaryPrincipal();

    Nothing in any of the classes defines collection I have tried :

    return (String) currentUser.getPrimaryPrincipal();

    Neither works, do you happen to have any samples of this project anywhere ?

    This is really useful but I am having difficulties getting it working at the moment

    • I can’t see the statement “return (String) collection.getPrimaryPrincipal()” within the getCurrentUserName method of RewooHttpSessionListener.

      But it seems to be that I’ve really misssed some variable definition for “collection” at the beginning of the onSuccess and onLogout methods. Sorry for that.

      Just add the following definition of “collection” to the beginning of the methods:
      PrincipalCollection collection = authenticationInfo.getPrincipals()

      Hope that works for you!

      • HEy dude sorry I should have got back, lots going on and all that.. I have got it working earlier today and will make a github project from it when in the next day or so. You haven’t missed much will point out the other things I had to do:
        so from bottom to top:
        —————————————————————————————–
        The last statement in last box should be: (principalCollection)
        —————————————————————————————–

        @Override
        public void onLogout(PrincipalCollection principalCollection) {
        RewooHttpSessionListener.userLoggedOut((String) principalCollection.getPrimaryPrincipal());
        }

        —————————————————————————————–
        The box above should be: (currentUser)
        —————————————————————————————–
        private String getCurrentUserName(HttpSessionEvent event) {
        PrincipalCollection currentUser = (PrincipalCollection) event.getSession().getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
        if (currentUser == null) {
        return null
        }
        return (String) currentUser.getPrimaryPrincipal();
        }

        I also set my resources.groovy to be: (paste then ctrl shift o to pull in dependencies (MyAuthenticationListener/ModularRealmAuthenticator) )

        beans = {
        //….
        //Other beans then:

        authListener(MyAuthenticationListener)

        shiroAuthenticator(ModularRealmAuthenticator) {
        authenticationStrategy = ref(“shiroAuthenticationStrategy”)
        authenticationListeners = [ ref(“authListener”) ]

        }

        I think that did it….. but the config part of updating web.xml you can use _Events.groovy to update web.xml automatically and although it works my format is current screwed – so will work on this final part and then make it into a git project for others to use –

        Thanks

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s