Customizing Spring Security

July 20, 2012 by Vinicius Isola

Spring Security is a very powerful and customizable security framework for JEE applications. This post will show how to integrate it seamlessly with your application setting up a custom login and logout.

You’ll also learn how to add a custom filter with very specific behavior and how to integrate with Spring Data so that your users can be loaded from a repository and avoid all the hassle of dealing with JDBC or Hibernate.

The code for this example can be found in my GitHub Repository. For this post, there are three projects involved: root, spring-security-first-user and user-model. The root project contains dependency management and the user-model can be reused for any other project involving spring security.

Spring Security Basics

Spring Security uses three main interfaces to define the [UserDetails](http://static.springsource.org/spring-security/site/docs/3.1.x/apidocs/org/springframework/security/core/userdetails/UserDetails.html, the GrantedAuthorities and how a user can be loaded from the UserDetailsService. When you create your application, the only thing you need to do is map whatever user and authority control that you have to those that Spring Security has. And because they are interfaces, you just need to implement them. It doesn’t matter where your data comes from, as long as you follow these rules.

The basic architecture for a web application that uses Spring Security is the following:

spring security architecture

A filter stack is created to handle all access to the application. An AuthenticationManager loads the user and his authorities. A HTTP session is always created and even anonymous users can be handled because a session is created independently of the user credentials.

A SecurityContext stores the security information for the specific user and it is stored in the ThreadLocal (though it can be handled differently for non-JEE applications). With the security context ready, filters in the stack will decide what controller/page that user has access to and if an authentication is required, it will redirect to the login page. If the user is already logged in but doesn’t have the correct credentials, a 403 will be issued.

Spring Data Integration

To integrate Spring Data with Spring Security, all you need is to make sure that your repository also extends the UserDetailsService interface, which means that you’ll need to implement one extra method. This method is the loadUserByUsername, that maps directly to the findOne with one difference: it must throw an exception if the user is not found (instead of returning null). The following is the implementation in the custom repository:

public User loadUserByUsername(String username) throws UsernameNotFoundException {
	User user = entityManager.find(User.class, username);
	if (user == null) throw new UsernameNotFoundException("Username not found: " + username);
	return  user;
}

All the code that you need to create users and authorities is in the user-model project. In this project there are two repositories but only one is used in this example: UserRepository. The custom implementation handles password hashing and also changing password. The save method doesn’t change the password, to change it, you need to go through the changePassword method and provide the old one. There is a bunch of unit tests implemented to help understand the expected behavior.

The user-model project also contains a /META-INF/spring/services.xml that configures Spring Data JPA to initialize the repositories correctly. To make it work you just need to import that file in your spring configuration and add all the Hibernate/JDBC configuration. An example of the extra configuration can be found in the test-context.xml, that’s the configuration file used for the unit testing.

Glueing things together

The spring-security-first-user project contains the full/working application. It is a simple web application that contains three Spring configuration files:

web.xml

The web.xml configure Spring context listener, Spring MVC dispatcher servlet and also the delegating filter. The only important detail in this file is the path to the configuration files, that are customized for both, the context listener and the dispatcher servlet.

Configuring the AuthenticationManager

The authentication manager is the part of Spring Security that will load your user and check his password. To do that, you need to tell where the data will come from (it can come from many different places). You also need to say what password hashing mechanism you’ll be using.

In this example, the data will come from the UserRepository and SHA-1 will be used to hash the password. It’s also necessary to configure what it’s going to be used as the salt for the hashing. The following is the configuration mentioned.

<!-- Using a SHA-1 password encoder -->
<beans:bean id="passwordEncoder" class="org.springframework.security.authentication.encoding.ShaPasswordEncoder" />

...

<authentication-manager>
	<!-- Using the customized user repository as the authentication manager. -->
	<authentication-provider user-service-ref="userRepository">
		<!-- Password encoder is configured above. -->
		<password-encoder ref="passwordEncoder">
			<!--
				It will use the username as the salt, this is important because its the behavior in
				the save method of the customized user repository. If not setup correctly, it will
				never match the password.
			-->
			<salt-source user-property="username" />
		</password-encoder>
	</authentication-provider>
</authentication-manager>

Two things worth mentioning here. First is that the username is being used as the salt because that’s what’s implemented in the save method of the UserRepositoryImpl. Second, the reference for the UserRepository is created by Spring Data JPA using the default naming convention. That happens when you add the repository declaration, like the following (from the services.xml in the user-model project):

<jpa:repositories base-package="com.bearprogrammer.user" />

Custom login and logout pages

Custom login and logout pages is a very common scenario because normally a web site/application already have a defined template that needs to be followed. Spring Security provides a default login page but allows you to change it to point to whatever page/action you want.

For this example login.do will be used as the login page and logout.do as the logout page. The following is the configuration for that.

<!--
	auto-config="true": auto configure as far as possible
	use-expressions="true": use expression to determine who can access what
-->
<http auto-config="true" use-expressions="true">
	...

	<!--
		Configure the custom login page.
		login-page="/login.do": where the user will be redirected when
		a login page needs to be rendered
	-->
	<form-login login-page="/login.do" />

	<!--
		Configure the custom logout page.
		invalidate-session="true": invalidate user session after logout
		logout-success-url="/logout.do": where the user will be redirected to
		after a successful logout
	-->
	<logout invalidate-session="true" logout-success-url="/logout.do" />
</http>

The first user problem

One recurrent problem every time you start a new application with Spring Security is that normally, you don’t have a user in the database so you need to create a user using some special environment (disabled security for example) and then copy that for each new environment you install your application. That’s needed because most of the time it’s hard to reproduce the exact hashing mechanism used in the application security.

Recently, working with content management applications like Wordpress and Joomla I’ve seen how easy it is to get those up and running. Just start it and when you first get in you get a huge form with all the information the application needs to set it up, including the administrator’s user and password. So I decided that it would be nice to have a way to make that same procedure in Spring Security.

To do that, I added one filter to the Spring Security’s stack that to check how many users exist in the database. That value is stored when the filter is initialized.

After it is initialized and contains the count of users in the database, the filter needs to check that number and if it’s equal to zero (no users) redirect the user to the create first user action. It also needs to check if it is not already in that action to avoid a recursive loop. If there is any user in the database, then it becomes a pass-through filter.

The following is the code in the doFilter of the filter:

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
	HttpServletRequest httpRequest = (HttpServletRequest) request;

	// If no user in the database, add first user authentication
	if (userCount == 0) {
		SecurityContextHolder.getContext().setAuthentication(FIRST_USER_AUTHENTICATION);
		// If going into first user create or save page, let it pass
		if (httpRequest.getRequestURL().toString().endsWith(FIRST_USER_CREATE_URL) ||
				httpRequest.getRequestURL().toString().endsWith(FIRST_USER_SAVE_URL)) {
			chain.doFilter(request, response);

			// If just saved first user, update user count
			if (httpRequest.getRequestURL().toString().endsWith(FIRST_USER_SAVE_URL)) {
				updateUserCount();

				// Clear security context
				SecurityContextHolder.getContext().setAuthentication(null);
			}

		// Otherwise, redirect to first user create page
		} else {
			HttpServletResponse httpResponse = (HttpServletResponse) response;
			httpResponse.sendRedirect(httpRequest.getContextPath() + FIRST_USER_CREATE_URL);
		}

	} else { // If there's already a user in the database
		chain.doFilter(request, response);
	}
}

If it’s trying to create the first user, a temporary authentication with the correct user and authority needs to be created, as if it was a real user logged in. That Authentication is implemented in the FirstUserAuthentication and is added to the security context in the following code:

SecurityContextHolder.getContext().setAuthentication(FIRST_USER_AUTHENTICATION);

The Filter Stack

For this filter to work correctly, we need to add it to the filter stack. The first thing we need to define is where in the stack is the correct place to put your custom filter. For this filter a security context is needed so it needs to come after the SECURITY_CONTEXT_FILTER, where the context is created or restored (from a session). But it needs to come before Spring Security tries to authenticate the user (we know that there is no user in the database), so it needs to come before FORM_LOGIN_FILTER. To do that, you just need to add the custom-filter tag using the before attribute:

<!-- Custom filter bean -->
<beans:bean id="createFirstUserFilter"
	class="com.bearprogrammer.spring.security.firstuser.filter.CreateFirstUserFilter"
	init-method="start" />

<http auto-config="true" use-expressions="true">
	...
	<!--
		This is where the custom filter is being added. It's being added
		just before authentication happens, that way, we already have a
		valid security context but not tried to authenticate yet.
	-->		
	<custom-filter ref="createFirstUserFilter" before="FORM_LOGIN_FILTER"  />
	...
</http>

If you want more information about the custom filter namespace, you can go here. And detailed information about each one of the core filters, here.

One last thing

Don’t forget to map the correct access to each resource. Login and logout pages need to be accessed by people that aren’t logged in. Everything related to the first user creation, needs the correct authority. And last, but definitely not least, all pages need an authenticated user. The following is that configuration:

<http auto-config="true" use-expressions="true">
	<!-- Pages that everyone can access -->
	<intercept-url pattern="/login.do" access="isAnonymous()" />
	<intercept-url pattern="/logout.do" access="isAnonymous()" />

	<!-- First user creation -->
	<intercept-url pattern="/firstUser/**" access="hasRole('firstuser')" />

	<!-- All other pages needs an authenticated user -->
	<intercept-url pattern="/**" access="isFullyAuthenticated()" />
	...
</http>