Social Login with Spring Security

Introduction


Social Login makes it easy for website to easily register and log in on your site with their existing social network identities from Facebook, Google, Twitter, LinkedIn and more than 30 other providers.

Social Login increases registration rates by up to 50%, minimizes barriers to site entry and provides permission-based access to users’ first-party identity data, allowing you to start delivering personalized experiences that build deeper relationships.

Overview

Today let us see how to implement social login feature using spring framework.This post will explain step by step procedure of how to add Spring Social to a Web application that uses Spring MVC and Spring Security.

We will develop a simple application which support 

1) Form Login : This is classic login,where there will be a form accepting username and password.User need to submit his/her user details.

2) Social Login (Facebook,twitter & linkedin) : Here social user can signin in the application using their Facebook/Twitter/linkedin accounts. Then we add support for current users to associate their social accounts with their accounts in the application,it is also called mapping social user with local user.

You can download source of this example application from GitHub.

Prerequisites

This tutorial assumes that you have already created the Facebook,Twitter and Linkedin application used by this application. You can create these applications by following these links:


Core Dependencies 

Here are the core dependencies versions of this application.
 <spring.version>4.2.0.RELEASE</spring.version>
 <spring.security.version>3.2.3.RELEASE</spring.security.version>
 <spring.social.version>1.1.0.RELEASE</spring.social.version>
 <spring.social.facebook.version>1.1.0.RELEASE</spring.social.facebook.version>
 <spring.social.twitter.version>1.1.0.RELEASE</spring.social.twitter.version>
 <spring.social.linkedin.version>1.0.0.RELEASE</spring.social.linkedin.version>

Spring security are core module contains core authentication and and access control components.

Spring social module  contains the connect framework and provides support for OAuth clients.The security module integrates Spring Security with Spring Social. It delegates the authentication concerns typically taken care by Spring Security to service providers by using Spring Social.

Spring Social Facebook (version 1.1.0.RELEASE) is an extension to Spring Social and it provides Facebook integration.
Spring Social Twitter (version 1.1.0.RELEASE) is an extension to Social Social which provides Twitter integration.
Spring Social Linkedin (version 1.1.0.RELEASE) is an extension to Social Social which provides Linkedin integration.

Profile :

Profiling properties required in application.

1. Build will copy properties file from the profile and place in application.properties file.
2. Add datasource connection properties.
3. Add Hibernate properties.
4. Add the Facebook application id and application secret to the properties file.
5. Add the Twitter consumer key and consumer secret to the properties file.
6. Add the Linkedin key and secret to the properties file.

In the app,we use only one profile (Dev).You can add some more profiles into app like Production,stage,testing....

# Database configuration props
database.driver=com.mysql.jdbc.Driver
database.url=jdbc:mysql://localhost:3306/LoginDB
database.username=root
database.password=pramati

# hibernate props
hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
hibernate.show.sql=true
hibernate.hbm2ddl.auto=create

# facebook provider details
facebook.api.key=781512165315051
facebook.api.secret=376f760458f2411bb9dd9a57b2a16fd9

# twitter provider details
twitter.api.key=kBcdXhCEQcTE8wz5zxliQCJX6
twitter.api.secret=6qiXptqIR1rHyWirlK1Alrk2FXiccyOk0dc2paZRUyjkJqI1IL

# linkedin provider details
linkedin.api.key=75ileo93c5xmr5
linkedin.api.secret=pUdw0kH9ggnj9QQE

Database Models :

In this application we are using single database for both form login and social login users mapping.And user table look like

USER Model:

@Entity
@Table(name = "USER")
public class User implements Serializable{

    @Id
    @Column(name = "USER_ID", unique = true, nullable = false)
    private String userId;

    @Column(name = "NAME", nullable = true, length = 32)
    private String name;

    @Column(name = "PASSWORD", nullable = true, length = 64)
    private String password;

    @Column(name = "EMAIL_ID", nullable = true, length = 128)
    private String emailId;

    @Column(name = "ACTIVE", nullable = false, length = 1)
    private Integer active;

    @Column(name = "PROVIDER", nullable = false, length = 32)
    private String provider;

    @ManyToMany(fetch = FetchType.EAGER,cascade = CascadeType.ALL)
    @JoinTable(
            name = "USER_ROLE",
            joinColumns = @JoinColumn(name = "USER_ID"),
            inverseJoinColumns = @JoinColumn(name = "ROLE_ID")
    )
    private Set<Role> roles = new HashSet<>();

//constructor,setters & getters.

}

Where as userId can be emailid or username.In the case of social login,user id can be a number provided by social providers.
Provider properties defined whether user is local user or social user.And possible values are 
1) "local" for local user.
2) facebook,linkedin,twitter for social users respectively.

ROLE Model :

@Entity
@Table(name = "ROLE")
public class Role implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "ROLE_ID", unique = true, nullable = false)
    private Integer roleId;

    @Column(name = "NAME", nullable = false, length = 32)
    private String name;

    @ManyToMany(mappedBy = "roles")
    private Set<User> users = new HashSet<>();

//constructor,setters & getters.

}

User Services :

We have to create three user components which deals with user authentication and creating user.

1. LocalUserDetailService : Local user details service is used to check whether local user exist in the database and if exist then returns local user.
Local user model is used authenticate with form login.

2. SocialUserDetailService : Social user details service is used to check whether social user exist in the database and if exist then returns social user.
Social user model is used to authenticate with social login.

3. RegistrationUserDetailService : If a new user want to subscribe in database then this service will create user based on user object.

Let us there java class definition

1. LocalUserDetailService : 

LocalUser Model : 

Local user is nothing but a security user with a user id to uniquely identify user.

public class LocalUser extends User {

    private String userId;

    public LocalUser(final String userId, final String username, final String password, final boolean enabled, final boolean accountNonExpired, final boolean credentialsNonExpired, final boolean accountNonLocked, final Collection<? extends GrantedAuthority> authorities) {
        super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
        this.userId = userId;
    }

    public String getUserId() {
        return userId;
    }
}

LocalUserDeatilService.java :

@Service("localUserDetailService")
public class LocalUserDetailService implements UserDetailsService {

    @Autowired
    private UserDAO userDAO;

    @Override
    @Transactional
    public LocalUser loadUserByUsername(final String userId) throws UsernameNotFoundException {
        User user = userDAO.get(userId);
        if (user == null) {
            return null;
        }
        List<SimpleGrantedAuthority> simpleGrantedAuthorities = buildSimpleGrantedAuthorities(user);
        return new LocalUser(user.getUserId(), user.getName(), user.getPassword(), user.getActive() == 1 ? true : false, true
                , true, true, simpleGrantedAuthorities);
    }

    private List<SimpleGrantedAuthority> buildSimpleGrantedAuthorities(final User user) {
        List<SimpleGrantedAuthority> simpleGrantedAuthorities = new ArrayList<>();
        if (user.getRoles() != null) {
            for (Role role : user.getRoles()) {
                simpleGrantedAuthorities.add(new SimpleGrantedAuthority(role.getName()));
            }
        }
        return simpleGrantedAuthorities;
    }
}

2. SocialUserDetailService

Social user details service will just delegate to local user details to verify whether social user exist in local database.
@Service("socialUserDetailService")
public class SocialUserDetailService implements SocialUserDetailsService {

    @Autowired
    @Qualifier(value = "localUserDetailService")
    private UserDetailsService userDetailService;

    @Override
    public SocialUserDetails loadUserByUserId(final String userId) throws UsernameNotFoundException, DataAccessException {
        LocalUser user = (LocalUser) userDetailService.loadUserByUsername(userId);
        if (user == null) {
            throw new SocialAuthenticationException("No local user mapped with social user " + userId);
        }
        return new SocialUser(user.getUsername(), user.getPassword(), user.isEnabled(), user.isAccountNonExpired(), user.isCredentialsNonExpired(), user.isAccountNonLocked(), user.getAuthorities());
    }
}

3. RegistrationUserDetailService

user service is used to register new user to local database.This is used in when a new want to sign up into application or if a new social user want to register into application.

public interface UserService {

    public UserDetails registerNewUser(UserRegistrationForm UserRegistrationForm)throws UserAlreadyExistAuthenticationException;

}

@Service("registrationUserDetailService")
public class RegistrationUserDetailService implements UserService {

    @Autowired
    @Qualifier(value = "localUserDetailService")
    private UserDetailsService userDetailService;

    @Autowired
    private UserDAO userDAO;


    @Override
    @Transactional(value = "transactionManager")
    public LocalUser registerNewUser(final UserRegistrationForm userRegistrationForm) throws UserAlreadyExistAuthenticationException {

        com.spring.security.social.login.example.database.model.User userExist = userDAO.get(userRegistrationForm.getUserId());
        if (userExist != null) {
            throw new UserAlreadyExistAuthenticationException("User with email id " + userRegistrationForm.getEmail() + " already exist");
        }

        com.spring.security.social.login.example.database.model.User user = buildUser(userRegistrationForm);
        userDAO.save(user);
        userDAO.flush();

        return (LocalUser) userDetailService.loadUserByUsername(userRegistrationForm.getUserId());
    }

    private User buildUser(final UserRegistrationForm formDTO) {
        User user = new User();
        user.setUserId(formDTO.getUserId());
        user.setEmailId(formDTO.getEmail());
        user.setName(formDTO.getFirstName() + formDTO.getLastName());
        user.setPassword(formDTO.getPassword());
        final HashSet<Role> roles = new HashSet<Role>();
        Role role = new Role();
        role.setName("ROLE_USER");
        roles.add(role);
        user.setRoles(roles);
        user.setActive(1);
        user.setProvider(formDTO.getSocialProvider().name());
        return user;
    }
}


Configuration :

1. Persistence configuration xml :

spring-persistence-servlet.xml is a configuration file will have data source configuration and hibernate session related bean declaration.This xml file is self explanatory.

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                http://www.springframework.org/schema/beans/spring-beans.xsd
                http://www.springframework.org/schema/context
                http://www.springframework.org/schema/context/spring-context.xsd
                http://www.springframework.org/schema/tx
                http://www.springframework.org/schema/tx/spring-tx.xsd">

    <tx:annotation-driven />

    <!-- Ensures scans all annotations -->
    <context:component-scan base-package="com.spring.security.social.login.example"/>

    <!-- Ensures that configuration properties are read from a property file -->
    <context:property-placeholder location="classpath:application.properties"/>

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

    <!-- data source, sessionfactory, hiberate template & transaction manager -->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="${database.driver}"></property>
        <property name="url" value="${database.url}"></property>
        <property name="username" value="${database.username}"></property>
        <property name="password" value="${database.password}"></property>
    </bean>
    <bean id="loginSessionFactory"
          class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="packagesToScan" value="com.spring.security.social.login.example"/>
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.hbm2ddl.auto">
                    ${hibernate.hbm2ddl.auto}
                </prop>
                <prop key="hibernate.show_sql">
                    ${hibernate.show.sql}
                </prop>
            </props>
        </property>
    </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>

2. Social login configuration :

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:security="http://www.springframework.org/schema/security"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       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.xsd">

    <import resource="spring-persistent-servlet.xml"/>

    <!--  authentication manager and its provider( social provider deals with social login & local user provider deals with form login ) -->
    <security:authentication-manager alias="authenticationManager">
        <security:authentication-provider ref="socialAuthenticationProvider"/>
        <security:authentication-provider user-service-ref="localUserDetailService"/>
    </security:authentication-manager>

    <bean id="socialAuthenticationProvider" class="org.springframework.social.security.SocialAuthenticationProvider">
        <constructor-arg ref="inMemoryUsersConnectionRepository"/>
        <constructor-arg ref="socialUserDetailService"/>
    </bean>

    <!-- form login beans -->
    <bean id="appAuthenticationEntryPoint"
          class="com.spring.security.social.login.example.entrypoint.AppAuthenticationEntryPoint">
        <constructor-arg name="loginFormUrl" value="/services/login"/>
    </bean>
    <bean id="rememberMeServices"
          class="org.springframework.security.web.authentication.NullRememberMeServices"/>
    <bean id="successHandler" class="com.spring.security.social.login.example.handler.AppSuccessHandler"/>
    <bean id="failureHandler"
          class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
        <constructor-arg name="defaultFailureUrl" value="/services/accessdenied"/>
    </bean>
    <bean class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter"
          id="SecurityAuthFilter">
        <property name="authenticationManager" ref="authenticationManager"/>
        <property name="authenticationSuccessHandler" ref="successHandler"/>
        <property name="authenticationFailureHandler" ref="failureHandler"/>
        <property name="filterProcessesUrl" value="/j_spring_security_check"/>
        <property name="rememberMeServices" ref="rememberMeServices"/>
    </bean>


    <!-- Anyone can access these urls -->
    <security:http pattern="/services/login" security="none"/>
    <security:http pattern="/services/accessdenied" security="none"/>
    <security:http pattern="/services/signup" security="none"/>
    <security:http pattern="/services/user/register" security="none"/>

    <security:http use-expressions="true" entry-point-ref="appAuthenticationEntryPoint">

        <security:intercept-url pattern="/auth/**" access="permitAll"/>
        <security:intercept-url pattern="/j_spring_security_check" access="permitAll"/>

        <security:intercept-url pattern="/" access="isAuthenticated()"/>
        <security:intercept-url pattern="/**" access="isAuthenticated()"/>

        <!-- Adds social authentication filter to the Spring Security filter chain. -->
        <security:custom-filter ref="socialAuthenticationFilter" before="PRE_AUTH_FILTER"/>
        <security:custom-filter position="FORM_LOGIN_FILTER" ref="SecurityAuthFilter"/>
    </security:http>

    <!-- social login filter which is a pre authentication filter and works for /auth service url -->
    <bean id="socialAuthenticationFilter" class="org.springframework.social.security.SocialAuthenticationFilter">
        <constructor-arg name="authManager" ref="authenticationManager"/>
        <constructor-arg name="userIdSource" ref="userIdSource"/>
        <constructor-arg name="usersConnectionRepository" ref="inMemoryUsersConnectionRepository"/>
        <constructor-arg name="authServiceLocator" ref="appSocialAuthenticationServiceRegistry"/>
        <property name="authenticationSuccessHandler" ref="successHandler"/>
    </bean>


    <!-- inmemory connection repository which holds connection repository per local user -->
    <bean id="inMemoryUsersConnectionRepository"
          class="org.springframework.social.connect.mem.InMemoryUsersConnectionRepository">
        <constructor-arg name="connectionFactoryLocator" ref="appSocialAuthenticationServiceRegistry"/>
        <property name="connectionSignUp" ref="connectionSignUp"/>
    </bean>

    <!-- service registry will holds connection factory of each social provider -->
    <bean id="appSocialAuthenticationServiceRegistry"
          class="com.spring.security.social.login.example.registry.AppSocialAuthenticationServiceRegistry">
        <constructor-arg>
            <list>
                <ref bean="facebookAuthenticationService"/>
                <ref bean="twitterAuthenticationService" />
                <ref bean="linkedInAuthenticationService"/>
            </list>
        </constructor-arg>
    </bean>

    <bean id="facebookAuthenticationService"
          class="org.springframework.social.facebook.security.FacebookAuthenticationService">
        <constructor-arg name="apiKey" value="${facebook.api.key}"/>
        <constructor-arg name="appSecret" value="${facebook.api.secret}"/>
    </bean>

    <bean id="twitterAuthenticationService"
          class="org.springframework.social.twitter.security.TwitterAuthenticationService">
        <constructor-arg name="apiKey" value="${twitter.api.key}"/>
        <constructor-arg name="appSecret" value="${twitter.api.secret}"/>
    </bean>

    <bean id="linkedInAuthenticationService"
          class="org.springframework.social.linkedin.security.LinkedInAuthenticationService">
        <constructor-arg name="apiKey" value="${linkedin.api.key}"/>
        <constructor-arg name="appSecret" value="${linkedin.api.secret}"/>
    </bean>

    <bean id="userIdSource" class="org.springframework.social.security.AuthenticationNameUserIdSource"/>

    <!-- If no local user is associated to a social connection then connection sign up will create a new local user and map it to social user -->
    <bean id="connectionSignUp" class="com.spring.security.social.login.example.registry.AppConnectionSignUp"/>
</beans>

Brief description of above beans and its usage

1. UsernamePasswordAuthenticationFilter : This bean is used for only form login.For more explination on this filter please go though my previous blog.

2. AppSocialAuthenticationServiceRegistry : 

AppSocialAuthenticationServiceRegistry is inherited from SocialAuthenticationServiceRegistry.This bean is used to register different Social authentication services.

For each social provider a Social authentication service should exist and which defines mapping between local user with social user.
ONE_TO_ONE : For a social user one local user will be associated.
ONE_TO_MANY : Multiple social user can be mapped with single local user.
In this application we are using ONE_TO_ONE mapping between local user v/s social user.

SocialAuthenticationServiceRegistry will maintains connection repositories per provider.Connection repository handles connection persistence methods across all users; this will be a normal singleton bean in your application context.

3. UserConnectionRepositoryhandles connection persistence methods for one specific user; the implementation bean will be request scoped, created for logged in users of your application.
On login of new social user, a connection repository will be created for a local user.And this connection repository will have connection for each provider.

For eq : testuser is your local user.
            samplesocialuser is your facebook user and sampletwitteruse is your twitter user.

then for testuser a connection repository will be created.And in connection repository facebook will have a connection for samplesocialuser and twitter will have a conection for sampletwitteruse.

In this application we are using InMemory connection respository.We can use JDBC implemention of same for business applications.

4. SocialAuthenticationFilter :
   This Filter will handle spring security social login for all social providers.Should be injected into the chain at or before the PRE_AUTH_FILTER location.

Controller 

In this application there is a pages controller which is used to redirect to respective page based on request.
@RestController
public class PagesController {

    @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 = {"/userpage"}, method = RequestMethod.GET)
    public ModelAndView userPage() {

        ModelAndView model = new ModelAndView();
        model.addObject("title", "Spring social security Hello World");
        model.addObject("user", getUser());
        model.setViewName("user");
        return model;
    }

    @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;
    }

}

User Interfaces 

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>
    <!-- css and script are there in attached source-->
</head>
<body>          
        <div class="wrap">

            <div class="login-form">
        
                <form class="login_form" name='loginForm' action="<c:url value='../j_spring_security_check' />" method='POST'>
                    <h1>Login Into Your Account</h1>
                    <ul>
                        <li>
                            <input type="text" class="textbox1" id="username" name="j_username" placeholder="Username" required="">
                            <p><img src="../images/contact.png" alt=""></p>
                        </li>
                        <li>
                            <input type="password" id="password" name="j_password" class="textbox2" placeholder="Password">
                            <p><img src="../images/lock.png" alt=""></p>
                        </li>
                    </ul>
                    <input type="submit" name="Sign In" value="Sign In">
                    <div class="clear"></div>   
                    <label class="checkbox"><input type="checkbox" name="checkbox" checked=""><i></i>Remember me</label>
                    <div class="forgot">
                        <a href="#">forgot password?</a>
                    </div>  
                    <div class="clear"></div>   
                </form>
                <c:url value="../services/signup" var="signupurl" />`
            <div class="account">
                <h2><a href="${signupurl}">Don't have an account? Sign Up!</a></h2>
                <div class="span">
                    <form name='facebookSocialloginForm'
                      action="<c:url value='../auth/facebook?scope=email,user_about_me,user_birthday' />" method='POST'>
                            <img src="../images/facebook.png" alt="">
                            <button type="submit">
                                <i>Sign In with Facebook</i>
                            </button>   
                            <div class="clear"></div>
                    </form>     
                </div>  
                <div class="span1">
                    <form name='TwitterSocialloginForm'
                      action="<c:url value='../auth/twitter?scope=email,user_about_me,user_birthday' />" method='POST'>
                        <img src="../images/twitter.png" alt="">
                        <button type="submit">
                            <i>Sign In with Twitter</i>
                        </button>           
                        <div class="clear"></div>
                    </form>
                </div>
                <div class="span2">
                    <form name='LinkedInSocialloginForm'
                      action="<c:url value='../auth/linkedin' />" method='POST'>
                        <img src="../images/linkedin.png" alt="">
                        <button type="submit">
                            <i>Sign In with Linkedin</i>
                        </button>   
                        <div class="clear"></div>
                    </form>
                </div>
            </div>  
            <div class="clear"></div>   
        </div>

    </div>


</body>
</html>

user.jsp

On successful login(either in form login or social login) user redirected to user.jsp page.
<%@ 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="/j_spring_security_logout" />">Logout</a>
</body>
</html>

accessdenied.jsp

On unsuccessful login(either in form login or social login) user redirected to user.jsp page.
<%@ 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>

Screens

SocialLoginScreen : 


LoginSuccess Page:

Registration Page :

User Database :


Source :

You can download source of this example from GitHub.

Some of the beans used in the example are not explained in this post.Please see spring-social-login-servlet.xml , this xml file is self explanatory.

Hope you understand Spring security social login implementation.Please drop your comments if you have any queries.

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.