Welcome to Spring Security Example using UserDetailsService. In the last post we learned how to use Spring Security in Web Application. Today we will look into how we can integrate Spring Security in Spring MVC Projects for authentication purposes.
Spring Security Example
Integrating Spring Security with Spring MVC Framework is very easy, because we already have Spring Beans configuration file. All we need is to create spring security authentication related changes to get it working. Today we will look into how we can implement authentication in Spring MVC application using in-memory, UserDetailsService
DAO implementation and JDBC based authentication.
First create a simple Spring MVC project in the Spring Tool Suite, that will give us the base spring MVC application to build our Spring security example application. Once we will be done with all the changes, our application will look like below image.
Let’s look into each of the components of our Spring security example project.
Spring Security Maven Dependencies
Our final pom.xml file looks like below.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.journaldev.spring</groupId>
<artifactId>SpringMVCSecurity</artifactId>
<name>SpringMVCSecurity</name>
<packaging>war</packaging>
<version>1.0.0-BUILD-SNAPSHOT</version>
<properties>
<java-version>1.6</java-version>
<org.springframework-version>4.0.2.RELEASE</org.springframework-version>
<org.aspectj-version>1.7.4</org.aspectj-version>
<org.slf4j-version>1.7.5</org.slf4j-version>
</properties>
<dependencies>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${org.springframework-version}</version>
<exclusions>
<!-- Exclude Commons Logging in favor of SLF4j -->
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>3.2.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>3.2.3.RELEASE</version>
</dependency>
<!-- AspectJ -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${org.aspectj-version}</version>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${org.slf4j-version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${org.slf4j-version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${org.slf4j-version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.15</version>
<exclusions>
<exclusion>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
</exclusion>
<exclusion>
<groupId>javax.jms</groupId>
<artifactId>jms</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jdmk</groupId>
<artifactId>jmxtools</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jmx</groupId>
<artifactId>jmxri</artifactId>
</exclusion>
</exclusions>
<scope>runtime</scope>
</dependency>
<!-- @Inject -->
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
<!-- Servlet -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!-- Test -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.7</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.0.2.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-eclipse-plugin</artifactId>
<version>2.9</version>
<configuration>
<additionalProjectnatures>
<projectnature>org.springframework.ide.eclipse.core.springnature</projectnature>
</additionalProjectnatures>
<additionalBuildcommands>
<buildcommand>org.springframework.ide.eclipse.core.springbuilder</buildcommand>
</additionalBuildcommands>
<downloadSources>true</downloadSources>
<downloadJavadocs>true</downloadJavadocs>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.5.1</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
<compilerArgument>-Xlint:all</compilerArgument>
<showWarnings>true</showWarnings>
<showDeprecation>true</showDeprecation>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.2.1</version>
<configuration>
<mainClass>org.test.int1.Main</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>
We have included spring-security-config
and spring-security-web
dependencies for Spring Security. Apart from that we have spring-jdbc
dependency because we will be using Spring JDBC authentication too.
Rest of the dependencies are related to Spring MVC, logging, AOP etc.
Spring Security Example Deployment Descriptor
Our web.xml file looks like below.
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="https://java.sun.com/xml/ns/javaee"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://java.sun.com/xml/ns/javaee https://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<!-- Spring Security Configuration File -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/appServlet/spring-security.xml</param-value>
</context-param>
<!-- Creates the Spring Container shared by all Servlet and Filters -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener>
<listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class>
</listener>
<session-config>
<session-timeout>15</session-timeout>
</session-config>
<!-- Spring Security Filter -->
<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>
<!-- Spring MVC - START -->
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/spring/appServlet/servlet-context.xml
</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>appServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- Spring MVC - END -->
</web-app>
contextConfigLocation
is the context parameter where we provide the spring security beans configuration file name. It is used by ContextLoaderListener
to configure authentication in our application.
We have also added HttpSessionEventPublisher
listener to publish session created/destroyed events to the Spring Root WebApplicationContext.
I am also setting session-timeout
to 15 minutes, this is used for auto timeout when user is inactive for 15 minutes.
DelegatingFilterProxy
is the application filter defined, it is used for intercepting the HTTP requests and performing authentication related tasks.
DispatcherServlet
servlet is the front controller for the Spring MVC application.
UserDetailsService
If we want to use any DAO class for authentication, we need to implement UserDetailsService
interface. Once the DAO is configured, it’s loadUserByUsername()
is used to validate the user.
package com.journaldev.spring.security.dao;
import java.util.Collection;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
public class AppUserDetailsServiceDAO implements UserDetailsService {
protected final Log logger = LogFactory.getLog(getClass());
@Override
public UserDetails loadUserByUsername(final String username)
throws UsernameNotFoundException {
logger.info("loadUserByUsername username="+username);
if(!username.equals("pankaj")){
throw new UsernameNotFoundException(username + " not found");
}
//creating dummy user details, should do JDBC operations
return new UserDetails() {
private static final long serialVersionUID = 2059202961588104658L;
@Override
public boolean isEnabled() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public String getUsername() {
return username;
}
@Override
public String getPassword() {
return "pankaj123";
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<SimpleGrantedAuthority> auths = new java.util.ArrayList<SimpleGrantedAuthority>();
auths.add(new SimpleGrantedAuthority("Admin"));
return auths;
}
};
}
}
Note that I am returning UserDetails
instance by using anonymous inner class implementation. Ideally, we should have an implementation class for UserDetails
that can have other user data also, such as emailID, user name, address etc.
Notice that the only combination that will work is when user name is “pankaj” and password is “pankaj123”.
Spring Security Example Controller Class
Here is our controller class that defines two URIs that we can access.
package com.journaldev.spring.controller;
import java.text.DateFormat;
import java.util.Date;
import java.util.Locale;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
public class HomeController {
private static final Logger logger = LoggerFactory.getLogger(HomeController.class);
@RequestMapping(value = "/home", method = RequestMethod.GET)
public String home(Locale locale, Model model) {
logger.info("Welcome home! The client locale is {}.", locale);
Date date = new Date();
DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale);
String formattedDate = dateFormat.format(date);
model.addAttribute("serverTime", formattedDate );
return "home";
}
@RequestMapping(value = "/emp/get/{id}", method = RequestMethod.GET)
public String getEmployee(Locale locale, Model model,@PathVariable("id") int id) {
logger.info("Welcome user! Requested Emp ID is: "+id);
Date date = new Date();
DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale);
String formattedDate = dateFormat.format(date);
model.addAttribute("serverTime", formattedDate );
model.addAttribute("id", id);
model.addAttribute("name", "Pankaj");
return "employee";
}
@RequestMapping(value="/login")
public String login(HttpServletRequest request, Model model){
return "login";
}
@RequestMapping(value="/logout")
public String logout(){
return "logout";
}
@RequestMapping(value="/denied")
public String denied(){
return "denied";
}
}
In our example, we will apply authentication to URI “/emp/get/{id}” only. All other URIs will be accessible without any authentication. login, logout and denied URIs are used to send corresponding response pages when secured URL is requested.
Spring Security Example Bean Configuration File
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="https://www.springframework.org/schema/mvc"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="https://www.springframework.org/schema/beans"
xmlns:context="https://www.springframework.org/schema/context"
xsi:schemaLocation="https://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd
https://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
https://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->
<!-- Enables the Spring MVC @Controller programming model -->
<annotation-driven />
<!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
<resources mapping="/resources/**" location="/resources/" />
<!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<beans:property name="prefix" value="/WEB-INF/views/" />
<beans:property name="suffix" value=".jsp" />
</beans:bean>
<context:component-scan base-package="com.journaldev.spring.controller" />
</beans:beans>
Our spring bean configuration file is simple, it has configurations related to Spring MVC application only.
Spring MVC Security Configuration
This is the most important part of our tutorial, let’s have a look at our file. We will understand each of the parts one by one.
spring-security.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="https://www.springframework.org/schema/security"
xmlns:beans="https://www.springframework.org/schema/beans" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
https://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- Configuring RoleVoter bean to use custom access roles, by default roles
should be in the form ROLE_{XXX} -->
<beans:bean id="roleVoter"
class="org.springframework.security.access.vote.RoleVoter">
<beans:property name="rolePrefix" value=""></beans:property>
</beans:bean>
<beans:bean id="accessDecisionManager"
class="org.springframework.security.access.vote.AffirmativeBased">
<beans:constructor-arg name="decisionVoters"
ref="roleVoter" />
</beans:bean>
<http authentication-manager-ref="jdbc-auth"
access-decision-manager-ref="accessDecisionManager">
<intercept-url pattern="/emp/**" access="Admin" />
<form-login login-page="/login" authentication-failure-url="/denied"
username-parameter="username" password-parameter="password"
default-target-url="/home" />
<logout invalidate-session="true" logout-success-url="/login"
logout-url="/j_spring_security_logout" />
<access-denied-handler error-page="/denied"/>
<session-management invalid-session-url="/login">
<concurrency-control max-sessions="1"
expired-url="/login" />
</session-management>
</http>
<authentication-manager id="in-memory-auth">
<authentication-provider>
<user-service>
<user name="pankaj" password="pankaj123" authorities="Admin" />
</user-service>
</authentication-provider>
</authentication-manager>
<authentication-manager id="dao-auth">
<authentication-provider user-service-ref="userDetailsService">
</authentication-provider>
</authentication-manager>
<beans:bean id="userDetailsService"
class="com.journaldev.spring.security.dao.AppUserDetailsServiceDAO" />
<authentication-manager id="jdbc-auth">
<authentication-provider>
<jdbc-user-service data-source-ref="dataSource"
users-by-username-query="select username,password,enabled from Employees where username = ?"
authorities-by-username-query="select username,role from Roles where username = ?" />
</authentication-provider>
</authentication-manager>
<!-- MySQL DB DataSource -->
<beans:bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<beans:property name="driverClassName" value="com.mysql.jdbc.Driver" />
<beans:property name="url"
value="jdbc:mysql://localhost:3306/TestDB" />
<beans:property name="username" value="pankaj" />
<beans:property name="password" value="pankaj123" />
</beans:bean>
<!-- If DataSource is configured in Tomcat Servlet Container -->
<beans:bean id="dbDataSource"
class="org.springframework.jndi.JndiObjectFactoryBean">
<beans:property name="jndiName" value="java:comp/env/jdbc/MyLocalDB" />
</beans:bean>
</beans:beans>
accessDecisionManager
bean is defined so that we can have our custom roles, by default all the roles should start with ROLE_ and we are overriding this setting in the roleVoter
bean property rolePrefix
.
We can have multiple authentication managers defined in the spring security configuration. I have defined in-memory-auth
for in-memory authentication, dao-auth
for UserDetailsService DAO implementation and jdbc-auth
for JDBC authentication. For JDBC authentication, I have provided configuration for DataSource defined in the application as well as if we want to use JNDI resource defined in the servlet container.
http authentication-manager-ref
is used to define the authentication manager that will be used for authenticating the user. Currently it’s configured to use the JDBC based authentication.
http access-decision-manager-ref
is used to specifying the ID of the AccessDecisionManager implementation which should be used for authorizing HTTP requests.
intercept-url
is used to define the URL pattern and authorities of the user who can access this page. For example, we have defined that URI “/emp/**” can be accessible only by users having “Admin” access.
form-login
defines the login form configuration and we can provide the username and password parameter names. authentication-failure-url
is used to define the URL for the authentication failure page. If no login failure URL is specified, Spring Security will automatically create a failure login URL at /spring_security_login?login_error and a corresponding filter to render that login failure URL when requested.
default-target-url
is used to define the default URL that will be redirected to after successful authentication, if the user’s previous action could not be resumed. This generally happens if the user visits a login page without having first requested a secured operation that triggers authentication. If unspecified, it defaults to the root of the application.
logout
is used to define the logout processing filter. Here we are invalidating the session and sending the user to login page after successful logout. logout-url
is used to define the URL to be used for logout action.
access-denied-handler
defines the global error page if the user is denied the access, because he is not authorized to perform the specified action.
session-management
will add a SessionManagementFilter filter to the filter stack for Session Management.
There are some other configurations also, but I have included most of the important ones that we use.
Spring Security Example View Pages
Let’s have a quick look at our view pages, before we deploy and test our application.
home.jsp
<%@ taglib uri="https://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ page session="false"%>
<html>
<head>
<title>Home</title>
</head>
<body>
<h1>Hello world!</h1>
<P>The time on the server is ${serverTime}.</P>
</body>
</html>
home.jsp is returned for “/home” URI and it should not require any authentication.
employee.jsp
<%@ taglib uri="https://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ page session="false"%>
<html>
<head>
<title>Get Employee Page</title>
</head>
<body>
<h1>Employee Information</h1>
<p>
Employee ID:${id}<br> Employee Name:${name}<br>
</p>
<c:if test="${pageContext.request.userPrincipal.name != null}">
Hi ${pageContext.request.userPrincipal.name}<br>
<c:url var="logoutAction" value="/j_spring_security_logout"></c:url>
<form action="${logoutAction}" method="post">
<input type="submit" value="Logout" />
</form>
</c:if>
</body>
</html>
This page is returned when we are accessing URI that requires authentication. Here I have provided logout option so that user can logout and terminate the session. Once logout is successful, user should be sent back to login page as configured.
login.jsp
<%@ taglib uri="https://java.sun.com/jsp/jstl/core" prefix="c"%>
<html>
<head>
<title>Login Page</title>
</head>
<body>
<h3>Login with Username and Password</h3>
<c:url var="loginUrl" value="/j_spring_security_check"></c:url>
<form action="${loginUrl}" method="POST">
<table>
<tr>
<td>User ID:</td>
<td><input type="text" name="username" /></td>
</tr>
<tr>
<td>Password:</td>
<td><input type="password" name="password" /></td>
</tr>
<tr>
<td colspan='2'><input name="submit" type="submit"
value="Login" /></td>
</tr>
</table>
</form>
</body>
</html>
There are few important points to note here. The first one is that the login URL is “/j_spring_security_check“. This is the default login processing URL, just like the logout-url.
Another important point is the form parameters name for username and password. They should be same as configured in the spring security configurations.
logout.jsp
<html>
<head>
<title>Logout Page</title>
</head>
<body>
<h2>
Logout Successful!
</h2>
</body>
</html>
denied.jsp
<html>
<head>
<title>Access Denied</title>
</head>
<body>
<h1>
Access Denied!
</h1>
</body>
</html>
logout.jsp and denied.jsp pages are simple, but we could have included some information here based on the user details.
Our spring security example application is ready to test, note that for JDBC authentication I am using the same setup as our previous Spring Security Example. So if you have landed directly here, you should check that out.
Spring Security MVC Example Testing
Just deploy the application in your favorite servlet container, mine is Apache Tomcat 7. Below images show us the different outputs for different URLs.
Spring Security Example – Home Page without Authentication
Spring Security Example – Login Page when Authentication Enabled Page is requested (/emp/get/{20})
Spring Security Example – Response Page when Authentication is Successful
Spring Security Example – Access Denied Page when Authentication is Failed
That’s all for the Spring Security Example using UserDetailsService, please download the sample project from below link and explore it to learn more.