Spring Security Authentication with Persistent Remember Me example

Overview


This article will show how to configure Remember Me functionality in Spring Security – using the standard cookie approach with persistence token.

Before reading this article I would recommend to go through my previous blog on Spring Security Remember me Web Flow.

As discussed, Spring Security provides two implementations for Remember-Me :

1. Simple Hash-Based Token Approach : It uses hashing to preserve the security of cookie-based tokens.Please read this article  for Remember me using Token Based Approach.
2. Persistent Token Approach : It uses a database or other persistent storage mechanism to store the generated tokens


In this example we will see spring security using Persistent Token Approach with a simple login form.

Example workflow :


1. If user login with a “remember me” checked, the system will store a “remember me” cookie in the requested browser.

2. If user’s browser provides a valid “remember me” cookie, the system will perform automatic login.

3. If user is login via “remember me” cookies, to update the user detail, user need to type the username and password again.



DataBase script : 


create table ROLE (
    ROLE_ID integer not null auto_increment, 
    NAME varchar(32) not null, 
    primary key (ROLE_ID))

create table USER (
    USER_ID varchar(255) not null, 
    ACTIVE integer not null, 
    NAME varchar(32) not null, 
    PASSWORD varchar(64) not null, 
    primary key (USER_ID))
create table USER_ROLE (
    USER_ID varchar(255) not null, 
    ROLE_ID integer not null, 
    primary key (USER_ID, ROLE_ID))

alter table USER_ROLE add constraint FK_oqmdk7xj0ainhxpvi79fkaq3y foreign key (ROLE_ID) references ROLE (ROLE_ID)
alter table USER_ROLE add constraint FK_j2j8kpywaghe8i36swcxv8784 foreign key (USER_ID) references USER (USER_ID)

create table persistent_logins (
    series varchar(64) not null, 
    last_used datetime not null, 
    token varchar(64) not null, 
    username varchar(64) not null, 
    primary key (series))


persistent_logins table is used to persist remember-me token details in the database.

Spring Configuration XML :

<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"
       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/mvc
                http://www.springframework.org/schema/mvc/spring-mvc.xsd
                http://www.springframework.org/schema/context
                http://www.springframework.org/schema/context/spring-context.xsd
                http://www.springframework.org/schema/security
                http://www.springframework.org/schema/security/spring-security.xsd">

    <context:component-scan base-package="com.spring.security.example"/>

    <bean
            class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix">
            <value>/WEB-INF/pages/</value>
        </property>
        <property name="suffix">
            <value>.jsp</value>
        </property>
    </bean>

    <!-- Application entry point which will redirect to login if user is not authenticated -->
    <bean id="appAuthenticationEntryPoint" class="com.spring.security.example.entrypoint.AppAuthenticationEntryPoint">
        <constructor-arg name="loginFormUrl" value="/services/login"/>
    </bean>

    <!-- if user authentication is successful then AppSuccessHandler will redirect to page based on role-->
    <bean id="successHandler" class="com.spring.security.example.handler.AppSuccessHandler"/>

    <!-- if user authentication is unsuccessful then failureHandler will redirect to access denied page-->
    <bean id="failureHandler"
          class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
        <constructor-arg name="defaultFailureUrl" value="/services/accessdenied"/>
     </bean>

    <security:http pattern="/services/login" security="none"/>
    <security:http pattern="/services/accessdenied" security="none"/>
    <security:http auto-config="true" use-expressions="true" entry-point-ref="appAuthenticationEntryPoint">
        <!-- Interceptor urls -->
        <security:intercept-url pattern="/" access="isAuthenticated()"/>
        <security:intercept-url pattern="/**" access="isAuthenticated()"/>
        <security:intercept-url pattern="/userpage" access="hasRole('USER')"/>
        <security:intercept-url pattern="/admin**" access="hasRole('ADMIN')"/>

        <security:form-login login-page="/services/login"
                    username-parameter="username"
                    password-parameter="password"
                    login-processing-url="/j_spring_security_check"
                    authentication-success-handler-ref="successHandler"
                    authentication-failure-handler-ref="failureHandler"
                />

        <!-- enable remember me -->
        <security:remember-me
                token-validity-seconds="300"
                remember-me-parameter="remember-me"
                data-source-ref="dataSource"
                />

    </security:http>

    <security:authentication-manager alias="authenticationManager">
        <security:authentication-provider user-service-ref="loginService"/>
    </security:authentication-manager>


    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/LoginDB"></property>
        <property name="username" value="root"></property>
        <property name="password" value="pramati"></property>
        <property name="connectionProperties">
            <props>
                <prop key="hibernate.show_sql">
                    true
                </prop>
                <prop key="hibernate.enable_lazy_load_no_trans">
                    true
                </prop>
            </props>
        </property>
    </bean>

    <bean id="loginSessionFactory"
          class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="packagesToScan" value="com.spring.security.example"/>
    </bean>

    <bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
        <property name="sessionFactory" ref="loginSessionFactory"/>
    </bean>
    <bean id="hibernateTemplate" class="org.springframework.orm.hibernate4.HibernateTemplate">
        <property name="sessionFactory" ref="loginSessionFactory"/>
    </bean>
</beans>

token-validity-seconds : The expire date of “remember-me” cookie, in seconds. Here I kept 300 seconds( 5 mins).
remember-me-parameter :The name of the “check box”. Defaults to ‘_spring_security_remember_me’.
data-source-ref : If this is specified, “Persistent Token Approach” will be used.If we did not provide data source then InMemoryTokenRepositoryImpl will be used to persist token.You can test same application be removing datasource.

Controller

@Controller
public class AppController {

    @RequestMapping(value = {"/userpage"}, method = RequestMethod.GET)
    public ModelAndView userPage() {

        if (isAdminPage())
            return adminPage();
        ModelAndView model = new ModelAndView();
        model.addObject("title", "Spring Security Hello World");
        model.addObject("user", getUser());
        model.setViewName("user");
        return model;
    }

    @RequestMapping(value = "/adminpage", method = RequestMethod.GET)
    public ModelAndView adminPage() {
            ModelAndView model = new ModelAndView();
            model.addObject("title", "Spring Security Hello World");
            model.addObject("user", getUser());
            model.setViewName("admin");
            return model;
    }

    @RequestMapping(value = "/login", method = RequestMethod.GET)
    public ModelAndView login(HttpServletRequest request, HttpServletResponse response) throws ServletException,IOException{
        ModelAndView model = new ModelAndView();
        model.addObject("title", "Login Page");
        model.setViewName("login");
        return model;
    }

    @RequestMapping(value = "/logout", method = RequestMethod.GET)
    public ModelAndView logout(HttpServletRequest request, HttpServletResponse response) throws ServletException,IOException{
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        if (auth != null) {
            new SecurityContextLogoutHandler().logout(request, response, auth);
        }
        return login(request,response);
    }

    @RequestMapping(value = {"/accessdenied"}, method = RequestMethod.GET)
    public ModelAndView accessDeniedPage() {
        ModelAndView model = new ModelAndView();
        model.addObject("message", "Either username or password is incorrect.");
        model.setViewName("accessdenied");
        return model;
    }

    private String getUser() {
        String userName = null;
        Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        if (principal instanceof UserDetails) {
            userName = ((UserDetails) principal).getUsername();
        } else {
            userName = principal.toString();
        }
        return userName;
    }


    private boolean isAdminPage() {
        Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        if (principal instanceof UserDetails) {
            Collection<? extends GrantedAuthority> authorities = ((UserDetails) principal).getAuthorities();
            if (authorities.size() == 1) {
                final Iterator<? extends GrantedAuthority> iterator = authorities.iterator();
                GrantedAuthority grantedAuthority = iterator.next();
                if (grantedAuthority.getAuthority().equals("ADMIN")) {
                    return true;
                }
            }
        }
        return false;
    }
}


Views 

<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
    <title>${title}</title>
        <style>
            <!-- removed css here,you can see in the attached source -->
        </style>
</head>
<body>
<br/><br/><br/><br/><br/><br/><br/>
<div class="login-block">
    <form name='loginForm'
                  action="<c:url value='../j_spring_security_check' />" method='POST'>
        <h1>Login</h1>
        <input type="text" id="username" name="username" placeholder="Username"  />
        <input type="password" id="password" name="password" placeholder="Password" />
        Remember Me: <input type="checkbox" name="remember-me" placeholder="remember-me"/></td>
        <button>Submit</button>

    </form>


</div>
</body>
</html>

Screens :

Login Page with Remember Me :



Remember Me Token After Login :


In the response headers you can Observe SPRING_SECURITY_REMEMBER_ME token generated in the backend for loged in user.

With Only Remember Me Token :

Let us request any resource in the application with out providing JSession token.


If you observe,We did not sent Jsession id in the request headers,only remember me token is sent in the request header.But still page is accessible because remember-me token is valid for this user.It means remember-me is working.

Source :

You can download source of this example from GitHub.

Hope you understand remember-me implementation.You can start adding this feature in your login page.

Spring Security Remember Me Authentication with Login Filter

Overview :


This article will show how to set up Remember Me functionality in Spring Security – using standard cookie approach.
Also we will configure login form with UsernamePasswordAuthenticationFilter custom filter.

Before reading this article I would recommend to go through my previous blog on Spring Security Remember me Web Flow.

As discussed, Spring Security provides two implementations for Remember-Me :

1. Simple Hash-Based Token Approach : It uses hashing to preserve the security of cookie-based tokens
2. Persistent Token Approach : It uses a database or other persistent storage mechanism to store the generated tokens.Please read this article for example of Remember me using Persistent Token Approach.

In this example we will see spring security using Token Hash-Based Approach with custom login filter.

Example Workflow :

1. If user login with a “remember me” checked, the system will store a “remember me” cookie in the requested browser.

2. If user’s browser provides a valid “remember me” cookie, the system will perform automatic login.

3. If user is login via “remember me” cookies, to update the user detail, user need to type the username and password again.

Token Preparation :


Here we are using TokenBasedRememberMeServices to prepare token,this is how token is prepared

base64(username + ":" + expirationTime + ":" + md5Hex(username + ":" + expirationTime + ":" password + ":" + key))

here key is A private key to prevent modification of the remember-me token.

Spring Configuration Xml :

<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"
       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/mvc
                http://www.springframework.org/schema/mvc/spring-mvc.xsd
                http://www.springframework.org/schema/context
                http://www.springframework.org/schema/context/spring-context.xsd
                http://www.springframework.org/schema/security
                http://www.springframework.org/schema/security/spring-security.xsd">

    <context:component-scan base-package="com.spring.security.example"/>

    <bean
            class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix">
            <value>/WEB-INF/pages/</value>
        </property>
        <property name="suffix">
            <value>.jsp</value>
        </property>
    </bean>

    <!-- Application entry point which will redirect to login if user is not authenticated -->
    <bean id="appAuthenticationEntryPoint" class="com.spring.security.example.entrypoint.AppAuthenticationEntryPoint">
        <constructor-arg name="loginFormUrl" value="/services/login"/>
    </bean>

    <!-- if user authentication is successful then AppSuccessHandler will redirect to page based on role-->
    <bean id="successHandler" class="com.spring.security.example.handler.AppSuccessHandler"/>

    <!-- if user authentication is unsuccessful then failureHandler will redirect to access denied page-->
    <bean id="failureHandler"
          class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
        <constructor-arg name="defaultFailureUrl" value="/services/accessdenied"/>
     </bean>

    <bean id="SecurityAuthFilter" class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
        <property name="authenticationSuccessHandler" ref="successHandler"/>
        <property name="authenticationFailureHandler" ref="failureHandler"/>
        <property name="authenticationManager" ref="authenticationManager"/>
        <property name="filterProcessesUrl" value="/j_spring_security_check"/>
        <property name="rememberMeServices" ref="rememberMeServices"/>
    </bean>

    <bean id="rememberMeServices"
          class="org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices">
        <constructor-arg name="key" value="TOKEN_KEY" />
        <constructor-arg name="userDetailsService" ref="loginService"/>
        <property name="tokenValiditySeconds" value="300"></property>
        <property name="parameter" value="remember-me"/>
     </bean>


    <security:http pattern="/services/login" security="none"/>
    <security:http pattern="/services/accessdenied" security="none"/>
    <security:http auto-config="false" use-expressions="true" entry-point-ref="appAuthenticationEntryPoint" >
        <!-- Interceptor urls -->
        <security:intercept-url pattern="/" access="isAuthenticated()"/>
        <security:intercept-url pattern="/**" access="isAuthenticated()"/>
        <security:intercept-url pattern="/userpage" access="hasRole('USER')"/>
        <security:intercept-url pattern="/admin**" access="hasRole('ADMIN')"/>
        <security:intercept-url pattern="/j_spring_security_check" access="permitAll"/>
        <security:custom-filter position="FORM_LOGIN_FILTER" ref="SecurityAuthFilter"/>
        <security:remember-me key="TOKEN_KEY" user-service-ref="loginService" />


    </security:http>

    <security:authentication-manager alias="authenticationManager">
        <security:authentication-provider user-service-ref="loginService"/>
    </security:authentication-manager>


    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/LoginDB"></property>
        <property name="username" value="root"></property>
        <property name="password" value="pramati"></property>
        <property name="connectionProperties">
            <props>
                <prop key="hibernate.show_sql">
                    true
                </prop>
                <prop key="hibernate.enable_lazy_load_no_trans">
                    true
                </prop>
            </props>
        </property>
    </bean>

    <bean id="loginSessionFactory"
          class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="packagesToScan" value="com.spring.security.example"/>
    </bean>

    <bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
        <property name="sessionFactory" ref="loginSessionFactory"/>
    </bean>
    <bean id="hibernateTemplate" class="org.springframework.orm.hibernate4.HibernateTemplate">
        <property name="sessionFactory" ref="loginSessionFactory"/>
    </bean>
</beans>

TokenBasedRememberMeServices requires Key and user details service.
Key is a private key to prevent modification of the remember-me token.
UserDetailsService from which it can retrieve the username and password for signature comparison purposes, and generate theRememberMeAuthenticationToken to contain the correct GrantedAuthorities.

User defined properties for remember me service
token-validity-seconds : The expire date of “remember-me” cookie, in seconds. Here I kept 300 seconds( 5 mins).
remember-me-parameter :The name of the “check box”. Defaults to ‘_spring_security_remember_me’.

Here we used custom login filter UsernamePasswordAuthenticationFilter, and configured success handler,failure handler and remember service to authentication filter.This filter will use to Processes an authentication from form  submission.This filter by default responds to the URL /j_spring_security_check.

Controller :

@Controller
public class AppController {

    @RequestMapping(value = {"/userpage"}, method = RequestMethod.GET)
    public ModelAndView userPage() {

        if (isAdminPage())
            return adminPage();
        ModelAndView model = new ModelAndView();
        model.addObject("title", "Spring Security Hello World");
        model.addObject("user", getUser());
        model.setViewName("user");
        return model;
    }

    @RequestMapping(value = "/adminpage", method = RequestMethod.GET)
    public ModelAndView adminPage() {
            ModelAndView model = new ModelAndView();
            model.addObject("title", "Spring Security Hello World");
            model.addObject("user", getUser());
            model.setViewName("admin");
            return model;
    }

    @RequestMapping(value = "/login", method = RequestMethod.GET)
    public ModelAndView login(HttpServletRequest request, HttpServletResponse response) throws ServletException,IOException{
        ModelAndView model = new ModelAndView();
        model.addObject("title", "Login Page");
        model.setViewName("login");
        return model;
    }

    @RequestMapping(value = "/logout", method = RequestMethod.GET)
    public ModelAndView logout(HttpServletRequest request, HttpServletResponse response) throws ServletException,IOException{
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        if (auth != null) {
            new SecurityContextLogoutHandler().logout(request, response, auth);
        }
        return login(request,response);
    }

    @RequestMapping(value = {"/accessdenied"}, method = RequestMethod.GET)
    public ModelAndView accessDeniedPage() {
        ModelAndView model = new ModelAndView();
        model.addObject("message", "Either username or password is incorrect.");
        model.setViewName("accessdenied");
        return model;
    }

    private String getUser() {
        String userName = null;
        Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        if (principal instanceof UserDetails) {
            userName = ((UserDetails) principal).getUsername();
        } else {
            userName = principal.toString();
        }
        return userName;
    }


    private boolean isAdminPage() {
        Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        if (principal instanceof UserDetails) {
            Collection<? extends GrantedAuthority> authorities = ((UserDetails) principal).getAuthorities();
            if (authorities.size() == 1) {
                final Iterator<? extends GrantedAuthority> iterator = authorities.iterator();
                GrantedAuthority grantedAuthority = iterator.next();
                if (grantedAuthority.getAuthority().equals("ADMIN")) {
                    return true;
                }
            }
        }
        return false;
    }
}

Views :

<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
    <title>${title}</title>
        <style>
            <!-- removing css,you can find them in attached source -->
        </style>
</head>
<body>
<br/><br/><br/><br/><br/><br/><br/>
<div class="login-block">
    <form name='loginForm'
                  action="<c:url value='../j_spring_security_check' />" method='POST'>
        <h1>Login</h1>
        <input type="text" id="username" name="j_username" placeholder="Username"  />
        <input type="password" id="password" name="j_password" placeholder="Password" />
        Remember Me: <input type="checkbox" name="remember-me" placeholder="remember-me"/></td>
        <button>Submit</button>

    </form>


</div>
</body>
</html>

Screens :

Login Page :



Remember Me Token in reponse :


Remember Me Token With out Jsession in Request :


If you observe,We did not sent Jsession id in the request headers,only remember me token is sent in the request header.But still page is accessible because remember-me token is valid for this user.It means remember-me is working.

Source :

You can download source of this example from GitHub.

Hope you understand remember-me implementation.

Remember-Me Authentication in Spring Security

Introduction :


Remember-me or persistent-login authentication refers to web sites being able to remember the identity of a principal between sessions. This is typically accomplished by sending a cookie to the browser, with the cookie being detected during future sessions and causing automated login to take place. Spring Security provides the necessary hooks for these operations to take place and has two concrete remember-me implementations. One uses hashing to preserve the security of cookie-based tokens and the other uses a database or other persistent storage mechanism to store the generated tokens.

Remember me spring security flow :




1) When the user successfully logs in with Remember Me checked, a login cookie is generated in addition to the standard session management cookie.

2) The login cookie contains the user's username and a random number and a current date time(this cookie is also called Token). The username and token are stored as a pair in a database table.

3) And this cookie is sent back in user response header.And this cookie is associated in every request sent to server.

4) When a non-logged-in user visits the site and presents a login cookie, the username and token are looked up in the database.
If the pair is present, the user is considered authenticated. The used token is removed from the database. A new token is generated, stored in database with the username, and issued to the user via a new login cookie.

5) If the pair is not present, the login cookie is ignored.Then redirect to login operations, then user must first successfully submit a normal username/password login form.If username is successfully authenticated then a new token is generated and persists username & token are stored as a pair in a database table and sent back new token to user as in response.


Implementation Details :


Spring provides an Interface for remember me, that is RememberMeServices.This interface has two implementations TokenBasedRememberMeServices and PersistentTokenBasedRememberMeServices.

RememberMeServices is hooked in UsernamePasswordAuthenticationFilter, which will invoke a concrete RememberMeServices at the appropriate times.This interface therefore provides the underlying remember-me implementation with sufficient notification of authentication-related events, and delegates to the implementation whenever a candidate web request might contain a cookie and wish to be remembered.

Lets us see RememberMeServices implementations macanism

1) TokenBasedRememberMeServices :


This approach uses hashing to achieve a useful remember-me strategy. In essence a cookie is sent to the browser upon successful interactive authentication, with the cookie being composed as follows:

base64(username + ":" + expirationTime + ":" + md5Hex(username + ":" + expirationTime + ":" password + ":" + key))

here key is A private key to prevent modification of the remember-me token.

When user logs in,TokenBasedRememberMeServices generates a RememberMeAuthenticationToken token with above macanism.This token is sent back to user in response.
Further requests, remember-me token will be associated in each request cookies.RememberMeServices will decode this token and token is validated by comparing with expected TokenSignature.If tokens are same then a new token is generated and sent back to user else InvalidCookieException is thrown.

In addition, TokenBasedRememberMeServices requires A UserDetailsService from which it can retrieve the username and password for signature comparison purposes, and generate the RememberMeAuthenticationToken to contain the correct GrantedAuthority[]s.


2) PersistentTokenBasedRememberMeServices : 


PersistentTokenBasedRememberMeServices, name itself explains token is persisted in repository and for every request it will get token from repository and compares with user token.Rest all is same as TokenBasedRememberMeServices.
PersistentTokenBasedRememberMeServices generate token named PersistentRememberMeToken which contains username,series(random value),tokenValue(Random value) and date.And this token is persisted in repository to remember.

Currentlly PersistentTokenBasedRememberMeServices supports two types of repository to persist token.

1) InMemoryTokenRepositoryImpl which is intended for testing only.

2) JdbcTokenRepositoryImpl which stores the tokens in a database.

For detail explanation and implementation for remember-me service, I have written separate blogs.


1) Spring security remember me using form login : In this blog, remember me service is implemented using PersistentTokenBasedRememberMeServices.

2) Spring security remember me with Custom login filter : In this blog,  remember me service is implemented using TokenBasedRememberMeServices.




Spring security entry point and role base login example

First I would recommend you to go through my previous blog post I have written for Spring Security hello world example.
Today I am going to explain a simple example of why to use entry point in spring security and how to use role based login in Spring Security 4.

First we will see a spring security flow diagram of today's implementation


In the above diagram, when user log-in by providing credentials then spring filter will evaluate user creds with the help of user details service provider.If user is authenticated successfully then spring filter will invoke success handler  else failure handler.If success then success handler will redirect to page based on his roles.If authentication failure then failure handler will redirect to access denied page.
If none of the user is logged in, then for every request Spring Security handles  the authentication process with the concept of an Entry Point.For any request to the application entry point will redirect to the login page.

let us see implementation of the above diagram

Technologies used 

java 1.7

tomcat 8

spring 4.1.6.RELEASE

spring-security 4.0.1.RELEASE

Maven 3

Maven dependencies

<properties>
        <spring-version>4.1.6.RELEASE</spring-version>
        <spring-orm-version>4.1.1.RELEASE</spring-orm-version>
        <spring-security-version>4.0.1.RELEASE</spring-security-version>
        <hibernate.version>4.3.11.Final</hibernate.version>
        <mysql.driver.version>5.1.30</mysql.driver.version>
    </properties>

    <dependencies>

        <!-- spring dependencies -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring-version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>${spring-version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring-version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>${spring-orm-version}</version>
        </dependency>

        <!-- spring security dependencies -->
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-core</artifactId>
            <version>${spring-security-version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-web</artifactId>
            <version>${spring-security-version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-config</artifactId>
            <version>${spring-security-version}</version>
        </dependency>

        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.5</version>
        </dependency>
    </dependencies>

    <build>
        <finalName>spring-security-entry-point-and-role-based-url</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.3</version>
                <configuration>
                    <source>1.7</source>
                    <target>1.7</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

Security Configuration XML


Lets look at the configuration xml. I assume that you have clear idea about spring security configuration so I’m not going to explain each and every thing on this project. If you have doubt about the spring configurations please follow my previous posts.

<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"
       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/mvc
                http://www.springframework.org/schema/mvc/spring-mvc.xsd
                http://www.springframework.org/schema/context
                http://www.springframework.org/schema/context/spring-context.xsd
                http://www.springframework.org/schema/security
                http://www.springframework.org/schema/security/spring-security.xsd">

    <context:component-scan base-package="com.spring.security"/>

    <bean
            class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix">
            <value>/WEB-INF/pages/</value>
        </property>
        <property name="suffix">
            <value>.jsp</value>
        </property>
    </bean>

    <!-- Application entry point which will redirect to login if user is not authenticated -->
    <bean id="appAuthenticationEntryPoint" class="com.spring.security.entrypoint.AppAuthenticationEntryPoint">
        <constructor-arg name="loginFormUrl" value="/services/login"/>
    </bean>

    <!-- if user authentication is successful then AppSuccessHandler will redirect to page based on role-->
    <bean id="successHandler" class="com.spring.security.handler.AppSuccessHandler"/>

    <!-- if user authentication is unsuccessful then failureHandler will redirect to access denied page-->
    <bean id="failureHandler" class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
        <constructor-arg name="defaultFailureUrl" value="/services/accessdenied"/>
    </bean>

    <!-- With out login below url can be accessed and they are public,hence security is none -->
    <security:http pattern="/services/login" security="none"/>
    <security:http pattern="/services/accessdenied" security="none"/>

    <!-- Observe entry-point-ref is configured and all the request /** should be authenticated, if any request is not authenticated then entry point will be invoked which intern redirect to login page  -->
    <security:http auto-config="true" use-expressions="true" entry-point-ref="appAuthenticationEntryPoint">
        <!-- Interceptor urls -->
        <security:intercept-url pattern="/" access="isAuthenticated()"/>
        <security:intercept-url pattern="/**" access="isAuthenticated()"/>
        <security:intercept-url pattern="/user**" access="hasRole('USER')" />
        <security:intercept-url pattern="/admin**" access="hasRole('ADMIN')" />

        <security:form-login login-page="/login"
                             login-processing-url="/j_spring_security_check"
                             authentication-success-handler-ref="successHandler"
                             authentication-failure-handler-ref="failureHandler"
                             username-parameter="username"
                             password-parameter="password"
                />

        <!-- disabling csrf protection -->
        <security:csrf disabled="true"/>
    </security:http>


    <security:authentication-manager alias="authenticationManager">
        <security:authentication-provider>
            <security:user-service>
                <security:user name="testuser" password="testuser" authorities="ROLE_USER" />
                <security:user name="admin" password="admin" authorities="ROLE_ADMIN" />
            </security:user-service>
        </security:authentication-provider>
    </security:authentication-manager>
</beans>

here Spring Security user credentials are placed in XML configuration. However it is not safe to keep user details in XML file,it is always better to use database to secure user creds.I recommend my previous article for spring security using database.

Spring Security handles this automatic triggering of the authentication process with the concept of an Entry Point – this is a required part of the configuration, and can be injected via the entry-point-ref attribute of the security:http  element.

Spring Security handles success handler & failure handler based on authentication result.Success handler and failure handler can be configured in security:form-login element.We use SimpleUrlAuthentication FailureHandler which is provided by spring, which will auto redirect to the access denied page.


EntryPoint :

We need to create a class that will extend Spring’s AuthenticationEntryPoint class and override its method commence to redirect when authentication header is not present in the request.By default This method will reject every unauthenticated request and send error code 401.

AppAuthenticationEntryPoint.java

public class AppAuthenticationEntryPoint extends LoginUrlAuthenticationEntryPoint {

    private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

    public AppAuthenticationEntryPoint(final String loginFormUrl) {
        super(loginFormUrl);
    }

    /**
     * Performs the redirect (or forward) to the login form URL.
     */
    public void commence(HttpServletRequest request, HttpServletResponse response,
                         AuthenticationException authException) throws IOException, ServletException {

        // redirect to login page. Use https if forceHttps true
        String redirectUrl = buildRedirectUrlToLoginPage(request, response, authException);
        redirectStrategy.sendRedirect(request, response, redirectUrl);
    }

}

If we didn’t set this handler then spring framework push the 403 Forbidden in response. We can even set unauthrozied(401) in response header as authException.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");

Authentication Success Handler :

AppSuccessHandler.java

public class AppSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {

    private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

    @Override
    protected void handle(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
            throws IOException {
        String targetUrl = determineTargetUrl(authentication);

        if (response.isCommitted()) {
            System.out.println("Can't redirect");
            return;
        }

        redirectStrategy.sendRedirect(request, response, targetUrl);
    }

    /*
     * This method extracts the roles of currently logged-in user and returns
     * appropriate URL according to his/her role.
     */
    protected String determineTargetUrl(Authentication authentication) {
        String url = "";

        Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();

        List<String> roles = new ArrayList<String>();

        for (GrantedAuthority a : authorities) {
            roles.add(a.getAuthority());
        }

        if (isAdmin(roles)) {
            url = "/services/adminpage";
        } else if (isUser(roles)) {
            url = "/services/userpage";
        } else {
            url = "/services/accessDenied";
        }

        return url;
    }

    private boolean isUser(List<String> roles) {
        if (roles.contains("ROLE_USER")) {
            return true;
        }
        return false;
    }

    private boolean isAdmin(List<String> roles) {
        if (roles.contains("ROLE_ADMIN")) {
            return true;
        }
        return false;
    }

    public void setRedirectStrategy(RedirectStrategy redirectStrategy) {
        this.redirectStrategy = redirectStrategy;
    }

    protected RedirectStrategy getRedirectStrategy() {
        return redirectStrategy;
    }

}

When authentication is successful then this handler will invoked and redirect to the page as configured in the determineTargetUrl api based on role.

AppController :

This controller will serve your request for login,logout,userpages and redirects to dynamic jsp page.

public class AppController {

    @RequestMapping(value = {"/userpage"}, method = RequestMethod.GET)
    public ModelAndView userPage() {

        if (isAdminPage())
            return adminPage();
        ModelAndView model = new ModelAndView();
        model.addObject("title", "Spring Security Hello World");
        model.addObject("user", getUser());
        model.setViewName("user");
        return model;
    }

    @RequestMapping(value = "/adminpage", method = RequestMethod.GET)
    public ModelAndView adminPage() {
            ModelAndView model = new ModelAndView();
            model.addObject("title", "Spring Security Hello World");
            model.addObject("user", getUser());
            model.setViewName("admin");
            return model;
    }

    @RequestMapping(value = "/login", method = RequestMethod.GET)
    public ModelAndView login(HttpServletRequest request, HttpServletResponse response) throws ServletException,IOException{
        ModelAndView model = new ModelAndView();
        model.addObject("title", "Login Page");
        model.setViewName("login");
        return model;
    }

    @RequestMapping(value = "/logout", method = RequestMethod.GET)
    public ModelAndView logout(HttpServletRequest request, HttpServletResponse response) throws ServletException,IOException{
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        if (auth != null) {
            new SecurityContextLogoutHandler().logout(request, response, auth);
        }
        return login(request,response);
    }

    @RequestMapping(value = {"/accessdenied"}, method = RequestMethod.GET)
    public ModelAndView accessDeniedPage() {
        ModelAndView model = new ModelAndView();
        model.addObject("message", "Either username or password is incorrect.");
        model.setViewName("accessdenied");
        return model;
    }

    private String getUser() {
        String userName = null;
        Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        if (principal instanceof UserDetails) {
            userName = ((UserDetails) principal).getUsername();
        } else {
            userName = principal.toString();
        }
        return userName;
    }


    private boolean isAdminPage() {
        Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        if (principal instanceof UserDetails) {
            Collection<? extends GrantedAuthority> authorities = ((UserDetails) principal).getAuthorities();
            if (authorities.size() == 1) {
                final Iterator<? extends GrantedAuthority> iterator = authorities.iterator();
                GrantedAuthority grantedAuthority = iterator.next();
                if (grantedAuthority.getAuthority().equals("ADMIN")) {
                    return true;
                }
            }
        }
        return false;
    }
}

Views :

Login.jsp :

<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
    <title>${title}</title>
        <style>
            //removing style,you can see complete css in the attached source.
        </style>
</head>
<body>
<br/><br/><br/><br/><br/><br/><br/>
<div class="login-block">
    <form name='loginForm'
                  action="<c:url value='../j_spring_security_check' />" method='POST'>
        <h1>Login</h1>
        <input type="text" id="username" name="username" placeholder="Username"  />
        <input type="password" id="password" name="password" placeholder="Password" />
        <button>Submit</button>
    </form>
</div>
</body>
</html>

AccessDenied.jsp:

<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
    <title>Access Denied Page</title>
</head>
<body>
    <h2> Access Denied </h2>
    <br/>${message}<br/>

    Click here for<a href="<c:url value="/login" />"> Login</a>
</body>
</html>

Admin.jsp :

<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
    <title>HelloWorld page</title>
</head>
<body>
    ${title}<br/><br/>
    Dear ${user}, you are successfully logged into this application as admin.
    <br/>
    <a href="<c:url value="/services/logout" />">Logout</a>
</body>
</html>

User.jsp :

<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
    <title>HelloWorld page</title>
</head>
<body>
    ${title}<br/><br/>
    Dear ${user}, you are successfully logged into this application.
    <br/>
    <a href="<c:url value="/services/logout" />">Logout</a>
</body>
</html>

Screens :

After deploying above application(spring-security-entry-point-and-role-based-url.war) in web server(tomcat) and you can find below screens

Login page :



Entry Point Redirecting to Login page :


Access Denied :


Admin Login :


Download Source : 

Please download source from GitHub.