Contents
Introduction
This article is all about implementing Spring Security in your Spring MVC web application. After reading this article, you will get an idea about how Spring Security works and how you can integrate Spring security in your spring application easily.
Background
Anyone reading this article should have knowledge of Core Java, J2EE and Spring MVC.
What Is a Security in the Application?
It is all about securing the resources of your application by providing authentication to the privileged users of the application.
What is Spring Security?
It's a Java based security solution. It is mostly applicable for the Java based web applications that are built upon Spring framework. It provides comprehensive security services for J2EE based enterprise software applications. It's very powerful and has a lot more flexibility. You can plug-in the spring security service to your application very easily.
Authentication and Authorization are two main operations that are included in Spring Security:
- Authentication is something that asks the user to provide valid credentials to login to the application.
- Authorization means validating what role(s) or privilege(s) the login user has for this application based on which the user will be allowed to access the different controls or functionality in the application.
So on a whole, Authentication validates the identity of a user, if the identity is authenticated, then it is used to decide the authorization of the user.
Spring Security Integration in Your Application
Before going into this, let's have a brief on how a web application works.
In web applications, normally the execution process is as follows:
- Client hits a URL in the browser to open a web application.
- It then reaches a Servlet Container (e.g Tomcat).
- Where the Servlet Container checks the web.xml to check some web configurations and finds where I need to go.
- The Servlet then sends Request to the Servlet.
- And Servlet processes the Request and sends the Response to the Servlet Container.
- And Servlet Container then sends that response to the client Web Browser.
Afterwards, the concept of Interceptor is introduced in J2EE. You can intercept or filter the Request between the Servlet Container and Servlet, such that before the request reaches the Servlet, you can do some of the pre processing and post processing jobs.
For example, you can do some security stuff, logging, or you can perform any logic to redirect the request to a specific URI based on the type of request or type of client from which the request has come in.
Now, let's see how to integrate Spring Security in your solution to build a User Login portal.
Let's take an example of a web application where as soon as user will enter and execute a URL of a web application in the web browser, it will first open a Login page where user has to enter his/her login credentials and submit them for authentication process. And if authenticated successfully, then the user will be taken to the application Home page. If authentication failed, then user will be taken back to the Login page.
Note: Here, I will focus on authentication via hard coded user name defined in the Spring Security configuration. And then, I will show you how to develop authentication via database using Spring Security.
Now let's see how we can implement this application logic using Spring Security.
Spring Security Set Up
First, you need to download the spring security dependency jars. Below are the 3 basic jars that need to be downloaded, which can perform spring-security for basic authentication and web application authentication.
There are other jars in spring-security which provide other advanced authentication techniques, such as: LDAP auth, OpenID auth, implement Remember me option in authentication, etc. But here, I will focus on web application authentication.
In the servlet configuration XML file or your application context XML, you need to specify the below URLs in the xsi:schemaLocation
of the <beans .. >
tag.
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:security="http://www.springframework.org/schema/security"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.1.xsd">
....
....
....
</beans>
To enable the Spring Security, you need to add the following filters to the /WEB-INF/web.xml.
<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>
So here in the <filter>
tag, you need to define the filter-name
and filter-class
, like you define the servlet-name
and servlet-class
for your application. But here, the filter-name
and filter-class
are fixed or predefined as given above.
In the <filter-mapping>
tag, you define the path that should pass through the spring security checks.
The springSecurityFilterChain
filter, i.e., the DelegatingFilterProxy
servlet delegates the request to a set of filters and interceptors defined in your spring security configuration in the application context. Such as authentication, authorization via the defined interceptors.
Spring Security Configuration
You need to define the following tags under the <beans...>....</beans>
tag in your application context XML file (or servlet config XML file).
<security:http auto-config="true" use-expressions="true">
<security:intercept-url pattern="/login**" access="permitAll" />
<security:intercept-url pattern="/resources/**" access="permitAll" />
<security:intercept-url pattern="/**" access="hasRole('USER')" />
<security:intercept-url pattern="/admin**" access="hasRole('ADMIN')" />
<security:form-login />
<security:logout logout-success-url="/logout" />
</security:http>
Inside the <security:http ... >
tag, you need to mention what resources you want to secure. As you can see, I have defined four interceptors using <security:intercept-url>
tags.
Here, I have mentioned what URLs or URL patterns are permitted to all users including anonymous users and what URLs or areas are restricted to accessible by certain User Roles only.
Such as in the above code snippets, I have mentioned the “login
” page, any pages inside “/resources/” path are accessible to everyone including anonymous users.
As every user should go to the Login Form page and the reason why I gave the “/resources/**” path access to anonymous users is because I have kept all my application CSS, JavaScripts and images inside “/resources/” path.
Also you can notice, I have restricted the “/admin” paths, i.e., any page inside /admin path can be accessed by the users having ADMIN role, and any page inside root path i.e., “/” is accessible to the authenticated users having USER privilege/role.
Spring-security provides a default built-in login page, which has two text boxes to enter the Username and Password, and a Submit button to submit the credentials for validation. You don't have to design a login form for your application. As soon as the user opens the application URL on his browser, spring-security checks if the user has not logged in, then it redirects the user to the default login form provided by spring-security.
If you want to use a custom login page for your application, then you can configure spring-security to use your custom login page instead. You can use the <security:form-login>
tag to define your custom login form page within the <security:http> … </security:http>
tag. Below is the configuration example.
<security:http auto-config="true" use-expressions="true">
<security:intercept-url pattern="/login**" access="permitAll" />
<security:intercept-url pattern="/resources/**" access="permitAll" />
<security:intercept-url pattern="/**" access="hasRole('USER')" />
<security:intercept-url pattern="/admin**" access="hasRole('ADMIN')" />
<security:form-login
login-page="/login"
default-target-url="/"
authentication-failure-url="/login?error=1"
username-parameter="username"
password-parameter="password" />
<security:logout logout-success-url="/logout" />
</security:http>
In the <security:form-login ...>
tag,
- In “
login-page
”, you can specify the custom login page URL path. - In “
default-target-url
”, you can specify the URL where the user should navigate after successful login. - In “
authentication-failure-url
”, you can specify the URL where the user should navigate after a login failure. - “
username-parameter
” and “password-parameter
” - These two are optional. By default, spring-security accepts the parameter names “j_username
” and “j_password
” as username and password in the login form. If you want to give any other name to the username and password input fields in the login form, then you can specify the custom parameter names in these two attributes in <security:form-login ...>
tag.
In the <security:logout ...>
tag:
- In “
logout-success-url
”, you can specify the page URL path which should execute when user uses spring-security logout process.
After the end of </security:http>
tag, you need to add the below tag which is <security:authentication-manager>
. This is meant for Authentication manager.
<security:authentication-manager>
<security:authentication-provider>
<security:user-service>
<security:user name="stitis" password="mindfire" authorities="USER" />
</security:user-service>
</security:authentication-provider>
</security:authentication-manager>
As I mentioned earlier in this article that before the user request reaches the Servlet, i.e., in case of applications built on spring framework, then we can say that before the request reaches the Spring Dispatcher Servlet, it goes through the spring security filter chain and during the filter process, it does certain pre processing and post processing. And these pre and post processing are nothing but the Authentication and Authorization (or we can call it as Security checks) processes.
In the above code snippets, the <security:authentication-manager>
, <security:authentication-provider>
and <security:user-service>
are meant for Authentication. The authentication-manager
delegates the user credentials to UserDetailServices
, i.e., the User Repository to check and get the user identity and granted authorities. And <security:http>
is meant for the security checks or authorization.
That's all on the Spring Security setup and configuration part.
Tips on Advanced Setup Inside <security:authentication-manager>
In the above example spring security setup, I have set up the authentication-manager
to check the login user credential with the plain text user defined in the <user-service>
tag. You can define multiple users for your application here like shown below:
<security:user-service>
<security:user name="stiti" password="mindfire" authorities="USER" />
<security:user name="ram" password="pass1234" authorities="ADMIN" />
.
.
and so on...
</security:user-service>
If you want to do the authentication against a Users
table from the Database, then you can replace the <security:user-service> ... </security:user-service>
tag with the <security:jdbc-user-service>
as follows:
<security:authentication-manager>
<security:authentication-provider>
<security:jdbc-user-service
data-source-ref="dataSource"
users-by-username-query=
"SELECT username, password FROM users WHERE username=? AND active=1"
authorities-by-username-query=
"SELECT US.username, UR.authority
FROM users US, user_roles UR
WHERE US.user_id = UR.user_id and US.username =?"
/>
</security:authentication-provider>
</security:authentication-manager>
Here, you are executing SQL queries to get the username and password from "users
" table in the database.
Similarly, the granted authorities for the username is also fetched from the "user_roles
" database table.
Here, you can notice that I have mentioned the datasource
reference in the "data-source-ref
" property. Which is "dataSource
".
So you need to define a Bean
with id="dataSource"
in your application Context
XML file as follows:
<beans ....>
...
...
...
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"></property>
<property name="url" value="${jdbc.databaseurl}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
...
...
</beans>
I have provided placeholders in the "value
" of the above database property tags. You can replace them with actual values.
If you want to do the authentication against a Users
table from the Database via the Data Access Object Layer (DAO) @Service
, then you can do the configuration as follows:
<security:authentication-manager>
<security:authentication-provider user-service-ref="loginService">
</security:authentication-provider>
</security:authentication-manager>
Here, you are executing SQL queries to get the username and password from "users
" table in the database.
Similarly, the granted authorities for the username is also fetched from the "user_roles
" database table.
Here, you can notice that I have mentioned user-service-ref="loginService"
in the <security:authentication-provider>
tag.
The spring security will get the authentication using a repository service which should be named as "loginService
".
We can create Data access object interface and implementation for our Login Service. Let's create an interface Java class called "LoginDAO.java":
package com.stiti.dao;
import com.stiti.model.AppUser;
public interface LoginDAO {
Object findUserByUsername(String username);
}
Let's create an implementation Java class called "LoginDAOImpl.java" and define it as Repository. Annotate the class as @Transactional
so that all the methods inside this class can be called by the Service
class.
com.stiti.model.AppUser
and AppUserRole
are the Model
classes. I have used Hibernate to do the DB operations, you can use your own way to fetch the DB Users and User Roles tables and define the findUserByUsername( String username )
function body.
findUserByUsername( String username )
returns an AppUser
type object.
package com.stiti.dao.impl;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.HibernateException;
import org.springframework.stereotype.Repository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import com.stiti.dao.LoginDAO;
import com.stiti.model.AppUser;
import com.stiti.model.AppUserRole;
@Repository("loginDao")
@Transactional
public class LoginDAOImpl implements LoginDAO {
@Autowired
SessionFactory sessionFactory;
@Override
public AppUser findUserByUsername( String username )
{
Session session = sessionFactory.getCurrentSession();
List<AppUser> users = new ArrayList<AppUser>();
List<Object> userData = new ArrayList<Object>();
Set<AppUserRole> userRoles = new HashSet<AppUserRole>(0);
try {
String hql = "FROM AppUser U WHERE U.username = :username";
org.hibernate.Query query = session.createQuery(hql)
.setParameter("username", username);
users = query.list();
} catch (HibernateException e) {
System.err.println("ERROR: "+ e.getMessage());
}
AppUser user = null;
if(users.size() > 0) {
user = (AppUser) users.get(0);
try {
String hql = "FROM AppUserRole R WHERE R.username = :username";
org.hibernate.Query query = session.createQuery(hql)
.setParameter("username", username);
userRoles = new HashSet<AppUserRole>(query.list());
} catch (HibernateException e) {
}
user.setUserRole(userRoles);
}
return user;
}
}
findUserByUsername( String username )
returns an AppUser
type object.
package com.stiti.service;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
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;
import com.stiti.model.AppUser;
import com.stiti.model.AppUserRole;
import com.stiti.dao.LoginDAO;
@Service("loginService")
public class LoginServiceImpl implements UserDetailsService {
@Autowired
private LoginDAO loginDao;
@Override
public UserDetails loadUserByUsername( String username ) throws UsernameNotFoundException
{
AppUser user = (AppUser) loginDao.findUserByUsername(username);
List<GrantedAuthority> authorities = buildUserAuthority(user.getUserRole());
return buildUserForAuthentication(user, authorities);
}
private List<GrantedAuthority> buildUserAuthority(Set<AppUserRole> appUserRole) {
Set<GrantedAuthority> setAuths = new HashSet<GrantedAuthority>();
for (AppUserRole userRole : appUserRole) {
System.out.println("****" + userRole.getUserRole());
setAuths.add(new SimpleGrantedAuthority(userRole.getUserRole()));
}
List<GrantedAuthority> Result = new ArrayList<GrantedAuthority>(setAuths);
return Result;
}
private User buildUserForAuthentication(AppUser user, List<GrantedAuthority> authorities) {
return new User(user.getUsername(), user.getPassword(),
true, true, true, true, authorities);
}
}
Write the Controller of the Spring MVC Application
Now we need to write the Controller of the Spring MVC application.
We need to define a RequestMapping
method in our Controller
class for the home path of the application which is in my example “/
”. When user opens the application URL, e.g., “http://www.example.com/”, the below method “loadHomePage()
” defined for this request mapping is executed. In this method, it first gets the user authentication and authorization to this URL.
Spring-security will first go and check the <security:intercept_url>
s in the spring configuration to find what role is allowed to access this URL path. In this example, it finds the users with role “USER
” are allowed to access this URL path. If the user is having a role = USER
, then it will load the Home page.
Otherwise, if it is an Anonymous user, then spring security will redirect them to the login page.
package com.stiti.controller;
import java.util.HashSet;
import java.util.Set;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.SessionAttributes;
@Controller
@SessionAttributes(value={"accountname"}, types={String.class})
public class HomeController {
@SuppressWarnings("unchecked")
@RequestMapping(value="/", method = RequestMethod.GET)
public String executeSecurityAndLoadHomePage(ModelMap model) {
String name = null;
Set<GrantedAuthority> role = new HashSet<GrantedAuthority>();
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (!(auth instanceof AnonymousAuthenticationToken)) {
UserDetails userDetail = (UserDetails) auth.getPrincipal();
name = userDetail.getUsername();
role = (Set<GrantedAuthority>) userDetail.getAuthorities();
}
model.addAttribute("accountname", name);
model.addAttribute("userRole", role);
return "Home";
}
}
Define a RequestMapping
method in our Controller
class to load the custom “Login Page”, provided if you have mentioned the custom login page URL in your Spring <security:http><security:form-login … >
tag . Otherwise, there is no need to define any controller method for Login page, spring will automatically take the user to spring-security's default login form page, which is a simple JSP page written by spring-security itself.
@RequestMapping(value="/login", method = RequestMethod.GET)
public String login(ModelMap model,
@RequestParam(value="error",defaultValue="") String error) {
if (!error.isEmpty()){
model.addAttribute("error", "true");
}
return "Login";
}
Define a method in our Controller
class for “logout-success-url
” defined in the <security:logout>
tag in spring-security config. For this example, I have defined the “logout-success-url
” as “/logout
”.
@RequestMapping(value="/logout", method = RequestMethod.GET)
public String logout(ModelMap model) {
return "Login";
}
Write the Login Form in the Login.jsp
Now let's see what the Login.jsp should contain in the login form.
<form name='login_form' action="<c:url value='j_spring_security_check' />" method='POST'>
<table>
<tr>
<td>User:</td>
<td><input type='text' name='username' value=''></td>
</tr>
<tr>
<td>Password:</td>
<td><input type='password' name='password' /></td>
</tr>
<tr>
<td colspan='2'><input name="submit" type="submit" value="submit" /></td>
</tr>
<tr>
<td colspan='2'><input name="reset" type="reset" /></td>
</tr>
</table>
</form>
Here, the Form
action is submitted to "/j_spring_security_check
".
The "j_spring_security_check
" - is a Servlet where the actual authentication is made and you must map the action of your login form to this Servlet.
“/j_spring_security_check
” URL must be processed by springSecurityFilterChain
filter in web.xml.
Conclusion
To implement security in the application, a developer has to do a lot of things in his application. Spring security substitutes all these overheads by simplifying the methods. It's easy to be plugged in to the application and spring security itself handles all the security aspects of your application and provides a tight security to your application.
History
- 2nd April, 2015: Initial version