Remember-Me in Spring Security Example
November 26, 2019
This page will walk through Remember-Me in Spring Security example. Remember-me functionality enables a user to keep logged-in. In our application we provide an option, usually checkbox, to the user to select remember-me and if the user checks it then after successful login, Spring application sends a remember-me cookie to the browser in addition to session cookie. Once the session cookie is expired, then if user accesses the secure page, it will automatically be logged-in using remember-me cookie. Spring performs remember-me functionality by creating token using authentication details. There are two approach to achieve remember-me functionality, one is simple hash-based token approach and another is persistent token approach. Here on this page we will provide example using both approach with annotation as well as XML configuration. In our example, we are using spring 4 security. CSRF is enabled by default in JavaConfig as well as XML configuration. When we use spring form tag in our UI and JavaConfig is annotated with @EnableWebSecurity
, we need not to include HTML hidden input field for CSRF parameter, it will be automatically included at run time.
Contents
- Software Used
- "Remember Me" in Spring Security
- Create Login and Logout Views
- Hash-Based Token Approach using Annotation
- Hash-Based Token Approach using XML Configuration
- Create Database for Persistent Token Approach
- Persistent Token Approach using Annotation
- Persistent Token Approach using XML Configuration
- Run Application
- Download Complete Source Code
Software Used
Find the software used in our demo.1. Java 8
2. Spring 4.2.5.RELEASE
3. Spring-Security 4.0.3.RELEASE
4. Tomcat 8
5. Gradle
6. Eclipse
7. MySQL
"Remember Me" in Spring Security
Remember-me is a functionality using which a user can be identified between sessions. It means once the user logins in web application with remember-me option, he will be able to access secure application even after session expired. This is also called persistent-login authentication. To achieve it spring sends an additional cookie to the browser and once the session is expired, user is automatically authenticated and is logged in using remember-me cookie. While configuring remember me functionality we can also set the remember me cookie expiry time. To achieve this functionality, spring provides below approaches.Simple hash-based token approach : Hashing strategy is used to create token. The token is created using username, expiration time, password and a key. After successful authentication a cookie using token value is sent to the browser. This approach has security issue and is commonly not recommended.
Persistent token approach : In this approach we need to create a table with name persistent_logins where token is saved. In remember-me configuration, we need to configure datasource while using XML and
PersistentTokenRepository
class while using JavaConfig.
Create Login and Logout Views
Find the login page.customLogin.jsp
<%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%> <html> <head> <title>Spring Security Example</title> </head> <body> <h3>Spring Security Example</h3> <font color="red"> ${SPRING_SECURITY_LAST_EXCEPTION.message} </font> <form:form action="${pageContext.request.contextPath}/appLogin" method="POST"> Enter UserName: <input type="text" name="app_username"/><br/><br/> Enter Password: <input type="password" name="app_password"/> <br/><br/> <input type='checkbox' name="remember-me-param"/>Remember Me? <br/> <input type="submit" value="Login"/> </form:form> <body> </html>
home.jsp
<%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <html> <head> <title>Spring Security Example</title> </head> <body> <form:form action="${pageContext.request.contextPath}/appLogout" method="POST"> <input type="submit" value="Logout"/> </form:form> Welcome to you. Find secure data. <br/> <c:forEach var="ob" items="${list}"> <br/><c:out value="${ob.stdId}"></c:out> <c:out value="${ob.stdName}"></c:out> </c:forEach> </body> </html>
Hash-Based Token Approach using Annotation
Find the JavaConfig for hash-based token approach.SecurityConfig.java
package com.concretepage.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { //authorize requests http.authorizeRequests(). antMatchers("/app/secure/**"). access("hasRole('ROLE_ADMIN')"); //login configuration http.formLogin(). loginPage("/app"). loginProcessingUrl("/appLogin"). usernameParameter("app_username"). passwordParameter("app_password"). defaultSuccessUrl("/app/secure/home"); //remember me configuration http.rememberMe(). key("rem-me-key"). rememberMeParameter("remember-me-param"). rememberMeCookieName("my-remember-me"). tokenValiditySeconds(86400); //logout configuration http.logout(). logoutUrl("/appLogout"). logoutSuccessUrl("/app"); } @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication().withUser("ram").password("ram123").roles("ADMIN"); } }
//remember me configuration http.rememberMe(). key("rem-me-key"). rememberMeParameter("remember-me-param"). rememberMeCookieName("my-remember-me"). tokenValiditySeconds(86400);
rememberMe(): It returns
RememberMeConfigurer
class using which remember-me configuration is done.
key(): It specifies the key to identify tokens.
rememberMeParameter(): It specifies the name attribute which we use to create HTML checkbox.
rememberMeCookieName(): It specifies the cookie name stored in the browser.
tokenValiditySeconds(): Specifies the time in seconds after which is token is expired.
Now find the print screen of project structure in eclipse.

package com.concretepage.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.view.InternalResourceViewResolver; @Configuration @ComponentScan("com.concretepage") @Import(SecurityConfig.class) @EnableWebMvc public class AppConfig { @Bean public InternalResourceViewResolver viewResolver() { InternalResourceViewResolver resolver = new InternalResourceViewResolver(); resolver.setPrefix("/WEB-INF/secure/"); resolver.setSuffix(".jsp"); return resolver; } }
package com.concretepage.config; import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer; public class SecurityInitializer extends AbstractSecurityWebApplicationInitializer { }
package com.concretepage.config; import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer; public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class<?>[] getRootConfigClasses() { return new Class[] { AppConfig.class }; } @Override protected Class<?>[] getServletConfigClasses() { return null; } @Override protected String[] getServletMappings() { return new String[] { "/" }; } }
package com.concretepage.controller; import java.util.ArrayList; import java.util.List; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import com.concretepage.bean.Student; @Controller @RequestMapping("/app") public class StudentController { @RequestMapping public String login() { return "customLogin"; } @RequestMapping("/secure/home") public String homePage(Model model) { Listlist = new ArrayList<>(); list.add(new Student(1, "Shankar")); list.add(new Student(2, "Vishnu")); list.add(new Student(3, "Bhahma")); model.addAttribute("list", list); return "home"; } }
package com.concretepage.bean; public class Student { private int stdId; private String stdName; public Student(int stdId, String stdName) { this.stdId = stdId; this.stdName = stdName; } public int getStdId() { return stdId; } public void setStdId(int stdId) { this.stdId = stdId; } public String getStdName() { return stdName; } public void setStdName(String stdName) { this.stdName = stdName; } }
apply plugin: 'java' apply plugin: 'eclipse' apply plugin: 'war' archivesBaseName = 'spring-security' version = '1' repositories { mavenCentral() } dependencies { compile 'org.springframework.boot:spring-boot-starter-web:1.3.3.RELEASE' compile 'org.springframework.boot:spring-boot-starter-security:1.3.3.RELEASE' compile 'jstl:jstl:1.2' providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat:1.3.3.RELEASE' }
Hash-Based Token Approach using XML Configuration
Find the XML configuration for hash-based token approach.security-config.xml
<?xml version="1.0" encoding="UTF-8"?> <beans:beans xmlns="http://www.springframework.org/schema/security" xmlns:beans="http://www.springframework.org/schema/beans" 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"> <http auto-config="true"> <intercept-url pattern="/app/secure/**" access="hasRole('ROLE_ADMIN')" /> <form-login login-page="/app" login-processing-url="/appLogin" username-parameter="app_username" password-parameter="app_password" default-target-url="/app/secure/home"/> <remember-me key="rem-me-key" remember-me-parameter="remember-me-param" remember-me-cookie="my-remember-me" token-validity-seconds="86400"/> <logout logout-url="/appLogout" logout-success-url="/app"/> </http> <authentication-manager> <authentication-provider> <user-service> <user name="ram" password="ram123" authorities="ROLE_ADMIN" /> </user-service> </authentication-provider> </authentication-manager> </beans:beans>
<remember-me key="rem-me-key" remember-me-parameter="remember-me-param" remember-me-cookie="my-remember-me" token-validity-seconds="86400"/>
remember-me: XML security tag to configure remember-me.
key: It defines a key to identify cookie.
remember-me-parameter : Defines the name attribute which is used to create HTML checkbox.
remember-me-cookie : Defines the cookie name stored in browser.
token-validity-seconds : Specifies the time in seconds after which is token is expired.
Now find the print screen of project structure in eclipse.

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" 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/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="com.concretepage" /> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/secure/"/> <property name="suffix" value=".jsp"/> </bean> <import resource="security-config.xml"/> </beans>
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0"> <display-name>Spring Security Example</display-name> <servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <context-param> <param-name>contextConfigLocation</param-name> <param-value> /WEB-INF/dispatcher-servlet.xml </param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- Spring Security Configuration --> <filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>
Create Database for Persistent Token Approach
In persistent token approach, we need to create a table with name persistent_logins as follows.Table: persistent_logins
CREATE TABLE `persistent_logins` ( `username` VARCHAR(64) NOT NULL, `series` VARCHAR(64) NOT NULL, `token` VARCHAR(64) NOT NULL, `last_used` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`series`) ) COLLATE='latin1_swedish_ci' ENGINE=InnoDB;
-- Dumping database structure for mydb CREATE DATABASE IF NOT EXISTS `mydb`; USE `mydb`; -- Dumping structure for table mydb.authorities CREATE TABLE IF NOT EXISTS `authorities` ( `username` varchar(50) NOT NULL, `authority` varchar(50) NOT NULL, UNIQUE KEY `ix_auth_username` (`username`,`authority`), CONSTRAINT `fk_authorities_users` FOREIGN KEY (`username`) REFERENCES `users` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; -- Dumping data for table mydb.authorities: ~1 rows (approximately) INSERT INTO `authorities` (`username`, `authority`) VALUES ('ram', 'ROLE_ADMIN'); -- Dumping structure for table mydb.persistent_logins CREATE TABLE IF NOT EXISTS `persistent_logins` ( `username` varchar(64) NOT NULL, `series` varchar(64) NOT NULL, `token` varchar(64) NOT NULL, `last_used` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`series`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; -- Dumping structure for table mydb.users CREATE TABLE IF NOT EXISTS `users` ( `username` varchar(50) NOT NULL, `password` varchar(50) NOT NULL, `enabled` tinyint(1) NOT NULL, PRIMARY KEY (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; -- Dumping data for table mydb.users: ~1 rows (approximately) INSERT INTO `users` (`username`, `password`, `enabled`) VALUES ('ram', 'e17e5425a021224b63e91499ff8ac491c87567db', 1);
Persistent Token Approach using Annotation
Find the JavaConfig to configure remember-me using persistent token approach .SecurityConfig.java
package com.concretepage.config; import javax.sql.DataSource; import org.apache.commons.dbcp.BasicDataSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; import org.springframework.core.env.Environment; import org.springframework.security.authentication.encoding.ShaPasswordEncoder; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl; import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository; import com.concretepage.service.AuthenticationService; @Configuration @EnableWebSecurity @PropertySource("classpath:jdbc.properties") public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired Environment env; @Autowired AuthenticationService authenticationService; @Override protected void configure(HttpSecurity http) throws Exception { //authorize requests http.authorizeRequests(). antMatchers("/app/secure/**"). access("hasRole('ROLE_ADMIN')"); //login configuration http.formLogin(). loginPage("/app"). loginProcessingUrl("/appLogin"). usernameParameter("app_username"). passwordParameter("app_password"). defaultSuccessUrl("/app/secure/home"); //remember me configuration http.rememberMe(). tokenRepository(persistentTokenRepository()). rememberMeParameter("remember-me-param"). rememberMeCookieName("my-remember-me"). tokenValiditySeconds(86400); //logout configuration http.logout(). logoutUrl("/appLogout"). logoutSuccessUrl("/app"); } @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { ShaPasswordEncoder encoder = new ShaPasswordEncoder(); auth.userDetailsService(authenticationService).passwordEncoder(encoder); } @Bean public DataSource getDataSource() { BasicDataSource dataSource = new BasicDataSource(); dataSource.setDriverClassName(env.getProperty("jdbc.driverClassName")); dataSource.setUrl(env.getProperty("jdbc.url")); dataSource.setUsername(env.getProperty("jdbc.username")); dataSource.setPassword(env.getProperty("jdbc.password")); return dataSource; } @Bean public PersistentTokenRepository persistentTokenRepository() { JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl(); tokenRepository.setDataSource(getDataSource()); return tokenRepository; } }
//remember me configuration http.rememberMe(). tokenRepository(persistentTokenRepository()). rememberMeParameter("remember-me-param"). rememberMeCookieName("my-remember-me"). tokenValiditySeconds(86400);
PersistentTokenRepository
which is used to query persistent_logins table.
Now find the print screen of project structure in eclipse.

package com.concretepage.bean; public class UserInfo { private String username; private String password; private String role; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getRole() { return role; } public void setRole(String role) { this.role = role; } }
package com.concretepage.dao; import java.sql.ResultSet; import java.sql.SQLException; import javax.sql.DataSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import org.springframework.stereotype.Repository; import com.concretepage.bean.UserInfo; @Repository public class UserDAO { private JdbcTemplate jdbcTemplate; @Autowired public void setDataSource(DataSource dataSource) { this.jdbcTemplate = new JdbcTemplate(dataSource); } public UserInfo getUserInfo(String username){ String sql = "SELECT u.username name, u.password pass, a.authority role FROM "+ "users u INNER JOIN authorities a on u.username=a.username WHERE "+ "u.enabled =1 and u.username = ?"; UserInfo userInfo = (UserInfo)jdbcTemplate.queryForObject(sql, new Object[]{username}, new RowMapper<UserInfo>() { public UserInfo mapRow(ResultSet rs, int rowNum) throws SQLException { UserInfo user = new UserInfo(); user.setUsername(rs.getString("name")); user.setPassword(rs.getString("pass")); user.setRole(rs.getString("role")); return user; } }); return userInfo; } }
package com.concretepage.service; import java.util.Arrays; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import com.concretepage.bean.UserInfo; import com.concretepage.dao.UserDAO; @Service public class AuthenticationService implements UserDetailsService { @Autowired private UserDAO userDAO; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { UserInfo userInfo = userDAO.getUserInfo(username); GrantedAuthority authority = new SimpleGrantedAuthority(userInfo.getRole()); UserDetails userDetails = (UserDetails)new User(userInfo.getUsername(), userInfo.getPassword(), Arrays.asList(authority)); return userDetails; } }
jdbc.driverClassName=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/mydb jdbc.username=root jdbc.password=
apply plugin: 'java' apply plugin: 'eclipse' apply plugin: 'war' archivesBaseName = 'spring-security' version = '1' repositories { mavenCentral() } dependencies { compile 'org.springframework.boot:spring-boot-starter-web:1.3.3.RELEASE' compile 'org.springframework.boot:spring-boot-starter-security:1.3.3.RELEASE' compile 'org.springframework.boot:spring-boot-starter-jdbc:1.3.3.RELEASE' compile 'jstl:jstl:1.2' compile 'mysql:mysql-connector-java:5.1.31' compile 'commons-dbcp:commons-dbcp:1.4' providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat:1.3.3.RELEASE' }
Persistent Token Approach using XML Configuration
Find the XML configuration to configure remember-me using persistent token approach .security-config.xml
<?xml version="1.0" encoding="UTF-8"?> <beans:beans xmlns="http://www.springframework.org/schema/security" xmlns:beans="http://www.springframework.org/schema/beans" 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"> <http auto-config="true"> <intercept-url pattern="/app/secure/**" access="hasRole('ROLE_ADMIN')" /> <form-login login-page="/app" login-processing-url="/appLogin" username-parameter="app_username" password-parameter="app_password" default-target-url="/app/secure/home"/> <remember-me data-source-ref="dataSource" remember-me-parameter="remember-me-param" remember-me-cookie="my-remember-me" token-validity-seconds="86400"/> <logout logout-url="/appLogout" logout-success-url="/app"/> </http> <authentication-manager> <authentication-provider> <password-encoder hash="sha"/> <jdbc-user-service data-source-ref="dataSource" authorities-by-username-query="SELECT username, authority FROM authorities WHERE username = ?" users-by-username-query="SELECT username, password, enabled FROM users WHERE username = ?"/> </authentication-provider> </authentication-manager> <beans:bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"> <beans:property name="driverClassName" value="com.mysql.jdbc.Driver"/> <beans:property name="url" value="jdbc:mysql://localhost:3306/mydb"/> <beans:property name="username" value="root"/> <beans:property name="password" value=""/> </beans:bean> </beans:beans>
<remember-me data-source-ref="dataSource" remember-me-parameter="remember-me-param" remember-me-cookie="my-remember-me" token-validity-seconds="86400"/>
Now find the print screen of project structure in eclipse.

Run Application
To run the application find the below steps.1. Download the project source code.
2. Go to root directory of the project using command prompt.
3. Run gradle clean build
4. We will get WAR file in build/lib directory.
5. Deploy WAR file in tomcat.
6. Run the application using URL http://localhost:8080/spring-security-1/app/secure/home
Page will be redirected to login page.




Now I am done. Happy spring security learning!