Friday, December 31, 2010

Spring Security - MVC: Implementing a Single Entry Filter Tutorial

In this tutorial we will implement a custom Spring Security filter that allows a URL to be accessed just once. We will position the filter after the CONCURRENT_SESSION_FILTER. We will take advantage of the SessionRegistry to query the current session. If you're not familiar with setting-up the SessionRegistry and Concurrent Session Management, please read first the Spring Security - MVC: Querying the SessionRegistry tutorial.

The requirements for our custom filter are the following:
  • You can only have one instance of a page
  • If you create a new tab and enter the same page, you will not be able to access the page again and you will be redirected to a custom page
  • If you create a new browser, same result
  • If you create close and open the same browser, same result
  • If you refresh the same tab, same result
  • You can only gain access if you manually log-out
  • You can only gain access if you clear your browser's cache
  • You can only gain access if your session has expired
So how do we do that? First, we evaluate at what position in the FilterProxyChain should we place this filter. We need the SessionRegistry and we need the HttpServletRequest. Somewhere around the CONCURRENT_SESSION_FILTER looks promising.

If you need to learn more about the FilterChainProxy and ordering of the aliases, please check the Spring Security Reference Table 2.1. Standard Filter Aliases and Ordering

The format for adding the filter is similar to the following:


Before we start adding real code, let's declare a pseudocode first:
if user has logged-in for the first time
   flag the session that the user has logged-in
if the user tries to log-in again
   check if there's a protected URL
       if yes, redirect user to a custom page
       if no, then proceed with the remaining filters
And here's the actual implementation for this filter.

SingleEntryFilter

This filter extends OncePerRequestFilter and overrides the doFilterInternal() method

What is OncePerRequestFilter?
Filter base class that guarantees to be just executed once per request, on any servlet container. It provides a doFilterInternal(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, javax.servlet.FilterChain) method with HttpServletRequest and HttpServletResponse arguments.

The getAlreadyFilteredAttributeName() method determines how to identify that a request is already filtered. The default implementation is based on the configured name of the concrete filter instance.

Source: Spring Reference 3 for OncePerRequestFilter
How did we came up with this implementation? I'm sure it didn't just came out from thin air. Actually, I had to study how the CONCURRENT_SESSION_FILTER alias is implemented. It's filter class is based on ConcurrentSessionFilter.

What is ConcurrentSessionFilter?
Filter required by concurrent session handling package.

This filter performs two functions. First, it calls SessionRegistry.refreshLastRequest(String) for each request so that registered sessions always have a correct "last update" date/time. Second, it retrieves a SessionInformation from the SessionRegistry for each request and checks if the session has been marked as expired. If it has been marked as expired, the configured logout handlers will be called (as happens with LogoutFilter), typically to invalidate the session. A redirect to the expiredURL specified will be performed, and the session invalidation will cause an HttpSessionDestroyedEvent to be published via the HttpSessionEventPublisher registered in web.xml.

Source: Spring Security 3 Reference for ConcurrentSessionFilter
Let's examine the doFilter() method of this class.

ConcurrentSessionFilter

This ConcurrentSessionFilter does the following steps:
1. Retrieve the current session
2. Check if session is not null
3. Check if the SessionInformation is not null
4. Check if the session has expired. If expired, logout and redirect
5. If not expired, continue with remaining filters.

Let's compare this with our custom filter SingleEntryFilter.

Inside the doFilterInternal() method, we do the following:
1. Retrieve the current session and verify if it's not null

2. Retrieve the SessionInformation and verify if it's not null

3. Retrieve the session attribute hasLoggedIn and check if it's null or not

If hasLoggedIn is not null, this means the user has logged already.

4. Next, we loop the guardURI list and check if there's a protected URL. If there's one, we redirect to the redirectURI

5. If this is the user's first time to access the site (i.e new session), set the hasLoggedIn attribute

6. Finally, continue with the remaining filters

Examine how this code resembles the ConcurrentSessionFilter

Now, let's update the the Spring Security XML configuration.

spring-security.xml

The changes we did in this file is we added the following custom filter:

We also declared the singleEntryFilter bean.

Notice the guardURI is a list. That means we can protect multiple URIs. Cool :)

We then need to update our login controller so that we can see the invalid login alert.

LoginLogoutController

To access the site, please login first by entering the following URL:
http://localhost:8080/spring-security-single-login-filter/krams/auth/login
Then access the following URL:
http://localhost:8080/spring-security-single-login-filter/krams/main/common
You can only access the URL once! Here's what you'll get if you try again:

That's it. We've just finished implementing our custom filter based on the requirements we laid earlier. They key action here is we studied the existing filters to see how Spring Security does things. I believe this is a good way to enhance one's knowledge of this framework.

Please read the tutorial Spring Security 3 - MVC: Querying the SessionRegistry for a complete step-by-step guide for the other parts of this application.

The best way to learn further is to try the actual application.

Download the project
You can access the project site at Google's Project Hosting at http://code.google.com/p/spring3-security-mvc-integration-tutorial/

You can download the project as a Maven build. Look for the spring-security-single-login-filter.zip in the Download sections.

You can run the project directly using an embedded server via Maven.
For Tomcat: mvn tomcat:run
For Jetty: mvn jetty:run
StumpleUpon DiggIt! Del.icio.us Blinklist Yahoo Furl Technorati Simpy Spurl Reddit Google I'm reading: Spring Security - MVC: Implementing a Single Entry Filter Tutorial ~ Twitter FaceBook

Subscribe by reader Subscribe by email Share

6 comments:

  1. great!
    thanks men.

    ReplyDelete
  2. @swing.magic, you're welcome :)

    ReplyDelete
  3. First thing first, love the tutorials they've been helping me out like crazy. Now here is the issue I'm having upon logging on as john with the pass admin I'm getting a null pointer exception on like 53 of SingleEntryFilter.java. I tried everything I could think of to fix the issue but it's still happening. Figured I'd ask to see if you could find where I'm screwing up. Thanks.

    ReplyDelete
  4. Hi Krams i want to implement Jasper batch export feature using Spring Jasper.Is Spring Jasper Providing this feature i have done some r&d and got the below url
    https://jira.springsource.org/browse/SPR-1207
    As this suggests that there no batch export feature in spring jasper.
    Am i correct???

    Plz help

    ReplyDelete
  5. Hey,

    As explained in this post, i am not getting expected result

    Firstly i accessed the url which is http://localhost:8080/spring-security-single-login-filter/krams/auth/login

    after that i accessed the url http://localhost:8080/spring-security-single-login-filter/krams/main/common

    but i was expecting i should get redirected to http://localhost:8080/spring-security-single-login-filter/krams/auth/login-alert page

    but it is not...

    and one more point here is, after successful login using the url


    http://localhost:8080/spring-security-single-login-filter/krams/main/common

    if i refresh the page i am getting redirected to

    http://localhost:8080/spring-security-single-login-filter/krams/auth/login-alert

    which is wrong redirection... I am not login again where i just refreshed the page...

    The above code needs to be improved in such a way that it should not redirected to alert page if refresh is happened.

    ReplyDelete
  6. I want to add this solution to JSF Spring Security based web application, where I need to have only enable one user to be able to login to one browser, even if a user tries to see login page after loggedIn he/she should be redirected to authenticated page for the current user. Can u show me an example for this scenario

    ReplyDelete