Open In App

Spring Security – Remember Me

Last Updated : 03 Nov, 2022
Improve
Improve
Like Article
Like
Save
Share
Report

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:

Project Structure

 

This is a maven-driven project. Let us see the pom.xml

XML




         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 ‘geeksforgeeks’. sample tables are ‘users’ and ‘authorities’. For ‘remember me’ authentication, we need ‘persistent_logins’ as well for storing the generated tokens

use geeksforgeeks;
-- 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/geeksforgeeks?serverTimezone=UTC # using geeksforgeeks
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

  1. Creation of a springSecurityFilterChain Servlet Filter is needed for protecting and validating all URLs by creating a @Configuration class.
  2. Need to register the springSecurityFilterChain filter with war. 
  3. rememberMe() method of the HttpSecurity class is required to enable remember-me authentication 
  4. 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.



Like Article
Suggest improvement
Previous
Next
Share your thoughts in the comments

Similar Reads