Tracking logged in user’s using spring-security and HttpSessionListener in java web application

February 19th, 2009 | Tags: , , ,

If you haven’t already, please first read my article on configuring spring security and after that the article on writing a custom AuthenticationprocessingFilter. It is imperative that you know how to do both before you continue.

We always want to know who is on our website, how many users are logged in and how many visitors are present. Not only is the information useful, it also looks good. :) I tried looking for pluggable solutions to track users but couldn’t find any. Having implemented spring security in a few web apps, I decided to see if there was an easy way to do this.

The problem with tracking user’s without spring security is that you have to track attachment of session attributes whenever a user logs in. This increases the amount of code you have to write and maintain and i’m not a fan of reinventing the wheel. Moreover, if you’re using spring security, you don’t really need to put any authentication credentials of the user in the session.

Spring security provides a mechanism to override the default implementation of their filters by replacing them. You can also place your own filters before or after spring security filters. For our purpose, we’ll create and replace the default AuthenticationProcessingFilter with our own implementation to track when a user logs into the system by overriding the onSuccessfulAuthentication method. This will allow us to be notified of users when they login. To track users logging off(either manually or upon their session timeout), we’ll implement our own HttpSessionListener. The sessionDestroyed method is invoked on both a manual spring security logout and when the session expires.

First our class which will track user’s :

public class UserTracker {

	private static Integer	loggedInUsers	= 0;

	public static void increment() {
		loggedInUsers++;
	}

	public static void decrement() {
		loggedInUsers--;
	}

	public static Integer getLoggedInusers() {
		return loggedInUsers;
	}
}

Yes, thats a very bad implementation but it’s just an example. ;) Remember to synchronize your methods in production or the tracking will break even under moderate load.

I’m going to skip the spring-security configuration here because you really should know how to configure spring security by now. Our AuthenticationProcessingFilter implementation :

public class MyAuthenticationProcessingFilter extends AuthenticationProcessingFilter {

	@Override
	protected void onSuccessfulAuthentication(HttpServletRequest request,
			HttpServletResponse response, Authentication authResult)
			throws IOException {
		super.onSuccessfulAuthentication(request, response, authResult);
		UserTracker.increment();
	}
}

Our HttpSessionListener:

public class MySessionListener implements HttpSessionListener {

	@Override
	public void sessionCreated(HttpSessionEvent arg0) {
	}

	@Override
	public void sessionDestroyed(HttpSessionEvent arg0) {
		UserTracker.decrement();
	}

}

Add this listener to your web.xml:

<listener>
		<listener-class>com.codercorp.security.MySessionListener</listener-class>
</listener>
<session-config>
		<session-timeout>1</session-timeout>
</session-config>

The session-config is for testing purposes, you really don’t want to wait 30 minutes for your session to timeout to test the system.

Thats it, you’re good to go. Whenever a user logs in the AuthenticationProcessingFilter will increment the total number of users and whenever a user logs out (or times out) our session listener will decrement the total number of users.

Feel free to leave a comment if this post has helped you. :)

Share and Enjoy:
  • del.icio.us
  • Google Bookmarks
  • DZone
  • Reddit
  • Digg
  • Facebook
  • Netvibes
  • StumbleUpon
  • Technorati
  • LinkedIn
  • MySpace
  • Print
  • Slashdot
  • Share/Bookmark
  1. Navier
    February 20th, 2009 at 02:00
    Reply | Quote | #1

    Will this work in a distributed system, say with a cluster of 2 app servers on 2 separate physical servers?

    • Gaurav Arora
      February 20th, 2009 at 11:16
      Reply | Quote | #2

      No, this will not work in a distributed environment, you’d need a more concrete system in that case. I’ll see if I can come up with something that scales (properly).

  2. John Rock
    February 24th, 2009 at 00:59
    Reply | Quote | #3

    How would you suggest the best way to extend this idea so that we not only keep an Integer count of logged in users, but also a list of usernames that are logged on…and then remove the relevant username from the list when their session is destroyed? Would this require storing the username in the session specifically or is there another way?

    • Gaurav Arora
      February 24th, 2009 at 14:37
      Reply | Quote | #4

      Do you just want to save their usernames or also their authentication details etc? If it’s just a list of usernames, it’s best to store it in a map of some kind against the principal object obtained via SecurityContextHolder.getContext().getAuthentication().getPrincipal().

  3. ocb
    February 24th, 2009 at 18:23
    Reply | Quote | #5

    Hello, how I can use the getLoggedInusers() method in other class or only can use it in jsp? And, Can I do a redirect in sessionDestroyed? thanks

    • Gaurav Arora
      February 25th, 2009 at 17:42
      Reply | Quote | #6

      Yes, you can use the getLoggedInUsers() method anywhere you like. But you cannot issue a redirect in the listener.

  4. John
    February 25th, 2009 at 13:16
    Reply | Quote | #7

    Thanks for the info on how to get the username. I must admit I am still confused though. Spring documentation is like a long riddle… If I implement my own custom authenticationProcessingFilter as you mention, I am unclear on how to handle the concerns that I currently am handling now using the straight jdbc user service (shown below). Specifically:

    Where do I handle the hashing of the password to validate the user’s entered one?

    Do I lose the ability to take advantage of EhCacheBasedUserCache?

    • Gaurav Arora
      February 25th, 2009 at 18:08
      Reply | Quote | #8

      @John:
      The custom AuthenticationProcessingFilter does not really have anything to do with your UserService. Your UserService is there only to retrieve details from the backend, which it is doing in your configuration. The AuthenticationProcessingFilter simply takes the username and password that is input by the user and retreieves the User object using the defined UserService.

      You do have to explicitly handle the hashing since you’re using a password encoder the pass returned from the database must be encoded already. The configuration you sent over should work just fine except for the password being returned should be encoded. Adding an AuthenticationProcessingFilter to it shouldn’t break it.

      As far as the caching goes, changes to UserService and AuthenticationProcessingFilter should not change the way User objects are cached. So you can use the cache still. (I’m not 100% sure about this though)

      Go ahead and try the changes, we can always fix it if the config does break. ;)

      Gaurav

    • Gaurav Arora
      February 25th, 2009 at 18:10
      Reply | Quote | #9

      Forgot to add that even if you do implement your own service, all you’d have to do was return a hashed password in the User object, thats it.

  5. Lukas
    February 28th, 2009 at 18:29

    Thank you for useful post.
    I just don’t understand, why you increment the counter, when a user logs in and decrement the counter, when a session expires. The opposite action of session expiration should be session creation. The problem could be, that you decrease counter, when a session expires, but there’s no guarantee, that the expired session was session of logged-in user. It could be session of not-logged in visitor.
    Wouldn’t be better to check, if there is security context present (in session), when you decrease the counter?

  6. Sachin Yadav
    April 6th, 2009 at 14:51

    Hi,

    You post is quite useful for me, i am using the customer listner to put userId and session in a hash map (in a singleton class). Now the problem is that site admin should eb able to see all the logged in users and can logout any of them from is panel.

    First half or requirement can easily be done using you code, can you please guide me know how do i call logout for a user when logout if clicked by admin and not the user himself.

  7. Zika Zikic
    September 4th, 2009 at 02:30

    Class UserTracker is not Thread-Safe, you should consider that first when you post such example.

    Rgrds.

  8. October 22nd, 2009 at 10:47

    Nice one Buddy,
    Gives a good head start.

  9. Jacques de Molay
    January 29th, 2010 at 06:38

    Nice example, but using sessionRegistry.getAllPrincipals() you get an up-to-date list of logged-in users.

TOP