Spring Security – Remember Me
In order to remember the users on the computer while logging into a website, via Spring Security we can maintain that by using a “Remember Me” option. Spring security sends a cookie to the browser. This will have a specific time limit and till that time the cookie is valid and in that timeframe, for the given URL, automatic login is possible. Let us see via a sample application code to see the working functionality.
Example Project
Spring Security is providing two approaches for implementing remember-me
- Hash-Based Token Approach
- Persistent Token Approach.
In our example, we are taking the Persistent Token Approach in which a database or other persistent storage mechanism is used and it is helpful to store the generated tokens.
Project Structure:
This is a maven-driven project. Let us see the pom.xml
XML
< project xmlns = "http://maven.apache.org/POM/4.0.0" xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> < modelVersion >4.0.0</ modelVersion > < groupId >com.gfg.springsecurity</ groupId > < artifactId >spring-security-remember-me-sample-application</ artifactId > < version >0.0.1-SNAPSHOT</ version > < name >spring-security-remember-me-sample-application</ name > < packaging >war</ packaging > < properties > <!-- It is good to use the later versions to get the full supportivity --> < maven.compiler.source >1.8</ maven.compiler.source > < maven.compiler.target >1.8</ maven.compiler.target > < failOnMissingWebXml >false</ failOnMissingWebXml > </ properties > < dependencies > < dependency > < groupId >org.springframework.security</ groupId > < artifactId >spring-security-web</ artifactId > < version >5.0.0.RELEASE</ version > </ dependency > < dependency > < groupId >org.springframework.security</ groupId > < artifactId >spring-security-config</ artifactId > < version >5.0.0.RELEASE</ version > </ dependency > < dependency > < groupId >org.springframework</ groupId > < artifactId >spring-webmvc</ artifactId > < version >5.0.2.RELEASE</ version > </ dependency > < dependency > < groupId >org.springframework</ groupId > < artifactId >spring-jdbc</ artifactId > < version >5.0.2.RELEASE</ version > </ dependency > < dependency > < groupId >javax.servlet</ groupId > < artifactId >javax.servlet-api</ artifactId > < version >3.1.0</ version > < scope >provided</ scope > </ dependency > < dependency > < groupId >javax.servlet.jsp</ groupId > < artifactId >javax.servlet.jsp-api</ artifactId > < version >2.3.1</ version > < scope >provided</ scope > </ dependency > < dependency > < groupId >javax.servlet.jsp.jstl</ groupId > < artifactId >javax.servlet.jsp.jstl-api</ artifactId > < version >1.2.1</ version > </ dependency > < dependency > < groupId >taglibs</ groupId > < artifactId >standard</ artifactId > < version >1.1.2</ version > </ dependency > < dependency > < groupId >org.apache.commons</ groupId > < artifactId >commons-dbcp2</ artifactId > < version >2.1.1</ version > </ dependency > < dependency > < groupId >mysql</ groupId > < artifactId >mysql-connector-java</ artifactId > < version >6.0.6</ version > </ dependency > </ dependencies > < build > < plugins > <!-- Maven jetty plugin for testing war --> < plugin > < groupId >org.eclipse.jetty</ groupId > < artifactId >jetty-maven-plugin</ artifactId > < version >9.4.8.v20171121</ version > </ plugin > <!-- To avoid errors, it is mandatory to add this plugin --> < plugin > < groupId >org.apache.maven.plugins</ groupId > < artifactId >maven-war-plugin</ artifactId > < version >3.3.1</ version > </ plugin > </ plugins > </ build > </ project > |
On the database side, we are using MySQL. Hence those configurations are given in pom.xml. To test the application for remembering, we need to have some sample tables in the database. Let the sample database is ‘w3wiki’. sample tables are ‘users’ and ‘authorities’. For ‘remember me’ authentication, we need ‘persistent_logins’ as well for storing the generated tokens
use w3wiki; -- users create table users( username varchar(50) not null primary key, password varchar(100) not null, enabled boolean not null ); -- authorities create table authorities( username varchar(50) not null, authority varchar(50) not null, constraint fk_authorities_users foreign key(username) references users(username) ); create unique index ix_auth_username on geekAuthorities (username,authority); create table persistent_logins( username varchar(50) not null, series varchar(64) primary key, token varchar(64) not null, last_used timestamp not null );
Let us insert a few data into the users and authorities table for testing purposes
-- Let us create a user with admin and password as password@123 -- While storing into the database let us store as encoded password with BCryptPasswordEncoder -- For password@123, it will be $2a$10$USD5XrNWIpf2sLnGJ62/v.hTtSIY1vdeF7v8Y4YaNJhTftbX1HBwi insert into users(username,password,enabled) values('admin','$2a$10$hbxecwitQQ.dDT4JOFzQAulNySFwEpaFLw38jda6Td.Y/cOiRzDFu',true); insert into authorities(username,authority) values('admin','ROLE_ADMIN');
To get the encoded password, by using a sample code, we can get it
Java
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; //..... BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); // Specify the required password here String password = "password@123" ; String encodedPassword = passwordEncoder.encode(password); //--- |
Now let us check the database connectivity information. It is available under the src/main/resources folder
src/main/resources/database.properties
mysql.driver=com.mysql.jdbc.Driver mysql.jdbcUrl=jdbc:mysql://localhost:3306/w3wiki?serverTimezone=UTC # using w3wiki mysql.username=root # Change the appropriate username here mysql.password=**** # Change the appropriate password here
Let us start by creating the @Configuration class and inside that define the @Bean method for DataSource
ApplicationConfiguration.java
Java
import javax.sql.DataSource; import org.apache.commons.dbcp2.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; @Configuration @PropertySource ( "classpath:database.properties" ) public class ApplicationConfiguration { @Autowired private Environment environment; @Bean public DataSource getDataSource() { BasicDataSource basicDataSource = new BasicDataSource(); basicDataSource.setDriverClassName(environment.getProperty( "mysql.driver" )); basicDataSource.setUrl(environment.getProperty( "mysql.jdbcUrl" )); basicDataSource.setUsername(environment.getProperty( "mysql.username" )); basicDataSource.setPassword(environment.getProperty( "mysql.password" )); return basicDataSource; } } |
Spring Security configuration
- Creation of a springSecurityFilterChain Servlet Filter is needed for protecting and validating all URLs by creating a @Configuration class.
- Need to register the springSecurityFilterChain filter with war.
- rememberMe() method of the HttpSecurity class is required to enable remember-me authentication
- Invoke the tokenRepository() method with the PersistentTokenRepository argument and store the generated tokens in the database table
Let us see how to do these
WebSecurityConfiguration.java
Java
import javax.sql.DataSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; 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.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl; import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository; @EnableWebSecurity public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { @Autowired private DataSource dataSource; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.jdbcAuthentication().dataSource(dataSource) .usersByUsernameQuery( "select username, password, enabled" + " from users where username=?" ) .authoritiesByUsernameQuery( "select username,authority " + "from authorities where username=?" ) .passwordEncoder(passwordEncoder()); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests().anyRequest().hasAnyRole( "ADMIN" , "USER" ) .and() .authorizeRequests().antMatchers( "/login**" ).permitAll() .and() .formLogin().loginPage( "/login" ).loginProcessingUrl( "/loginAction" ).permitAll() .and() .logout().logoutSuccessUrl( "/login" ).permitAll() .and() .rememberMe().rememberMeParameter( "remember-me" ).tokenRepository(tokenRepository()) .and() .csrf().disable(); } @Bean public PersistentTokenRepository tokenRepository() { JdbcTokenRepositoryImpl jdbcTokenRepositoryImpl= new JdbcTokenRepositoryImpl(); jdbcTokenRepositoryImpl.setDataSource(dataSource); return jdbcTokenRepositoryImpl; } } |
SecurityWebApplicationInitializer.java
Java
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer; public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer { } |
Spring MVC Configuration is getting done by using JSP Views for the view part
WebConfiguration.java
Java
import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; import org.springframework.web.servlet.config.annotation.ViewResolverRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration @EnableWebMvc @ComponentScan (basePackages = { "com.gfg.spring.controller" }) public class WebConfiguration implements WebMvcConfigurer { @Override public void configureViewResolvers(ViewResolverRegistry registry) { registry.jsp().prefix( "/WEB-INF/views/" ).suffix( ".jsp" ); } @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController( "/login" ).setViewName( "login" ); } } |
Servlet container Initialization and configuration can be seen by using
MvcWebApplicationInitializer.java
Java
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer; public class MvcWebApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { // Load database and spring security configuration @Override protected Class<?>[] getRootConfigClasses() { return new Class[] { ApplicationConfiguration. class , WebSecurityConfiguration. class }; } // Load spring web configuration @Override protected Class<?>[] getServletConfigClasses() { return new Class[] { WebConfiguration. class }; } @Override protected String[] getServletMappings() { return new String[] { "/" }; } } |
Let us see the controller class now
SampleContoller.java
Java
import java.security.Principal; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; @Controller public class SampleContoller { @GetMapping ( "/" ) public String index(Model model, Principal principal) { model.addAttribute( "message" , "Welcome! You are logged by using " + principal.getName() + " username" ); return "index" ; } } |
Let us see the JSP view part now
login.jsp
HTML
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <%@taglib uri="http://www.springframework.org/tags" prefix="spring"%> <!DOCTYPE html> < html > < head > < meta http-equiv = "Content-Type" content = "text/html; charset=ISO-8859-1" > < title >Spring Security</ title > < style > table#sample tr:nth-child(odd) { background-color: #0074ab ; color: yellow ; } table#sample tr:nth-child(even) { background-color: #e6f7ff ; } table#sample { border-collapse: collapse ; } table#sample td { padding: 5px ; } table#sample caption { font-style: italic ; background-color: black ; color: white ; } </ style > </ head > < body background = "#FFFFFF" > < center > < h1 >Spring Security - Remember Me Example</ h1 > < h4 >Sample Login Form</ h4 > < form action='<spring:url value = "/loginAction" />' method="post"> < table id = "sample" > < tr > < td >Username</ td > < td >< input type = "text" name = "username" ></ td > </ tr > < tr > < td >Password</ td > < td >< input type = "password" name = "password" ></ td > </ tr > < tr > < td >< input type = "checkbox" name = "remember-me" ></ td > < td >Remember me on this Computer</ td > </ tr > < tr > < td align = "center" colspan = "2" >< button type = "submit" >Login</ button ></ td > </ tr > </ table > </ form > < br /> </ center > </ body > </ html > |
index.jsp
HTML
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <!DOCTYPE html> < html > < head > < meta http-equiv = "Content-Type" content = "text/html; charset=ISO-8859-1" > < title >Spring Security</ title > </ head > < body > < h1 >Spring Security - Remember Me Example</ h1 > < h2 >${message}</ h2 > < form action = "/logout" method = "post" > < input value = "Logout" type = "submit" > </ form > </ body > </ html > |
As this is the maven project, first let us build the application from the command prompt as follows
mvn clean install
Run the application by using
mvn jetty:run
Let us test now by hitting http://localhost:8080/
admin/password@123 has to be given as credentials. As it is the user available in the user’s table and that password is kept in an encoded way. As the remember me option is selected, in the database, we can see an entry under ‘persistent_logins’
At the same time, we can check the same under cookies as well. As the chrome browser is used, let us check that via chrome browser settings options
As of now, the credentials are stored in cookies and also in DB due to the persistence mechanism, after we close the browser and open it again, we can able to see the index page when we hit http://localhost:8080. This is the advantage of remember me option. This will be possible till the expiry time. Moreover cookies data should not be cleared. Here for our example, till June 24, the cookies are not cleared, we can see the ‘Remember Me’ functionality enabled.
Contact Us