Wednesday 8 January 2014

Spring Security: Remember me Example

Remember me authentication is a mechanism that allows a user to maintain his identity across multiple browser sessions. Typically, a browser session ends when we close the browser. This means that with Remember-me, a user will be able to automatically login even after he restarts the browser. It remembers the identity of the user between two different 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. Note that both the implementations require a UserDetailsService. 

1. Simple Hash-Based Token Approach

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

username: As identifiable to the `UserDetailsService`. 

password: That matches the one in the retrieve UserDetails. 

expirationTime: The date and time when the remember-me token expires, expressed in milliseconds .

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

The remember-me token is valid only for the period specified, and provided that the username, password and key does not change. To enable remember-me authentication just add the <remember-me> element into <http element>

<http> ... <remember-me key="myAppKey"/> </http>


2. Persistent Token Approach

This approach uses the database to store the generated tokens. The database that will be used should contain a persistent_logins table, created using the following SQL (or equivalent):

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

To use this approach with the namespace configuration, you need to supply a datasource reference

<http> ... <remember-me data-source-ref="someDataSource"/> </http>

Example

The setup is the same as the one used for my previous post demonstrating the use of a custom UserDetailsService.

Only changes required are in spring-security.xml configuration. We will be using the Hash-Based token approach. 



 
  

  
  

  

  
 

 
  
 

Demo

If we try to access the URL- '/SpringSecurity/user/welcome'
Spring Security intercepts this URL and presents a login form.

Note that there exists a check-box for remember me.If we tick this check-box and login with valid credentials, a cookie named 'SPRING_SECURITY_REMEMBER_ME' is created along with the cookie storing JSessionId. 




If we delete this cookie for JSessionId, we are still able to login automatically and access the intercepted URL.

This is all because of the simple Hash-Based generated token stored inside the browser's cookie.

You can view/download the complete source code from here.

Spring Security: Using a custom Authentication Provider and a Password Encoder

To get familiar with Spring Security basic concepts you can refer to my previous posts. In this post, we will see how we can use a custom authentication provider to perform the authentication.

The most common approach to verifying an authentication request is to load the corresponding UserDetails and check the loaded password against the one that has been entered by the user. This is the approach used by the 
DaoAuthenticationProvider. It is the simplest AuthenticationProvider implemented by Spring Security.It leverages a UserDetailsService (as a DAO) in order to lookup the username, password and GrantedAuthorities. It authenticates the user simply by comparing the password submitted in a UsernamePasswordAuthenticationToken against the one loaded by the UserDetailsService.

web.xml contents remain the same as given in my previous post for using a custom UserDetailsService.Following are the dependencies for this example.

pom.xml


 4.0.0
 SpringSecurity
 SpringSecurity
 war
 0.0.1-SNAPSHOT
 SpringSecurity1 Maven Webapp
 http://maven.apache.org
 
  
   junit
   junit
   3.8.1
   test
  
  
   org.springframework
   spring-orm
   3.2.0.RELEASE
  
  
   org.springframework
   spring-webmvc
   3.2.0.RELEASE
  
  
   org.springframework.security
   spring-security-web
   3.2.0.RELEASE
  
  
   org.springframework.security
   spring-security-config
   3.2.0.RELEASE
  
  
   org.springframework.security
   spring-security-taglibs
   3.2.0.RELEASE
  
  
   jstl
   jstl
   1.2
   compile
  
  
   taglibs
   standard
   1.1.2
   compile
  
  
   javax
   javaee-api
   7.0
  
  
   org.hibernate
   hibernate-core
   3.6.10.Final
  
  
   mysql
   mysql-connector-java
   5.1.26
  
  
   commons-dbcp
   commons-dbcp
   20030825.184428
  
  
   commons-pool
   commons-pool
   20030825.183949
  
  
   commons-collections
   commons-collections
   3.2.1
  
  
   javassist
   javassist
   3.12.1.GA
  
  
   org.codehaus.jackson
   jackson-mapper-asl
   1.9.12
  
 
 
  SpringSecurity
 

spring-security.xml



 
  
  
   
  
 

 
  
  
 

 
  
  
 

 

 


This is how you can configure an authentication provider. 'authService' bean is the class which implements the UserDetailsService interface. It is the same as given in the previous post. The PasswordEncoder is optional. A PasswordEncoder provides encoding and decoding of passwords presented in the UserDetailsobject that is returned from the configured UserDetailsService.

Spring Security’s PasswordEncoder interface is used to support the use of passwords which are encoded in some way in persistent storage. You should never store passwords in plain text. Always use a one-way password hashing algorithm such as bcrypt which uses a built-in salt value which is different for each stored password. Do not use a plain hash function such as MD5 or SHA, or even a salted version. Bcrypt is deliberately designed to be slow and to hinder offline password cracking, whereas standard hash algorithms are fast and can easily be used to test thousands of passwords in parallel on custom hardware. You might think this doesn’t apply to you since your password database is secure and offline attacks aren’t a risk. If so, do some research and read up on all the high-profile sites which have been compromised in this way and have been pilloried for storing their passwords insecurely. It’s best to be on the safe side. 

Using 'org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder' is a good choice for security. Note that for the authentication provider to be able to compare password submitted in a UsernamePasswordAuthenticationToken against the one loaded by the UserDetailsService, you will need to store these passwords inside some database only after encrypting it using the BCryptPasswordEncoder.

spring-servlet.xml





 

 

 
  
   
    
   
  
 

 
  
  
 

 
  
  
  
  
 

 
  
  
   classpath:hibernate.cfg.xml
  
  
   org.hibernate.cfg.AnnotationConfiguration
   
  
 

 

 
  
 

Controller

package com.spring.security.controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import com.spring.security.domain.MyUser;
import com.spring.security.service.IUserService;

@Controller
public class HelloController {

 @Autowired
 IUserService userService;

 @RequestMapping(value = "/user/welcome", method = RequestMethod.GET)
 public String printWelcomeUser() {
  return "hello";
 }

 @RequestMapping(value = "/admin/welcome", method = RequestMethod.GET)
 public String printWelcomeAdmin() {
  return "admin";
 }

 @RequestMapping(value = "/login", method = RequestMethod.GET)
 public String getLoginPage(Model model) {
  return "login";
 }

 @RequestMapping(value = "/home", method = RequestMethod.GET)
 public String getHomePage(Model model) {
  return "hello";
 }

 @RequestMapping(value = "/accessdenied", method = RequestMethod.GET)
 public String getFailurePage(Model model) {
  return "failure";
 }

 @RequestMapping(value = "/logout", method = RequestMethod.GET)
 public String getLogoutPage(Model model, HttpServletRequest req) {
  req.getSession().invalidate();
  return "logout";
 }

 @RequestMapping(value = "/user", method = RequestMethod.POST)
 @ResponseBody
 String saveUser(@RequestBody MyUser user, HttpServletResponse response) {
  System.out.println("User:" + user.getUsername());
  userService.saveUser(user);
  response.setStatus(201);
  return "success";
 }
}

AuthService

package com.spring.security.service.impl;

import java.util.ArrayList;
import java.util.Collection;

import org.springframework.beans.factory.annotation.Autowired;
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 org.springframework.transaction.annotation.Transactional;

import com.spring.security.dao.IUserDao;
import com.spring.security.domain.MyUser;
import com.spring.security.service.IAuthService;

@Service
public class AuthServiceImpl implements IAuthService, UserDetailsService {

 @Autowired
 IUserDao userDao;

 @Transactional
 @Override
 public UserDetails loadUserByUsername(String username)
   throws UsernameNotFoundException {

  MyUser details = userDao.getUser(username);
  Collection authorities = new ArrayList();
  SimpleGrantedAuthority userAuthority = new SimpleGrantedAuthority(
    "ROLE_USER");
  SimpleGrantedAuthority adminAuthority = new SimpleGrantedAuthority(
    "ROLE_ADMIN");
  if (details.getRole().equals("user"))
   authorities.add(userAuthority);
  else if (details.getRole().equals("admin")) {
   authorities.add(userAuthority);
   authorities.add(adminAuthority);
  }
  UserDetails user = new User(details.getUsername(),
    details.getPassword(), true, true, true, true, authorities);
  return user;
 }

}

UserSevice

package com.spring.security.service.impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.spring.security.dao.IUserDao;
import com.spring.security.domain.MyUser;
import com.spring.security.service.IUserService;

@Service
public class UserServiceImpl implements IUserService {

 @Autowired
 IUserDao userDao;

 @Autowired
 private BCryptPasswordEncoder passwordEncoder;

 @Transactional
 @Override
 public void saveUser(MyUser user) {
  String password = user.getPassword();
  String encryptedPassword = passwordEncoder.encode(password);
  user.setPassword(encryptedPassword);
  userDao.saveUser(user);
 }

}
As seen above, the password is first encrypted and then passed to the userDao to get saved.


UserDao

package com.spring.security.dao.impl;

import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Restrictions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import com.spring.security.dao.IUserDao;
import com.spring.security.domain.MyUser;

@Repository
public class UserDaoImpl implements IUserDao {

 @Autowired
 private SessionFactory sessionFactory;

 @Override
 public MyUser getUser(String username) {
  Session session = sessionFactory.getCurrentSession();
  Criteria criteria = session.createCriteria(MyUser.class);
  criteria.add(Restrictions.eq("username", username));
  MyUser user = (MyUser) criteria.uniqueResult();
  return user;
 }

 @Override
 public void saveUser(MyUser user) {
  sessionFactory.getCurrentSession().save(user);
 }
}

You can view/download the complete source code from here.

Thanks !