Spring security in java - CodeProject

:

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 how Spring Security works and how you can integrate spring security in your spring application easily. 

Background

Anyone reading this article should have a knowledge on 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 application that are build upon Spring framework. It provides a 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 are included in Spring Security.

1. Authentication is something that asks the user to provide valid credentials to login to the application.
2. Authorization means validating what role(s) or privilege(s) does 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 the 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 Servelt 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. 

e.g you can do some security stuffs, 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 an 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 needs to be downloaded, which can perform spring-security for basic authentication and web application authentication. 

spring-security-config-3.2.7-RELEASE.jar
spring-security-core-3.2-RELEASE .jar
spring-security-web-3.2-RELEASE .jar

There are other jars in spring-security which provides 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. 

 

<!-- Enable 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>

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 you application context xml file (or servlet config xml file).

<security:http auto-config="true" use-expressions="true">
    <!-- Interceptor urls -->
    <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 />
            
    <!-- Logout -->
    <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 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 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 log 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')" />

    <!-- Login Form -->
    <security:form-login 

        login-page="/login"

        default-target-url="/"

        authentication-failure-url="/login?error=1" 

        username-parameter="username"

        password-parameter="password" />
         
    <!-- Logout -->
    <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 given 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 build 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 say 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 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 ....>
...
...
...
    <!-- Datasource Config -->
    <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 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 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 a 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;

/**
 * @author Stiti Samantray
 */
@Repository("loginDao")
@Transactional
public class LoginDAOImpl implements LoginDAO {

    @Autowired
    SessionFactory sessionFactory;


    /**
     * Finds the AppUser which has a matching username
     * @param username
     * @return
     */
    @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);

            // Get the user roles
            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) {
                // You can log the error here. Or print to console
            }

            user.setUserRole(userRoles);
        }

        return user;

    }
}

findUserByUsername( String username ) returns a 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;

/**
 * This class gets the appuser information from the database and 
 * populates the "org.springframework.security.core.userdetails.User" type object for appuser.
 *
 * @author Stiti Samantray
 */
@Service("loginService")
public class LoginServiceImpl implements UserDetailsService {

    @Autowired
    private LoginDAO loginDao;


    /**
     * @see UserDetailsService#loadUserByUsername(String)
     */
    @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>();
        // Build user's authorities
        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>();

        //check if user is login
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        if (!(auth instanceof AnonymousAuthenticationToken)) {
            // Get the user details
            UserDetails userDetail = (UserDetails) auth.getPrincipal();
            name = userDetail.getUsername();
            role = (Set<GrantedAuthority>) userDetail.getAuthorities();
        }

        // set the model attributes
        model.addAttribute("accountname", name);
        model.addAttribute("userRole", role);

        // go to Home page
        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 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.

// Show Login Form
@RequestMapping(value="/login", method = RequestMethod.GET)
public String login(ModelMap model, 
        @RequestParam(value="error",defaultValue="") String error) {

    // If fails to login
    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”.

// Logout page
@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 substitute all these overheads by simplifying the methods. It's easy to 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.