Wednesday, August 8, 2012

Using Spring Security Concurrent Session controls with Grails

While it isn't a problem for most website, many have a requirement to control how many users can be connected concurrently. This is usually related to sites that sell 'per user' licenses, such as my current company. Unfortunately, this isn't very straightforward in Grails.

First thing to note: Grails Spring Security plugin doesn't support concurrent session. Spring security does have the feature, and the plugin doesn't prevent it from being used, but it doesn't help either. And it's a bit harder because you don't have the advantage of the spring security namespace to help with some of this. Most of the information I found on how to set this up came from this blog post: Using that as a guide, There are three things that have to be added to resources.groovy:

concurrencyFilter(ConcurrentSessionFilter) {
    sessionRegistry = sessionRegistry
    logoutHandlers = [ref("rememberMeServices"), ref("securityContextLogoutHandler")]

concurrentSessionControlStrategy(ConcurrentSessionControlStrategy, sessionRegistry) {
    alwaysCreateSession = true
    exceptionIfMaximumExceeded = true
    maximumSessions = 1
The SessionRegistry is just a mechanism with a thread-safe map to register new sessions. It's stores them both by session id and by principal. It uses a class called SessionInformation. This object contains some information, such as the last time anything happened on this session, and if it's expired. In general, sessions are only registered by the ConcurrentSessionControlStrategy. This strategy actually extends the Spring SessionFixationStragey, which is a whole other ball of wax I won't go into. Either way, this strategy is generally used by the UsernamePasswordAuthenticationFilter. Normally I imagine this is setup using the spring namespace. However, in grails you have to deal with it manually. The way I found for it to work is an entry in boostrap.groovy:
authenticationProcessingFilter.sessionAuthenticationStrategy = concurrentSessionControlStrategy
What this strategy does is enforce *at authentication time* various rules about concurrent sessions. In our case, we set the Maximum sessions to 1. I also set it to throw an exception if it's exceeded. It basically stops the authentication. I do this because I would prefer to tell the user what will happen if they log in: the other session will be expired. This is done by the strategy by looping through the SessionInformation objects tied to the given principal and calling expireNow() on the least recently used one.

Which brings me to the filter.

The filter shown above is inserted into the standard spring security authentication filter chain. From the blog post above, it can be added to the filter chain using the following code in bootstrap.groovy:
SpringSecurityUtils.clientRegisterFilter('concurrencyFilter', SecurityFilterPosition.CONCURRENT_SESSION_FILTER)
Basically, what the filter does is call the SessionRegistry for the given session id. If there is a session information and it is marked as expired, it forces a redirect to the provided 'expiredUrl'.

Once this is done, it generally works as you expect. If you prevent a user from logging in, it's fairly simple to mark other session information as 'expired' and the filter will then redirect them to the 'expiredURL'.

Unless you're using Remember me, in which case everything is screwed.

If you notice in the config of the filter, there's a couple of log out handlers, and one of them is the remember me service. Which removes the remember me token when the filter 'expires' the session. But there's still a gigantic problem: RememberMe tokens are processed by a completely different filter that is oblivious to the concurrent session control. This is the RememberMeAuthenticationFilter. Keep in mind that the username and password filter is only called for a specific url and is usually posted to by some login page. So, it only touches a request if the url of the request matches the one it's looking for.

So, here's the scenario: User A logs in with a remember me token. The normal UsernamePasswordAuthenticationFilter is called. Because there's the remember me option enabled. (easily setup by the grails plugin) It tells the RememberMeServices to add the cookie to the response. Everything is fine after that. So, let's say that user sits idle for a bit and their session expires. The SessionRegistryImpl is also setup to receive HttpSession change events. This is setup by turning on the session event publisher via an option in config.groovy. Something like: grails.plugins.springsecurity.useHttpSessionEventPublisher = true. Which, is also necessary for concurrent session handling to work.

The container expiring the session causes the registry to remove the SessionInformation for this session from the registry. Now, let's say another user logs in with the same credentials (the same principal in Spring Security terms) That user is logged in and now has a session tied to that principal in the registry. Now, let's say that the first user with the remember me cookie starts using the application again. This creates a request that doesn't have a security context, which would normally punt you back to login. However, the RememberMeAuthenticationFilter sees the cookie and authenticates the user. In this case, it's actually a slightly different Authentication object, but the user is still authenticated. The RememberMe filter never looks at the registry or strategy, so as far as the SessionRegistry is concerned, there is still only one user for this principal. Thus the user with the token can use it all day long without running afoul of the concurrent session limits. What this basically means is that anyone using Spring Security with remember me can bypass concurrent session limits by using a remember me token and letting their session expire.

My first assumption was that there was something that needs to be setup that wasn't being done. Maybe something the namespace was doing. But I can't find any hook in either the remember me filter of the service for anything that would handle this. Even looking at newer versions of spring security, the only changes I see are for the registry to use java.util.concurrent, which was probably done after 3.1 when they could assume Java 5.

There are some pretty reasonable extension points to the filter though. I extended it and overrode the method onSuccessfulAuthentication, thinking I could do some of the same things the strategy does. I could register a new information and mark others as expired. But it gets a bit complicated. In this case, I think the users with the token coming back from idle should be the one bounced. Which isn't super hard, as long as the remember me filter is before the ConcurrentSessionFilter. However, there's all kinds of potential threading issues. Especially if an application uses ajax. It's really non-deterministic. I honestly think that the registry would need to be upgraded to understand the RememberMe cookie.

Usually, when working with an established open source project, I assume that I'm doing something wrong. However, when looking through the code, I can't find anything to handle this properly.

So, my current bottom line is this: Concurrent Session can be used with the Grails plugin, however, not if you allow your users to use a 'remember me' token.


I had passed on this issue to some friends at Vmware, and it looks like I was correct: Concurrent Session doesn't work with RememberMe. Here's the ticket that was created:


BadBob said...
This comment has been removed by the author.
BadBob said...

Great and very detailed article. It helps me so much. Thanks a lot.
Your issue was closed as duplicate. Actual issue is:
Everyone, who read this article, please vote for that issue.

Linda Walker said...

With what can be provided to those who need and I believe that your website is good. friv 7

host tony said...

What's even more comfortable when you are entertaining after a day of hard work.
friv 3

steve7876 said...

Oh my word! This looks so darn delicious!How apple pay works with passbook? I swear reading your recipes and trying almost all of them is going way off my diet plans, but the food turns our so goods. Thanks! Can't wait to try this!!

steve7876 said...

Fantastic publish. We seemed to be examining consistently this website in addition to My business is impressed! Extremely helpful info especially the past part: We look after such information a whole lot. We needed this selected information for a long time. Many gifts

shahbaz said...

It should be noted that whilst ordering papers for sale at paper writing service, you can get unkind attitude. In case you feel that the bureau is trying to cheat you, don't buy term paper from it.Ghana Business News

shahbaz said...

In status out foundation charge in kind to alternative the children to materialize pure leaders of which should fracture this precise fat community arousing the appropriate worried up joined among sustainable enter. fort Lauderdale mold removal

steve7876 said...

Great instructions and the author thoughts so well.Sydneyswimmers

terell wiley said...

That's a great idea but i think it's a bit harder because you don't have the advantage of the spring security namespace to help with some of this.
Anyway check this out guys Buy Reverbnation Plays.

shahbaz said...

Thanks , I have just been searching for information about this subject for a long time and yours is the best I have found out till

shahbaz said...

I believe that this information will be beneficial to travel marketers to plan their strategies. Benchmarking will also help a lot. http://gouda-verhuisbedrijf.dolor de cabeza

steve7876 said...

This is really a nice and informative,fortemusic containing all information and also has a great impact on the new technology. Thanks for sharing it,

Mark Taylor said...

Your site is marvels and you have done actually amazing work. Your blog contains so many educational articles. I would like to say thanks for publishing these posts here…

Visit: GED Online Diploma

Hazel Claire said...

I am very much impressed from your post.It has amazing information.I learned a lot of new things which explores my knowledge in developments.

buy pinterest repins

steve7876 said...

Healthy Body Inc is a leader in cutting-edge nutritional Weight Loss Supplements Products. Our mission is to research, create and offer industry-leading premium Health, Beauty, and Fitness supplements.

Thiết kế Pro said...

Very impressive article. I have read each and every point and found it very interesting.
Yepi 2 | kizi 6 | 85play | Frozen Games