Do your web apps need to run in the servlet containers from different vendors? How do you manage their vendor specific security settings? I recently ran into this exact problem while developing StackHunter. Like me, you probably started with container-managed security as you have many times before. The pain probably started after you tried deploying to your second or third container and got worse from there. This is one of the problems with Java web apps — they aren’t portable between containers out-of-the-box. You can’t just take a war file from one vendor’s container and deploy it to another without configuring the security handlers in that vendor’s unique way. This article will show you how to replace the “standard” Java container-managed security with Spring Security to create a single, secure application that can be deployed to any servlet container.

The Container-Managed, Non-Portable Approach

The traditional container-managed approach uses the web.xml file to identify protected resources. The file has one or more security-constraint sections that links resource paths with their required user roles. It also has a login-config section that defines the type of authentication in use (BASIC, FORM, etc.), along with some of their settings.

My original web.xml looked something like this.

<security-constraint> <web-resource-collection> <web-resource-name>ADMIN Resources</web-resource-name> <url-pattern>/setup/*</url-pattern> <url-pattern>/users/*</url-pattern> </web-resource-collection> <auth-constraint> <role-name>ADMIN</role-name> </auth-constraint> </security-constraint> <security-constraint> <web-resource-collection> <web-resource-name>VIEWER Resources</web-resource-name> <url-pattern>/*</url-pattern> </web-resource-collection> <auth-constraint> <role-name>ADMIN</role-name> <role-name>VIEWER</role-name> </auth-constraint> </security-constraint> <security-constraint> <web-resource-collection> <web-resource-name>Unprotected Resources</web-resource-name> <url-pattern>/images/*</url-pattern> <url-pattern>/public/*</url-pattern> </web-resource-collection> </security-constraint> <login-config> <auth-method>FORM</auth-method> <realm-name>Protected Resources</realm-name> <form-login-config> <form-login-page>/public/login/</form-login-page> <form-error-page>/public/login-failed/</form-error-page> </form-login-config> </login-config> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 <security-constraint> <web-resource-collection> <web-resource-name> ADMIN Resources </web-resource-name> <url-pattern> /setup/* </url-pattern> <url-pattern> /users/* </url-pattern> </web-resource-collection> <auth-constraint> <role-name> ADMIN </role-name> </auth-constraint> </security-constraint> <security-constraint> <web-resource-collection> <web-resource-name> VIEWER Resources </web-resource-name> <url-pattern> /* </url-pattern> </web-resource-collection> <auth-constraint> <role-name> ADMIN </role-name> <role-name> VIEWER </role-name> </auth-constraint> </security-constraint> <security-constraint> <web-resource-collection> <web-resource-name> Unprotected Resources </web-resource-name> <url-pattern> /images/* </url-pattern> <url-pattern> /public/* </url-pattern> </web-resource-collection> </security-constraint> <login-config> <auth-method> FORM </auth-method> <realm-name> Protected Resources </realm-name> <form-login-config> <form-login-page> /public/login/ </form-login-page> <form-error-page> /public/login-failed/ </form-error-page> </form-login-config> </login-config>

Vendor-Specific Settings

The above web.xml file contains all the cross-vendor parts of the standard approach. However, it doesn’t say anything about “how” authentication actually takes place. Will users to be authenticated against LDAP, a database, or web service? Will it use a custom class or one provided by the container?

For this info, we have to turn to each vendor’s unique security setup. Since StackHunter started on Tomcat, it required the following steps:

Modify Tomcat’s server.xml file to a) define the security realm and b) tell Tomcat about the custom user and role classes. Create a JAAS login configuration file to specify the custom authentication provider class. Add a JVM param to tell Tomcat where to find the above JAAS login configuration file.

(You can read more about how to create a custom login module here: JAAS authentication in Tomcat example.)

Now multiply those steps by the 10+ servlet containers on the market. Keeping up-to-date with all the servers’ security configs is not where we want to be spending our time.

The Spring Security, Portable Approach

The good news is that there are alternatives to the Java web security standard. A few of the options are:

SecurityFilter – http://securityfilter.sourceforge.net/

Apache Shiro – http://shiro.apache.org/

Spring Security – http://projects.spring.io/spring-security/

I went with Spring Security since it seems to have a lot of developer support and isn’t any more difficult than the others to set up. Plus I was already using Spring for data access and other things.

Download Spring Jars

On a side note: if you’re not using Maven to build your project, you can download the latest Spring Framework and Spring Security jars at:

Programming in XML

While the Spring Framework is a great tool for building Java software, its philosophy of configuring beans outside of the code sometimes makes it too XML heavy my taste. Let’s face it, configuration in large quantities is just another form of programming. I also like being able to reason about the classes I’m using (and the flow of control) without having to look outside the class files.

With all that in mind, Spring Security still comes through with flying colors. Even the configuration is just the right amount XML and indirection.

Here are the three steps you’ll need to replace container-managed security with Spring Security.

Step 1 – Create Your Custom AuthenticationProvider

Create a subclass of org.springframework.security.authentication.AuthenticationProvider . The authenticate method should either:

Return null if it doesn’t support authentication on the supplied object. Return an instance of org.springframework.security.core.Authentication if authentication is successful. Throw org.springframework.security.authentication.BadCredentialsException , DisabledException , or LockedException if authentication is unsuccessful.

If you’re currently using a JAAS login module, it should be pretty easy to migrate it to spring way.

In my case, the user service handled all the authentication details — like account locking, password hashing, etc. — which made it pretty easy to plug into the new Spring class.

public class DashboardAuthenticationProvider implements AuthenticationProvider { @Override public Authentication authenticate(Authentication auth) throws AuthenticationException { String username = (String) auth.getPrincipal(); String password = (String) auth.getCredentials(); UserLoginResponse loginResponse = Services.getUserService().login(username, password); if (!loginResponse.isSuccess()) { throw new BadCredentialsException("invalid user or password"); } String role = "ROLE_" + loginResponse.getUser().getRole().toString(); List<SimpleGrantedAuthority> authorities = Arrays.asList(new SimpleGrantedAuthority(role)); return new UsernamePasswordAuthenticationToken(username, password, authorities); } @Override public boolean supports(Class<?> type) { return Authentication.class.isAssignableFrom(type); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public class DashboardAuthenticationProvider implements AuthenticationProvider { @Override public Authentication authenticate ( Authentication auth ) throws AuthenticationException { String username = ( String ) auth . getPrincipal ( ) ; String password = ( String ) auth . getCredentials ( ) ; UserLoginResponse loginResponse = Services . getUserService ( ) . login ( username , password ) ; if ( ! loginResponse . isSuccess ( ) ) { throw new BadCredentialsException ( "invalid user or password" ) ; } String role = "ROLE_" + loginResponse . getUser ( ) . getRole ( ) . toString ( ) ; List <SimpleGrantedAuthority> authorities = Arrays . asList ( new SimpleGrantedAuthority ( role ) ) ; return new UsernamePasswordAuthenticationToken ( username , password , authorities ) ; } @Override public boolean supports ( Class < ? > type ) { return Authentication . class . isAssignableFrom ( type ) ; } }

Step 2 – Create Your Security Configuration

Create a new file named /WEB-INF/spring-security.xml similar to the one below.

The first set of tags (with the security="none" attribute) identify all the public, unsecured resource paths. The intercept-url element inside http , associates resource paths to their required user roles. And the form-login element sets the authentication type to form and identifies the page with the login form along with other paths.

All the specified paths are relative to the context root and the pattern attribute use ANT notation — where double asterisks (/**) means “include all sub-folders”.

The last few lines of this file sets the authentication provider to the class defined above.

<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-4.0.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.2.xsd"> <http pattern="/css/**" security="none"/> <http pattern="/js/**" security="none"/> <http pattern="/images/**" security="none"/> <http pattern="/public/**" security="none"/> <http pattern="/api/**" security="none"/> <http pattern="/error/**" security="none"/> <http pattern="/favicon.ico" security="none"/> <http auto-config="true"> <!-- Admin Resources --> <intercept-url pattern="/setup/**" access="ROLE_ADMIN" /> <intercept-url pattern="/users/**" access="ROLE_ADMIN" /> <intercept-url pattern="/events/**" access="ROLE_ADMIN" /> <!-- Developer Resources --> <intercept-url pattern="/apps/**" access="ROLE_ADMIN, ROLE_DEVELOPER" /> <!-- Viewer Resources --> <intercept-url pattern="/**" access="ROLE_ADMIN, ROLE_DEVELOPER, ROLE_VIEWER" /> <form-login login-page="/public/login/" default-target-url="/" authentication-failure-url="/public/login-failed/" /> <logout logout-success-url="/" /> </http> <authentication-manager> <authentication-provider ref='dashboardAuthenticationProvider' /> </authentication-manager> <beans:bean id="dashboardAuthenticationProvider" class="com.stackhunter.dashboard.security.DashboardAuthenticationProvider" /> </beans:beans> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 < 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-4.0.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.2.xsd" > < http pattern = "/css/**" security = "none" / > < http pattern = "/js/**" security = "none" / > < http pattern = "/images/**" security = "none" / > < http pattern = "/public/**" security = "none" / > < http pattern = "/api/**" security = "none" / > < http pattern = "/error/**" security = "none" / > < http pattern = "/favicon.ico" security = "none" / > < http auto - config = "true" > < ! -- Admin Resources -- > < intercept - url pattern = "/setup/**" access = "ROLE_ADMIN" / > < intercept - url pattern = "/users/**" access = "ROLE_ADMIN" / > < intercept - url pattern = "/events/**" access = "ROLE_ADMIN" / > < ! -- Developer Resources -- > < intercept - url pattern = "/apps/**" access = "ROLE_ADMIN, ROLE_DEVELOPER" / > < ! -- Viewer Resources -- > < intercept - url pattern = "/**" access = "ROLE_ADMIN, ROLE_DEVELOPER, ROLE_VIEWER" / > < form - login login - page = "/public/login/" default - target - url = "/" authentication - failure - url = "/public/login-failed/" / > < logout logout - success - url = "/" / > < / http > < authentication - manager > < authentication - provider ref = 'dashboardAuthenticationProvider' / > < / authentication - manager > < beans : bean id = "dashboardAuthenticationProvider" class = "com.stackhunter.dashboard.security.DashboardAuthenticationProvider" / > < / beans : beans >

Step 3 – Add The web.xml Hooks

The final step is to update the web.xml to:

Remove all the old security-constraint and login-config tags. Load the security settings from the file above. Use the Spring servlet filter to intercept requests and handle authentication.

Make sure this servlet filter is placed above any other filters in your web.xml that expects authentication to have already occurred.

<!-- Loads Spring Security config file --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring-security.xml</param-value> </context-param> <!-- Spring Security --> <filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <!-- Loads Spring Security config file --> <listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener> <context-param> <param-name> contextConfigLocation </param-name> <param-value> /WEB-INF/spring-security.xml </param-value> </context-param> <!-- Spring Security --> <filter> <filter-name> springSecurityFilterChain </filter-name> <filter-class> org.springframework.web.filter.DelegatingFilterProxy </filter-class> </filter> <filter-mapping> <filter-name> springSecurityFilterChain </filter-name> <url-pattern> /* </url-pattern> </filter-mapping>

Just Package and Deploy, and Deploy

That’s all there is to it. Just package the spring-security.xml file, your custom authenticator, and the Spring jars into your war and deploy.

Spring Security personally saved me lots of time documenting and maintaining the installation steps for each servlet container. More importantly, it reduced my web app’s installation steps and removed a potential source of support issues. I hope it does as well for you.

