Skip to content

Spring Security 3 Unit Testing Example with DbUnit

This tutorial extends on the baseline laid by Spring Security Tutorial maven archetype.

I’ve modified the spring security config file and will be unit testing a simple ProfileService (CRUD) on a Profile entity (User object). The tests include login/logout and spring services security unit testing.

application-security.xml

<?xml version="1.0" encoding="UTF-8"?>

<beans:beans
    xmlns="http://www.springframework.org/schema/security"
    xmlns:beans="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-3.0.xsd

http://www.springframework.org/schema/security

http://www.springframework.org/schema/security/spring-security-3.0.xsd">

	<global-method-security pre-post-annotations="enabled"/>

	<http use-expressions="true">
		<intercept-url pattern="/secure/extreme/**" access="hasRole('ROLE_SUPERVISOR')"/>
		<intercept-url pattern="/secure/**" access="isAuthenticated()" />
		<intercept-url pattern="/**" access="permitAll" />
		<form-login />
		<logout />
		<remember-me />

		<!-- Uncomment to limit the number of sessions a user can have -->
		<session-management invalid-session-url="/timeout.jsp">
			<concurrency-control max-sessions="1" error-if-maximum-exceeded="true" />
		</session-management>
	</http>

	<authentication-manager>
		<authentication-provider>
			<!--  <password-encoder hash="md5"/> -->
			<user-service>
				<user name="admin" password="admin" authorities="ROLE_ADMIN, ROLE_USER" />
				<user name="user" password="user" authorities="ROLE_USER" />
				<user name="reports" password="reports" authorities="ROLE_REPORTS" />
			</user-service>
		</authentication-provider>
	</authentication-manager>	

</beans:beans>

Base DB Unit Test with utils for Spring Security

import java.io.FileInputStream;
import java.sql.Connection;
import javax.inject.Inject;
import javax.sql.DataSource;
import org.apache.log4j.Logger;
import org.dbunit.database.*;
import org.dbunit.operation.DatabaseOperation;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.test.context.transaction.*;

/**
 * Base Test that wraps a DbUnit style Test case by inserting a sample testdata
 * on startup. Provides necessary functions to login/logout, add roles, etc
 */
public abstract class BaseSecurityTest {

	private static final Logger logger = Logger
			.getLogger(BaseSecurityTest.class);

	Connection con;
	IDatabaseConnection dbUnitCon;
	IDataSet dataSet;

	@Inject
	private DataSource dataSource;

	protected abstract String getTestClassname();

	@BeforeTransaction
	public void beforeTx() throws Exception {
		logger.debug("Opening Db Connection ... ");
		con = DataSourceUtils.getConnection(dataSource);
		dbUnitCon = new DatabaseConnection(con);
		dataSet = getDataSet(getTestClassname());

		DatabaseOperation.CLEAN_INSERT.execute(dbUnitCon, dataSet);
	}

	@AfterTransaction
	public void afterTx() throws Exception {
		logger.debug("Releasing Db Connection ...");
		DataSourceUtils.releaseConnection(con, dataSource);
	}

	protected IDataSet getDataSet(String name) throws Exception {
		return new FlatXmlDataSetBuilder().build(new FileInputStream(
				"src/test/resources/" + name + ".xml"));
	}

	protected void clearContext() {
		SecurityContextHolder.clearContext();
	}

	protected void login(String username, String password) {
		SecurityContextHolder.getContext().setAuthentication(
				new UsernamePasswordAuthenticationToken(username, password));

		logger.debug("User:" + username + " logged in");
	}

	protected String getLoginDetails() {
		Object principal = SecurityContextHolder.getContext()
				.getAuthentication().getPrincipal();

		if (principal instanceof UserDetails) {
			return ((UserDetails)principal).getUsername();
		}
		else {
			return principal.toString();
		}

	}
}

ProfileServiceTest

import static org.junit.Assert.assertEquals;
import java.util.List;
import javax.inject.Inject;
import org.apache.log4j.Logger;
import xxx.Profile;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.transaction.TransactionConfiguration;
import org.springframework.transaction.annotation.Transactional;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
		"classpath*:spring/applicationContext-core.xml",
		"classpath*:spring/applicationContext-mail.xml",
		"classpath*:spring/applicationContext-security.xml" })
@TransactionConfiguration
@Transactional
public class ProfileServiceTest extends BaseSecurityTest {

	private static final Logger logger = Logger
			.getLogger(ProfileServiceTest.class);

	@Inject
	private ProfileService profileService;

	@Override
	protected String getTestClassname() {
		return "ProfileServiceTest";
	}

	@Test
	public void testUserLogin() {
		login("admin", "admin");
		String username = getLoginDetails();
		assertEquals(username, "admin");

		login("user", "user");
		String username2 = getLoginDetails();
		assertEquals(username2, "user");

		login("reports", "reports");
		String username3 = getLoginDetails();
		assertEquals(username3, "reports");
	}

	@Test
	public void testRead() {

		// Test with role that has access to the service
		login("user", "user");
		profileService.read("1");

		login("reports", "reports");
		profileService.read("1");

		// Test with role that doesn't have access to the service
		login("admin", "admin");
		try {
			profileService.read("1");
		}
		catch (AccessDeniedException e) {
			// Expected
			assert(true);
			return;
		}

		// it should not reach here
		assert(false);
	}

	@Test
	public void testFindProfiles() {
		List<Profile> list = profileService.FindProfiles();
		assertEquals(4, list.size());
	}

	@Test
	public void testFindProfileById() {
		List<Profile> list = profileService.findProfileById("123");
		assertEquals(1, list.size());
	}

	@Test
	public void testSave() {

		// Test with role that has access to the service
		login("admin", "admin");
		Profile dummy = new Profile();
		profileService.save(dummy);		

		// Test with role that doesn't have access to the service
		login("reports", "reports");
		try {
			profileService.save(dummy);
		}
		catch (AccessDeniedException e) {
			// Expected
			assert(true);
			return;
		}

		// it should not reach here
		assert(false);
	}
}

The Profile Service

public interface ProfileService {

	@PreAuthorize("hasRole('ROLE_USER') or hasRole('ROLE_REPORTS')")
	Profile read(String id);

	List<Profile> findProfiles();

	List<Profile> findProfileById(String Id);

	@PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER')")
	Profile save(Profile account);

	@PreAuthorize("hasRole('ROLE_ADMIN')")
	void delete();

}
[Slashdot] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

GIT Commands I Need to Memorize

I work as an application developer. Most of my career has been spent in large corporate environments that have configuration management teams, systems administrators, dbas, etc in addition to software engineers. As such I am a version control user, and not an administrator or power user.

During my daily routine I commit to a remote repo, and I update my existing repo. I regularly compare my local changes with existing ones, and I regularly look at commit history.

I rarely branch code, I rarely check out branches or commit to branches other than the trunk.

I’ve done this with CVS (which still remains my preferred vcs), Visual Source Safe, SVN, and StarTeam (which happens to be my least favorite vcs).

When I first learned about GIT a couple of years ago, I was interested to hear that it was somehow different than these other systems.

I’ve been using GIT very irregularly for the last couple of years, I have a small github account. But I haven’t used it enough to commit the standard commands (listed above) to memory. So this then is my meager attempt at doing so:
*** This assumes you’ve installed git and have configured the global settings.

  • Creating a repo:

    mkdir project
    (create some code in the directory)
    cd project
    git init
    

    But wait there’s more…
    This is deceptively easy. In fact you’re more likely to want to create a local and remote repository, a la github style for example.
    In that case, you’re going to want to do this:

    1. Create a remote repo
    2. Generate your ssh-keys
    3. Git init, like above
    4. git add file_to_commit
      git commit -m 'first commit of all files added'
      git remote add origin git@github.com:user/git-repo.git
      
  • Check out an existing repo:
    This is pretty straightforward, but assumes you’ve set up your ssh-keys.

     git clone [url] 
  • Commit local changes:
    Once you start working, a benefit of having a local repository is that you can commit a lot, without having to worry about breaking a continuous integration environment in the process.
    This is pretty much the same as with other vcs.
    First add:

     git add somefile.txt 

    Then commit:

     commit -m 'my message' 

    Caveat:

    • It’s probably useful to add a couple of files at once, rather than one at a time:
       git add one.txt two.txt 

      or

       git add 'documentation/*.screen' 

      will recursively add all new files ending with .screen from the documentation directory

    •  git status -s 

      will tell you what has been added, and what has been changed since the add. (-s is for short format)

    • Commit changes to remote repo
       git push origin master 
  • Remove stuff:

     git rm file 

    will remove the file from the staging area entirely and also off your disk.

     git rm -r dir 

    recursive delete

  • Check what’s been committed:
    This is different than status because I want to know what’s different between my remote repo and my local one.

     git diff --stat master...origin

    Git will automatically figure out what the common commit of the two commit is and do the diff off of that.

Hat tips:
Git Reference – my favorite site I’ve found explaining git. It’s practical, provides examples, and the authors don’t go out of their way to make simple concepts seem complex. I’ve paraphrased a few of their examples here.

[Slashdot] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

Subversion, JavaHL, and Eclipse on Linux

I normally use emacs and command line svn when working at home on Linux. Eclipse to me, in principle, is a necessary evil of programming with larger groups. But a recent volunteer project I’ve been working on for the Philippine Scholars of Minnesota is forcing me out of this habit.

And with this change, I’ve suddenly been introduced to the world of JavaHL. So what is JavaHL? Per the previous link, it’s the Java language binding for the Subversion API. On Windows the subclipse project is able to package JavaHL, but not on other operating systems. So that’s why I’ve never had to deal with it before.

Fortunately, after finding out what JavaHL actually is, it’s pretty easy to install on Ubuntu, as you might expect:

sudo apt-get install libsvn-java

This will install JavaHL to /usr/lib/jni as a standard install, and this is where eclipse will look for it by default. If you’ve installed it in a non-standard way, you might want to look at this link regarding overwriting the java.library.path with an eclipserc file.

[Slashdot] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

Syncing IPod in Linux

A colleague recently pointed me to an article about syncing my ipod with a Linux box. The article is good but my experience getting this to work was sufficiently different enough to warrant another post.

My first issue came with Step 1:

sudo add-apt-repository ppa:pmcenery/ppa

This issue I had was that the keyserver was down. This may not always be a problem, but based on a few google searches it’s hardly an uncommon problem.
I found my solution with this article:

sudo sh -c "echo 'deb http://ppa.launchpad.net/PPA_NAME/ppa/ubuntu UBUNTU_VERSION main' >> /etc/apt/sources.list"
sudo apt-key adv --keyserver keys.gnupg.net --recv-keys XXXXXXX

My next issue was with Step 2:

sudo apt-get install gvfs gvfs-backends gvfs-bin gvfs-fuse libgvfscommon0 ifuse libgpod-dev libgpod-common libiphone-utils libiphone0 python-iphone libplist++1 libplist-utils python-plist libusb-1.0-0 libusb-1.0-0-dev libusbmuxd1 usbmuxd

I replaced libiphone-utils with libimobiledevice-utils, and removed python-iphone from the list.

And lastly, Step 5:
I created /mnt/ipod

mkdir -p /mnt/ipod

and made it sufficiently writable. I did not need to do anything else in this step!

[Slashdot] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

Debugging MySQL ERROR1025 – Error on rename of

If you ever executed the following command in mysql:

alter table table drop column;

and got the following error:
1025 – Error on rename of ‘.\\#sql-880_3c’ to ‘.\\ ’ (errno: 150),

Don’t panic.

It’s probably because you’re trying to drop a column that’s a foreign key. Execute this command: SHOW ENGINE INNODB STATUS to debug more. A large text is dumped on the console and if you read carefully, you’re likely to have something similar to: “LATEST FOREIGN KEY ERROR…” The solution is to drop foreign key and any corresponding index before dropping the column.

Here are the steps that you must do

ALTER TABLE <table> DROP FOREIGN KEY <fk>;
ALTER TABLE <table> DROP INDEX <idx>;
ALTER TABLE <table> DROP COLUMN <column>;
[Slashdot] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

Installing Sup on Ubuntu Server 8.04 LTS

This is not a complete step-by-step set of instructions, but it should be pretty close.

sudo apt-get install ruby-full build-essential

wget http://rubyforge.org/frs/download.php/69365/rubygems-1.3.6.tgz
 tar -zxvf rubygems-1.3.6.tgz
 cd rubygems-1.3.6/
 sudo ruby setup.rb
 sudo ln -s /usr/bin/gem1.8 /usr/bin/gem
 sudo gem update --system
sudo gem install rake

sudo apt-get install zlib1g-dev uuid-dev

sudo gem install ncursesw

 # We need this extra package
 sudo apt-get install libncurses5-dev libncursesw5-dev
 sudo gem install sup
 sup

Also had to make one code change, described here

#optional
#installed offlineimap
sudo apt-get install offlineimap

Links
Sup RubyForge Wiki
Use offlineimap with gmail – add some hooks
Setting up offlineimap for gmail backup
handy script examples to add sources

[Slashdot] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

Migrating from Hibernate 3.3.2, JPA 1 to Hibernate 3.5 CR2 and JPA 2

Starting with version 3.5 (currently CR2), Hibernate Annotations 3.x and Hibernate EntityManager projects have been merged into the Hibernate Core codebase as invidual modules. As a surprising addition, Hibernate Envers subproject is added as well (that’s a great thing as you get Auditing, out of box hereafter). So if you’re stuck with Hibernate 3.3.x and would like to try the almost final release, what do you need to do?

Unfortunately, the JBoss folks won’t tell you since they are very busy rebranding, redesigning their websites for the past couple of years (and it’s still so fragmented, I wonder why they just can’t hire a UI designer and a Content Manager. Talk about narcissist JEE programmers). Anyways, I wanted to try the new distribution since it’s getting close to the final release and it implements JPA 2. In case you’ve missed the boat, this is a good place to find out what’s new in JPA 2.

I tried the brute force method first: replace all 3.3.x jars with 3.5 CR2 jars.

Before: 7 jars
ejb3-persistence.jar
hibernate-annotations.jar
hibernate-commons-annotations.jar
hibernate-entitymanager.jar
hibernate3-ast.jar
hibernate3.jar
envers-1.2.1.ga-hibernate-3.3.jar

After: 2 jars
javax.persistence.jar
hibernate3.jar — download from hibernate.org

Now, that’s a good sign. And all other optional and required dependencies (jta, javaassist, swarmcache,c3po, all other cache jars) haven’t changed.

But wait, where can you download javax.persistence.jar? Isn’t that supposed to be ejb3.1-persistence.jar? I’m not sure either. What I’m sure is this: There is no direct download link for the JPA2 api jar anywhere. Try it. Mail me if you find it.

I downloaded an entire GlassFish 3 distribution (JEE 6 reference implementation along with JPA 2) instead. And copied the javax.persistence.jar from its modules folder, instead. So in summary, I replaced the 7 jars with these 2 jars and does it work?

So, far my existing applications haven’t broken. The tests seems fine. And looks like the migration did work. Impressive.

[Slashdot] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

The Thoughtworks Rover Problem – Groovy Edition

I had some down time at work, and set about working on the Rover problem.

There are three problems, I chose the middle one to start with. I wrote it in groovy and used the very groovy clibuilder M. Laforge recommended in his comment. I mavenized it to make it easier to share.

You can find my solution here if you’re interested. It’s a good practice problem.

[Slashdot] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

Simple Processes: Bash vs. Python – “A No-Brainer”

I haven’t posted in about 5 billion years. Well, it seems like that anyway. As I sit here partying with my laptop on a Friday night, I’ve been wondering how to display the track length of the current track that xmms2 is playing, without having to use the “xmms2 status” command. Yes, I listen to and control all my music via a full-featured music player/manager from the command line (xmms2). The problem with using the “status” command, is that it just prints out a continuous status of what’s playing and it just keeps going, which is exactly what it’s supposed to do. It’s just like “tail -f”, So you have to ctrl-c to get out of it. Yes. That is my sole motivation for doing this, to avoid ctrl-c. Well, and maybe to learn a thing or two, but mostly out of defiance of having to use ctrl-c.

So I figured, hey I know enough bash to at least get something rolling in order to do this. Of course I had to keep looking stuff up in reference guides and on websites, because there’s no way I can remember it all. Basically, it came down to 4 key tools for going the bash route.

1. sed – to strip brackets, slashes, colons, spaces, and other undesired characters in order to make the strings easier to deal with
2. awk – to find the location of decimal points or other characters
3. cut – to get characters to the left or right of decimal points
4. bc – to calculate and convert the milliseconds to human readable form

I finally got it to work reliably tonight.
It was a horrid process that took forever. What a waste of time. But hey, that’s all part of the RTC experience!
So here it is, please excuse the bad variable names and sloppiness. I was too pissed to give a ****.
Note: I renamed this to xmms2tracklensafe and cut out all the curse words and tried to put some sense into the comments.

#!/bin/bash
tmpfileinfo=xtemp14251.tmp
tmpfile=xtemp91839.tmp
#use xmms2 info here
xmms2 info > $tmpfileinfo
vargrep=$(grep "duration" $tmpfileinfo)
echo $vargrep > $tmpfileinfo
#chopping the brackets, slashes, and spaces out of the string
sed -i 's/\[//g' $tmpfileinfo
sed -i 's/\]//g' $tmpfileinfo
sed -i 's/\///g' $tmpfileinfo
sed -i 's/'\ '//g' $tmpfileinfo
#convert the = to an X, easy to find
sed -i 's/\=/X/g' $tmpfileinfo
#for an mp3 - pluginmaddurationX
sed -i 's/\pluginmaddurationX//g' $tmpfileinfo
#for a flac - pluginflacdurationX
sed -i 's/\pluginflacdurationX//g' $tmpfileinfo
#God knows what other codecs there might be
#prints the time in milliseconds
echo "$(cat $tmpfileinfo) ms"
vargrep2=$(cat $tmpfileinfo)
varx=$vargrep2
#convert the time to minutes
var2=$(echo "$varx/1000/60" | bc -l)
echo $var2 > $tmpfile
#we are left with a fraction of a minute, which occurs after the decimal, we need to find the decimal point
var3=$(awk 'BEGIN { print index('$var2', ".") }')
#get the characters after the decimal point, this is our fraction of a minute
var4=$(cut -c $var3- $tmpfile)
#convert that fraction of a minute into seconds
var5=$(echo "scale=1; $var4*60" | bc -l | xargs printf "%1.0f")
#simply subtracting a 1 from the index of the decimal point, to be used in order to retrieve the number left of the decimal
varcut=$(echo "$var3 - 1" | bc -l | xargs printf "%1.0f")
#gets the number to the left of the decimal
var6=$(cut -c -$varcut $tmpfile)
#now we need to deal with the decimal in the number that has been converted to seconds
var7=$(awk 'BEGIN { print index('$var5', ".") }')
varcut2=1
#I honestly don't remember why I did this.  I think I needed it before I started adding | xargs printf "%1.0f"
if [ "0" != $var7 ]
	then
		varcut2=$(echo "$var7 - 1" | bc -l | xargs printf "%1.0f")
fi

echo $var5 > $tmpfile
#now we get the seconds as a whole number, by getting the characters left of the decimal
var8=$(cut -c -$varcut2 $tmpfile)
var8=$(cat $tmpfile)

#need to prepend a 0 to the seconds if they are under 10
if [ $var8 -lt 10 ]
	then
		var8="0$var8"
fi

#print out the time in human readable form, minutes : seconds
echo "$var6:$var8"
#clean up the temp files
rm -f $tmpfile
rm -f $tmpfileinfo

Wow, what a pain in the pee-hole. It took me several hours over two nights and I even lost some sleep over it. So, I decided to try it Jeff’s way, using Python, since he brags about it so much. I had never used it before, and now that I have, I think I’ll be using it a lot! A lot of you are saying, “Well yeah, duh. Idiot.” or something more insulting, and I fully accept that. In fact, I encourage it!

First, we have the simple bash script that is used to execute the entire process from the command line.

#!/bin/bash

tmpfile=assscrambler124524.tmp
xmms2 info > $tmpfile

python ~/scripts/xmms2len.py

rm -f $tmpfile

And now we have the giant, complex, and horrendous python script.

file = open('./assscrambler124524.tmp')
for line in file:
	if(line.find('duration',0) > -1) :
		#print(line)
		s = line
		searchstr = 'duration = ';
		x = s.rindex(searchstr, 0);
		if(x > -1):
			timeMillis = s[x+len(searchstr):len(s)].strip()
			#print(timeMillis)
			timeConverted = float(timeMillis)/1000/60
			#print(timeConverted);
			stime = str(timeConverted)
			decIndex = stime.rindex('.', 0)
			timeFraction = float(stime[decIndex:len(stime)])
			seconds = int(round(timeFraction*60,0))
			strseconds = str(seconds)
			minutes = stime[0:decIndex]
			strminutes = str(minutes)
			if(seconds < 10) :
				strseconds = '0' + str(seconds)
			print(timeMillis + ' ms\n' + str(minutes) + ':' + strseconds)
			break

Wait, that's it? Just some simple searches, substrings, calculations, easy conversions between types, and that's all? Not to mention the incredibly easy way to open a file and iterate through its lines.
Now, I commented out some print statements here, and I could have compacted it more, but I wanted it to be reasonably clear. The point of course, and 95% of you know probably know this already, is that python kicks bash's ass when it comes to things that should be, well, programs. Of course bash scripts will always have their place, and they are necessary for so many things. I still love bash and am very fond of it, but from a programming perspective, it just doesn't "cut the mustard", as Jeff would say.

Well, that's it for my exciting Friday night. Someday I'll get my crap together and talk more about my xmms2 setup, the xmms2 python equalizer, the xmms2 scrobbler for last.fm, and the other scripts I've written for xmms2.

Cya!

My main reference for python: http://www.astro.ufl.edu/~warner/prog/python.html

[Slashdot] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

Custom Logout Filter handler in Spring 3

Spring Security provides comprehensive support for adding custom handlers for logout/login. The default logout simply logs the user out and goes to the login page. What if the application needs to determine the user’s role and redirect to a separate screen? For example, for an admin, a custom logout screen. What if the user is automatically logged out after 10 mins of user inactivity? You don’t want the user to see a login screen without any message on it. That’s bad.

Here’s an example demonstrating a custom logout handler using Spring 3 security module. The code has some incompatibility issues with Spring 2 as Spring 3 Security is not 100% backward compatibility.

Sample User

public class User extends org.springframework.security.core.userdetails.User {
 // Stuff
}

Custom Filter Handler

import java.io.IOException;

import javax.annotation.PostConstruct;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.core.Authentication;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;

import xxx.model.User;

public class LogoutFilterWrapper
	implements Filter {

	private String logoutSuccessfulUrl;

	private String logoutSuccessfulUrlAdmin;

	private String logoutSuccessfulInactivityUrl;

	private LogoutFilter filter;

	@PostConstruct
	protected void initialize() {

		final SecurityContextLogoutHandler context = new SecurityContextLogoutHandler();
		context.setInvalidateHttpSession( true );
		this.filter =
			new LogoutFilter( new CustomLogoutSuccessHandler(), new LogoutHandler[] { context } );
	}

	public void setLogoutSuccessfulUrl(
		String inUrl ) {

		this.logoutSuccessfulUrl = inUrl;
	}

	public void setLogoutSuccessfulUrlAdmin(
		String inUrl ) {

		this.logoutSuccessfulUrlAdmin = inUrl;
	}

	public void setLogoutSuccessfulUrlInactivity(
		String inUrl ) {

		this.logoutSuccessfulInactivityUrl = inUrl;
	}

	public final void init(
		FilterConfig inFilterConfig )
		throws ServletException {

		this.filter.init( inFilterConfig );
	}

	public final void destroy() {

		this.filter.destroy();
	}

	public final void doFilter(
		ServletRequest inRequest,
		ServletResponse inResponse,
		FilterChain inChain )
		throws IOException, ServletException {

		this.filter.doFilter( inRequest, inResponse, inChain );
	}

	/**
	 * Success Handler
	 */
	private class CustomLogoutSuccessHandler
		implements LogoutSuccessHandler {

		private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

		@Override
		public void onLogoutSuccess(
			HttpServletRequest request,
			HttpServletResponse response,
			Authentication authentication )
			throws IOException, ServletException {

			String targetUrl = "";
			User principal = null;

			if ( authentication != null ) {
				if ( !(authentication.getPrincipal() instanceof User) ) {
					throw new IllegalArgumentException( "Invalid security principal type!" );
				}
				principal = (User) authentication.getPrincipal();
			}

			if ( principal == null ) {
				throw new IllegalStateException( "Security principal is not initialized!" );
			}

			final String loginUrl =
				principal.isAdministrator() ? LogoutFilterWrapper.this.logoutSuccessfulUrlAdmin
					: LogoutFilterWrapper.this.logoutSuccessfulUrl;

			final String timeoutUrl =
				LogoutFilterWrapper.this.logoutSuccessfulInactivityUrl
					+ "?login="
					+ request.getContextPath()
					+ loginUrl;

			targetUrl = request.getQueryString().contains( "timeout=true" ) ? timeoutUrl : loginUrl;

			redirectStrategy.sendRedirect( request, response, targetUrl );
		}
	}
}

Spring-config

<!-- Register a custom logout filter -->
<beans:bean id="customLogoutFilter" class="com.cramer.srf.security.LogoutFilterWrapper">
    <beans:property name="logoutSuccessfulUrl" value="/public/auth/login.htmlx" />
    <beans:property name="logoutSuccessfulUrlAdmin" value="/public/auth/admlogin.htmlx" />
    <beans:property name="logoutSuccessfulUrlInactivity" value="/public/auth/timedout.htmlx" />
</beans:bean>
[Slashdot] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]