Table of Contents
1. Introduction to the Spring Security Tutorial: Form Login
Update: I’ve added a new post which explains how to configure the Form Login using Java based configuration. The new post focuses purely on explaining how the XML configuration below can be replaced with Java configuration, so it’s useful to read both posts side-by-side. Here is a link to the new post: Spring Security Tutorial: Form Login (Java Config)
This post is a Spring Security form login tutorial. The post will show you how to configure form based login using the following methods to store credentials:
- Hardcoded with plain text
- Hardcoded using SHA1 encoded passwords
- JDBC based
- MongoDB based (these principles could be applied to any database)
In addition to the topics above, the post will also show you how to:
- Configure a custom Login page (Spring MVC)
- Configure a custom 403 page (Spring MVC)
- Configure two-tier security based on regular users and administrators
- Use annotation based authorization directly on the Java classes (or methods)
- Use HTTP basic as your authentication mechanism
Everything explained in the post can be found in the source code available for download at the bottom of the post. The archive contains a neatly packaged Maven project which you can run locally using “mvn clean install tomcat7:run-war-only”. Obviously Maven contains all the dependencies required for the tutorial and, in addition, I’ve bundled in the administrator authorization code, how to configure static content, and more as a bonus!
2. Ground Work: Configuring Your Web.xml
I’ve broken this section down into two. The first part explains the different segments of the web.xml and the second part gives you the full web.xml file used to configure the Java web application. So let’s start with the explanation:
- Spring Config Files: tells Spring where to find the relevant application context configuration files. I’ve separated the MVC / Application code in one configuration file and the security related configuration in a separate file.
- Spring Listeners: these listeners are used to load the application’s configuration. The ContextLoaderListener is your bread and butter Spring loader and the RequestContextFilter is in there as Spring Security’s DelegatingFilterProxy requires it in order to get access to the context.
- MVC Filter: this is the standard DispatcherServlet required for Spring MVC. Rather than loading it’s own context, I’ve simply shared the Root WebApplicationContext with it. The MVC filter is then mapped onto the login URL and the root URL.
- Security Filter: this is the filter which takes care of making all the Spring Security magic happen. The filter is mapped to the root path of the application (i.e. /*).
- JSPs: this configures our 403 error JSP. I’ve decided not to use Spring MVC for this as it’s a very simple JSP page with no bells and whistles.
- Error Page: here the 403 JSP is mapped against the 403 error code.
The full web.xml configuration:
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0" metadata-complete="true"> <!-- Spring Config Files --> <context-param> <param-name>contextConfigLocation</param-name> <param-value> classpath:mvc-applicationContext.xml classpath:security-applicationContext.xml </param-value> </context-param> <!-- Spring Listeners --> <listener> <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class> </listener> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- MVC Filter --> <servlet> <servlet-name>mvcDispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextAttribute</param-name> <param-value>org.springframework.web.context.WebApplicationContext.ROOT</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>mvcDispatcher</servlet-name> <url-pattern>/login</url-pattern> <url-pattern>/</url-pattern> </servlet-mapping> <!-- Security Filter --> <filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <init-param> <param-name>contextAttribute</param-name> <param-value>org.springframework.web.context.WebApplicationContext.ROOT</param-value> </init-param> </filter> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- JSPs --> <servlet> <servlet-name>403Jsp</servlet-name> <jsp-file>/403.jsp</jsp-file> </servlet> <servlet-mapping> <servlet-name>403Jsp</servlet-name> <url-pattern>/403</url-pattern> </servlet-mapping> <!-- The error page --> <error-page> <error-code>403</error-code> <location>/403</location> </error-page> </web-app>
With that out of the way we can move on to the next step!
3. Configuring Spring MVC: Controllers and JSPs
This section explains how to configure the Spring MVC controllers you’ll need. I’ll begin with the Controllers and their respective JSPs, and finally the full MVC Application Context.
3.1 Login Controller and JSP
The first thing we need in order to use a custom Login page is setup a controller for the Login page. The controller does very little, however, you can easily switch out the String response for a ModelAndView and pass data back to the JSP page if you like.
package org.codehustler.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @Controller public class LoginController { @RequestMapping( value = "/login", method = RequestMethod.GET ) public String login() { return "login"; } }
Below is the JSP page, but first I’ll cover some points of interest on the page:
- The FORM tag uses a custom URL for processing the login. This is configured in our Spring Security Application Context file. This makes it less obvious that you are using Spring Security behind the scenes. However, if you want to use the default URL you can use <c:url value=’j_spring_security_check’ /> instead.
- The initial part of the FORM gives an example of how to display error messages based on the type of incident.
- Both the username and password inputs use custom tag names (i.e. “username” and “password”) instead of the default j_username and j_password fields. Again, this makes it less obvious that you are using Spring Security.
For the sake of brevity, I’ve removed the CSS stylesheets and the Favicon from the page. If you download the source code you can see how they are configured in terms of security, MVC and web.xml.
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ page language="java" import="javax.servlet.jsp.PageContext" %> <!DOCTYPE html> <html> <head> <title>Spring Security Form Login Tutorial</title> </head> <body> <H1>Welcome to the Spring Security Form Login Tutorial!</H1> <form id="form" action="<c:url value='/login.do'/>" method="POST"> <c:if test="${not empty param.err}"> <div><c:out value="${SPRING_SECURITY_LAST_EXCEPTION.message}"/></div> </c:if> <c:if test="${not empty param.out}"> <div>You've logged out successfully.</div> </c:if> <c:if test="${not empty param.time}"> <div>You've been logged out due to inactivity.</div> </c:if> Username:<br> <input type="text" name="username" value=""/><br><br> Password:<br> <input type="password" name="password" value=""/> <input value="Login" name="submit" type="submit"/> </form> </body> </html>
So we’ve got a login page, now we need a page to take the user to once they’ve logged in.
3.2 Index Controller and JSP
Same as with the login page, we need a Controller and JSP for the index page. Again, you can easily swap out the String return for a ModelAndView object and pass data back to the index page. For example, you could add the Principal class as an arguement to the index method and call principal.getName() to retrieve the logged in user’s name.
You will notice at the top of the controller there is an @PreAuthorize annotation which provides authorization to access the controller. I like putting this annotation into my access points as a catch-all. Sometimes the Spring Security configuration can get quite complex and if you mess up the order of your intercept-url tags (we will cover this below) you could end up with people in places they shouldn’t be.
package org.codehustler.controller; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @Controller @PreAuthorize("hasRole('ROLE_USER')") public class IndexController { @RequestMapping( value = "/", method = RequestMethod.GET ) public String index() { return "index"; } }
Next is the index.jsp file. Nothing really special here other than the Logout URL. Similarly to the Login processing URL, I’ve used a custom logout URL. If you want to use the default one you can use <c:url value=’j_spring_security_logout’ />
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <!DOCTYPE html> <html> <head> <title>Spring Security Form Login Tutorial</title> </head> <body> <H1>Welcome to the User screen!</H1> <p><a href="<c:url value='/logout'/>">Logout</a></p> </body> </html>
We’re all done with Controllers and JSPs. Let’s put it together now.
3.3 Tying it All Together with the MVC Application Context
The last part of this section brings everything together using the mvc-applicationContext.xml file. Here is what’s happening below:
- context: annotation-config allows classes to be annotated rather than declared explicitly in the Application Context file.
- mvc: annotation-driven same principle as the annotation-config above but specifically for Spring MVC.
- InternalResourceViewResolver helps Spring MVC figure out where the views are and what extension they use.
- context: component-scan tell Spring where to look for our components and services.
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd"> <!-- Spring Context Configuration --> <context:annotation-config /> <!-- MVC Configuration --> <mvc:annotation-driven/> <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="" /> <property name="suffix" value=".jsp" /> </bean> <!-- MVC Controllers - Using annotations so we can simply scan the components --> <context:component-scan base-package="org.codehustler" /> </beans>
With this we’ve got all our MVC configuration in check and we can move onto the good stuff, the Spring Security configuration.
4. Spring Security: Form Login and Authentication Providers
This section is going to be broken down into 5 parts. The first part covers the general configuration for the login form security and the subsequent 4 parts explain how to setup the different authentication providers.
4.1 Form Login Configuration
This part contains the core of the login form configuration. First I’ll explain what is happening in the security-applicationContext.xml:
- global-method-security this is used to enable the @PreAuthorize tag. However, this setting enables a whole host of other goodies which I’ve not covered in this post. Here’s a link to the documentation, look for Security Namespace Configuration
- http this is the container element for the HTTP security configuration:
- session-management this adds the SessionManagementFilter to Spring Security’s chain of filters and allows you to manage session security (below I’ve used this setting to allow only 1 concurrent session per user).
- form-login: here we configure our login form:
- login-page the URL where the login page can be found.
- login-processing-url the URL used to process the login request. See the section above on the Login Controller for more information.
- default-target-url the URL where the user goes to if they provide valid credentials.
- authentication-failure-url the URL where the user goes to if they provide invalid credentials.
- username-parameter the custom HTML username property defined in the login.jsp page.
- password-parameter the custom HTML password property defined in the login.jsp page.
- logout this is the logout processing filter, which pretty much sums it up.
- intercept-url this is where we specify the security for our application’s URLs. Please be aware that the order in which these are declared is extremely important as they will be executed in the order they are found. So if the first entry was “/**”, the user would not be able to access the login page. Thanks to the use-expressions attribute of the http tag we can use expressions to define the access attribute. Here is a list of available expressions. An interesting attribute here is the requires-channel, this forces the URLs to use HTTPS rather than HTTP. The POM file in the source code download automatically takes care of configuring Tomcat so that you can see this behaviour in action.
- authentication-manager this one comes straight from the Spring documentation: registers the AuthenticationManager instance and allows its list of AuthenticationProviders to be defined.
As there are a lot of tag attributes to cover, I’ve only explained the most important ones. However, if you download the source code and open the security-applicationContext.xml file in an IDE like Eclipse, the schema files will load and you’ll be able to get a description of each attribute by simply hovering over them.
<?xml version="1.0" encoding="UTF-8" ?> <b:beans xmlns:b="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/security" xmlns:oauth="http://www.springframework.org/schema/security/oauth" xsi:schemaLocation="http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd http://www.springframework.org/schema/security/oauth http://www.springframework.org/schema/security/spring-security-oauth.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd"> <!-- Global Security Settings --> <global-method-security pre-post-annotations="enabled" /> <!-- Form and Security Access Configuration --> <http use-expressions="true" access-denied-page="/403" disable-url-rewriting="true"> <session-management invalid-session-url="/login?time=1"> <concurrency-control max-sessions="1" expired-url="/login?time=1" /> </session-management> <form-login login-page="/login" login-processing-url="/login.do" default-target-url="/" always-use-default-target="true" authentication-failure-url="/login?err=1" username-parameter="username" password-parameter="password" /> <logout logout-url="/logout" logout-success-url="/login?out=1" delete-cookies="JSESSIONID" invalidate-session="true" /> <intercept-url requires-channel="https" pattern="/login*" access="permitAll()" /> <intercept-url requires-channel="https" pattern="/**" access="hasRole('ROLE_USER')" /> </http> <!-- Authentication Providers for Form Login --> <authentication-manager alias="authenticationManager"> <!-- ADD THE AUTHENTICATION PROVIDERS HERE --> </authentication-manager> <!-- ADD THE DATASOURCES HERE --> </b:beans>
On a side note, if you want to use basic authentication and have the browser display a dialog box where the user can add their credentials, simply use the following configuration for your HTTP tag:
<http> <intercept-url requires-channel="https" pattern="/login*" access="permitAll()" /> <intercept-url requires-channel="https" pattern="/**" access="hasRole('ROLE_USER')" /> <http-basic /> </http>
Ok, so everything is secured. Let’s take a look at how to supply the credentials using different authentication providers. Note: the XML provided below should be inserted into the XML above where it says: “Add the Authentication Providers here” or “Add the datasources here”.
4.2 Authentication Provider: Keeping it Simple
This part explains how to configure the simplest authentication provider possible. The authorities attribute is where we define which roles the user has. These are used above in our intercept-urls to determine if the user has the appropriate roles to access a resource. The rest of the XML below is pretty self-explanatory. If in doubt, leave a comment below.
<authentication-provider> <user-service> <user name="user" password="user" authorities="ROLE_USER" /> </user-service> </authentication-provider>
The configuration above isn’t great and it’s definetely not very secure as we have a clear text password just sitting there. If you really need something this simple, then I suggest you implement the next part instead as it’ll be a little safer!
4.3 Authentication Provider: Keeping it Simple with SHA1
This part extends the previous part by SHA1 encoding the passwords. Here is a good site which provides SHA1 encoding online. Once again, the XML below is pretty self-explanatory. If in doubt, leave a comment below.
<authentication-provider> <password-encoder hash="sha" /> <user-service> <user name="user" password="12dea96fec20593566ab75692c9949596833adc9" authorities="ROLE_USER" /> </user-service> </authentication-provider>
Great! Now you know how to configure a simple login authentication provider. Realistically though, most people will have their credentials stored in some form of database. Read on if that’s your case.
4.4 Authentication Provider: JDBC connector
A common scenario is to store your credentials in a database. This section explains how to use the standard, out-of-the-box configuration for accessing your database using JDBC.
If you fancy using the Spring Security’s default database schema, then the authentication provider below will do.
<authentication-provider> <jdbc-user-service data-source-ref="dataSource" /> <password-encoder hash="sha" /> </authentication-provider>
However, if you want to use your own database schema, then you can modify the provider above to use custom SQL queries which work with your schema.
<authentication-provider> <jdbc-user-service data-source-ref="dataSource" users-by-username-query="select username,password from users where username=?" authorities-by-username-query="select u.username, r.authority from users u, roles r where u.userid = r.userid and u.username =?" /> </authentication-provider>
In both samples above we refer to a dataSource bean. Add and configure the code below in order to instantiate your dataSource bean.
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/codehustler" /> <property name="username" value="root" /> <property name="password" value="password" /> </bean>
At this point most people are pretty happy, but there are still those thinking: “Well, that’s convenient isn’t it! Pity I use database XYZ!”. Fair enough. Let’s take a look at what we can do to create an authentication provider that can work with pretty much any database we choose. In this case I’ve chosen MongoDB as an example because I quite like it =)
4.5 Authentication Provider: MongoDB
The first thing we must do is configure our authentication provider. You’ll notice that there isn’t really a big difference between this and the previous providers.
<authentication-provider user-service-ref="loginService"> <password-encoder hash="sha" /> </authentication-provider>
The important bit here is the user-service-ref which points to our custom UserDetailsService called LoginService. This is where we provide Spring Security with the UserDetails it requires in order to perform authentication.
We’ve previously enabled context scanning and annotation driven configuration, so we don’t need to explicitly add the LoginService to the application context. This is what the LoginService looks like:
package org.codehustler.security; import java.util.Arrays; import java.util.List; import org.codehustler.domain.UserModel; import org.codehustler.repository.UserRepositoryImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; @Service public class LoginService implements UserDetailsService { private UserRepositoryImpl repository; @Autowired public LoginService( UserRepositoryImpl repository ) { this.repository = repository; } public UserDetails loadUserByUsername( String username ) throws UsernameNotFoundException { UserModel user = repository.findUserByUsername( username ); if( user == null ) throw new UsernameNotFoundException( "Oops!" ); List<SimpleGrantedAuthority> authorities = Arrays.asList( new SimpleGrantedAuthority( user.getRole() ) ); return new User( user.getUsername(), user.getSHA1Password(), authorities ); } }
The class is used by Spring Security to load the user’s details. LoginService will query MongoDB and use the user’s information to populate Spring Security’s User object.
The LoginService depends on our UserModel which contains the user’s credentials. You will notice some strange annotations on the class and they’re there because I used Morphia to simplify MongoDB related interactions. If you’re using MongoDB and don’t know about Morphia, then it’s definetely worth exploring. Here’s the UserModel:
package org.codehustler.domain; import org.apache.commons.codec.digest.DigestUtils; import org.bson.types.ObjectId; import com.github.jmkgreen.morphia.annotations.Entity; import com.github.jmkgreen.morphia.annotations.Id; import com.github.jmkgreen.morphia.annotations.Indexed; @Entity( value="users", noClassnameStored=true, concern="SAFE" ) public class UserModel { @Id private ObjectId id; @Indexed(unique=true) private String username; private String password; private String role; protected UserModel() {} public UserModel( String username, String password, String role ) { this.username = username; this.password = DigestUtils.sha1Hex( password ); this.role = role; } public ObjectId getId() { return id; } public String getUsername() { return username; } public String getSHA1Password() { return password; } public String getRole() { return role; } }
The last piece required in order for the LoginService to work is the UserRepositoryImpl. Simply put this provides access to the MongoDB collection which has our users in it.
package org.codehustler.repository; import org.bson.types.ObjectId; import org.codehustler.domain.UserModel; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import com.github.jmkgreen.morphia.AdvancedDatastore; import com.github.jmkgreen.morphia.dao.BasicDAO; import com.github.jmkgreen.morphia.query.Query; @Component public class UserRepositoryImpl extends BasicDAO<UserModel, ObjectId> { @Autowired public UserRepositoryImpl( AdvancedDatastore datastore ) { super( datastore ); } public UserModel findUserByUsername( String username ) { Query<UserModel> query = createQuery(); query.field( "username" ).equal( username ); return find( query ).get(); } }
I’ve kept this class as simple as possible but it would be quite easy to add an extra controller, with a form, that allows you to add new users to MongoDB.
Now, as our final step and in order to get this to work we need to configure MongoDB and Morphia. The configuration below looks for a local instance of MongoDB (127.0.0.1) and uses the database “codehustler”. Add the following lines to the bottom of your security-applicationContext.xml:
<bean id="mongo" scope="singleton" class="com.mongodb.MongoClient"> <constructor-arg index="0"> <bean id="repOne" class="com.mongodb.ServerAddress"> <constructor-arg index="0" type="java.lang.String" value="127.0.0.1" /> </bean> </constructor-arg> </bean> <bean id="morphia" scope="singleton" class="com.github.jmkgreen.morphia.Morphia" /> <bean id="datastore" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean"> <property name="targetObject" ref="morphia"/> <property name="targetMethod" value="createAdvancedDatastore"/> <property name="arguments"> <list> <ref bean="mongo"/> <value>codehustler</value> </list> </property> </bean>
Using the UserModel it’s easy to figure out what the data model is. However, if you’re looking for some already digested information, then take a look at the source code, I’ve added some sample data there.
5. Conclusion
That’s all folks! Hopefully this will help anyone looking to secure their application using Spring Security’s form based login. As always, if you have any questions please leave a comment and I’ll try to answer as soon as I have a moment.
The sample code used in this post (and all other posts with code samples) is available by subscribing below. The downloaded source code usually comes with a few goodies which aren’t in the post!
The source code is all configured using Maven, so you can be up and running by simply typing “mvn clean install tomcat7:run-war-only”. This will spin up the demo on http://localhost:8080/.
- Spring Security Tutorial: Form Login Java Configuration - September 27, 2014
- Spring Security Tutorial: 3-Legged OAuth 1.0 - June 9, 2014
- Spring Security Tutorial: 2-Legged OAuth 1.0 - June 3, 2014