Monday, June 23, 2008

Porting Container Managed Authentication (CMA) to Spring Security 2

Last week I ported one of my open source project from Container Managed Authentication (CMA) on Tomcat 6 to Spring Security 2, and decided to record some of my finding here. Spring Security 2 offers a wide range of support for different types of authentication mechanism and also allows you to centralize all security related configuration in your Spring context xml file, plus like its predecessor Acegi it allows you to fully customize every step of authentication process which is way more flexible comparing to CMA in my opinion. Enough introduction, here is the steps I performed for the porting.

Step 1 - Add Spring Security dependency in your POM:

If you are not using Maven, you need to download Spring Security library manually and add it to your project build path.

Step 2 - Remove CMA security configuration in your web.xml:

Including all security-constraint, login-config, and security-role elements in your web.xml file

Step 3 - Remove Tomcat security realm configuration:

My realm configuration was defined in META-INF/context.xml file in the WAR, but it could also be defined in server level conf/context.xml file.

note: till now your CMA configuration has been completely removed

Step 4 - Add Spring Security filter chain in your web.xml:




note: don't worry about where the chain is, for now ;-)

Step 5 - Create Spring Security context xml file




As you probably noticed already, two spring beans referred in this xml file have not been mentioned yet - authenticationService and passwordEncoderService. AuthenticationService is a custom class that implements UserDetailsService interface which is responsible for loading the user details for authentication purposes. You can use Hibernate if you have mapping setup for user and role entities already or lighter weight iBATIS or even raw JDBC call for the implementation. PasswordEncoderService class, implements PasswordEncoder interface, was created to help Spring Security compare encoded password, although Spring Security comes with some build-in encoders but I could not find a match for Tomcat's MD5+Hex (Base 16) style encoding therefore provided my own implementation, see Tomcat documentation and source code for details. Both beans are declared using annotation and autowiring.

Step 6 - Change your login form submit target

Change your login form submit from j_security_check to j_spring_security_check so it can be processed by Spring Security instead

Now you should be able to login as usual without changing anything in your database through Spring Security, but some of your pages might not be rendering correctly. That is because some of the method calls on HttpServletRequest do not return correct value anymore, such as getRemoteUser(), since default Tomcat HttpServletRequest implementation is not aware of Spring Security SecurityContext therefore you need to provide a wrapper class that can correctly translate these calls to return the right value. Luckily Spring Security has these wrappers build-in already, all you need to do is add an extra filter in the filter chain.

Final Step - Add securityContextHolderAwareRequestFilter


Now the porting is finally done. Hopefully through this example you can see the power and flexbility of Spring Security 2, with much simplified configuration comparing to Acegi it is indeed a well designed and robust security framework deserve much consideration .

No comments: