Spring Security Remember Me Hashing Authentication Example
In this tutorial we demonstrate how to create a Spring Security Remember Me Hashing Authentication application. Remember me authentication is a feature that allows web sites to remember the identity of a user between sessions. Spring security provides two remember-me implementation. One uses hashing to preserve the security of cookie-based tokens which we’ll tackle in this tutorial. The second uses a database or other persistent storage mechanism to store the generated tokens.
Remember Me Hashed Based Token Approach
When the user enables remember me authentication, a cookie is created and passed upon subsequent logins. This approach uses hashing to achieve a useful remember-me strategy. The cookie is composed as follows:
base64(username + ":" + expirationTime + ":" +
md5Hex(username + ":" + expirationTime + ":" password + ":" + key))
username: As identifiable to the UserDetailsService
password: That matches the one in the retrieved 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
Note: The remember me token is valid only for the specified
expirationTime
and provided that theusername
,password
andkey
does not change. Warning: This is a potentially security issue. When the remember me token is captured by a malicious user-agent this user-agent is able to use the token until it expires. If better security is needed you should use thepersistent remember-me token
approach.
Project Structure
Let’s start by looking at the project structure.
Maven Dependencies
We use Apache Maven to manage our project dependencies. Make sure the following dependencies reside on the class-path.
<?xml version="1.0" encoding="UTF-8"?>
<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.memorynotfound.spring.security</groupId>
<artifactId>hashing-remember-me</artifactId>
<version>1.0.0-SNAPSHOT</version>
<url>https://memorynotfound.com</url>
<name>Spring Security - ${project.artifactId}</name>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.8.RELEASE</version>
</parent>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity4</artifactId>
</dependency>
<!-- bootstrap and jquery -->
<dependency>
<groupId>org.webjars</groupId>
<artifactId>bootstrap</artifactId>
<version>3.3.7</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.2.1</version>
</dependency>
<!-- testing -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Spring Security Remember Me Hashing Authentication Configuration
To enable remember me hashing authentication configuration we need to register this with spring. In the following section, we demonstrate both Java -and XML Configuration.
Spring Java Configuration
Here we are configuring the Remember Me authentication using Java Configuration. By extending our Spring Configuration class with WebSecurityConfigurerAdapter
we can simply configure the remember me authentication in the configure(HttpSecurity http)
method. We need to configure a secure and unique key. This key is typically a strong
and unique cipher. We can optionally configure the remember me cookie name and set the tokens validity period. This defaults to 2 weeks.
package com.memorynotfound.spring.security.config;
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.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers(
"/js/**",
"/css/**",
"/img/**",
"/webjars/**");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.invalidateHttpSession(true)
.clearAuthentication(true)
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/login?logout")
.permitAll()
.and()
.rememberMe()
.key("unique-and-secret")
.rememberMeCookieName("remember-me-cookie-name")
.tokenValiditySeconds(24 * 60 * 60);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("user").password("password").roles("USER");
}
}
Spring XML Configuration
The spring-security-config.xml
is located in the src/main/resources/
folder and is the equivalent Spring XML Configuration.
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<http>
<intercept-url pattern="/login" access="permitAll()"/>
<intercept-url pattern="/js/**" access="permitAll()"/>
<intercept-url pattern="/css/**" access="permitAll()"/>
<intercept-url pattern="/img/**" access="permitAll()"/>
<intercept-url pattern="/webjars/**" access="permitAll()"/>
<intercept-url pattern="/**" access="isAuthenticated()"/>
<form-login
login-page="/login"/>
<logout
invalidate-session="true"
logout-url="/logout"
logout-success-url="/login?logout"/>
<remember-me
key="unique-and-secret"
remember-me-cookie="remember-me-cookie-name"
token-validity-seconds="86400"/>
</http>
<authentication-manager>
<authentication-provider>
<user-service>
<user name="user"
password="password"
authorities="ROLE_USER" />
</user-service>
</authentication-provider>
</authentication-manager>
</beans:beans>
Creating Controller
We created some simple navigation controllers.
package com.memorynotfound.spring.security.web;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class HomeController {
@GetMapping("/")
public String greeting(){
return "index";
}
@GetMapping("/login")
public String login() {
return "login";
}
}
Spring Boot
We use Spring Boot to start our application.
package com.memorynotfound.spring.security;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
// uncomment if you want to use Spring Security XML Configuration
// @ImportResource("classpath:spring-security-config.xml")
public class Run {
public static void main(String[] args) {
SpringApplication.run(Run.class, args);
}
}
Thymeleaf Templates
We used Thymeleaf to create our views. These templates uses bootstrap
and jquery
loaded from the org.webjars
.
Creating the login page
The login.html
page is located in the src/main/resources/templates
folder. In the form we created a remember-me
checkbox. When the user enables remember me authentication a cookie is passed to the browser which will expire in the specified amount. When the user accesses the page between sessions, Spring Security automatically authenticates the uses based on the remember-me token.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<link rel="stylesheet" type="text/css" th:href="@{/webjars/bootstrap/3.3.7/css/bootstrap.min.css}"/>
<link rel="stylesheet" type="text/css" th:href="@{/css/main.css}"/>
<title>Login</title>
</head>
<body>
<div class="container">
<div class="row">
<div class="col-md-4 col-md-offset-4">
<div class="panel panel-default">
<div class="panel-body">
<div class="text-center">
<h3><i class="glyphicon glyphicon-lock" style="font-size:2em;"></i></h3>
<h2 class="text-center">Login</h2>
<div class="panel-body">
<div th:if="${param.error}">
<div class="alert alert-danger">
Invalid username or password.
</div>
</div>
<div th:if="${param.logout}">
<div class="alert alert-info">
You have been logged out.
</div>
</div>
<form th:action="@{/login}" method="post">
<div class="form-group">
<div class="input-group">
<span class="input-group-addon">@</span>
<input id="username"
name="username"
autofocus="autofocus"
class="form-control"
placeholder="Username"/>
</div>
</div>
<div class="form-group">
<div class="input-group">
<span class="input-group-addon">
<i class="glyphicon glyphicon-lock"></i>
</span>
<input id="password"
name="password"
class="form-control"
placeholder="Password"
type="password"/>
</div>
</div>
<div class="form-group">
<label>
<input id="remember-me"
name="remember-me"
type="checkbox"/> Remember me
</label>
</div>
<div class="form-group">
<button type="submit" class="btn btn-success btn-block">Login</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script type="text/javascript" th:src="@{/webjars/jquery/3.2.1/jquery.min.js/}"></script>
<script type="text/javascript" th:src="@{/webjars/bootstrap/3.3.7/js/bootstrap.min.js}"></script>
</body>
</html>
Creating a secured page
The index.html
page is located in the src/main/resources/templates
folder.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<link rel="stylesheet" type="text/css" th:href="@{/webjars/bootstrap/3.3.7/css/bootstrap.min.css}"/>
<link rel="stylesheet" type="text/css" th:href="@{/css/main.css}"/>
<title>Registration</title>
</head>
<body>
<div class="container">
<h1>Spring Security Remember Me Hashing Configuration Example</h1>
<div sec:authorize="isRememberMe()">
The user: <span sec:authentication="name"></span> is logged in by "Remember Me Cookies".
</div>
<div sec:authorize="isFullyAuthenticated()">
The user: <span sec:authentication="name"></span> is logged in by "Username / Password".
</div>
</div>
<footer>
<div class="container">
<p>
© Memorynotfound.com
<span sec:authorize="isAuthenticated()" style="display: inline-block;">
| Logged user: <span sec:authentication="name"></span> |
Roles: <span sec:authentication="principal.authorities"></span> |
<a th:href="@{/logout}">Sign Out</a>
</span>
</p>
</div>
</footer>
<script type="text/javascript" th:src="@{/webjars/jquery/3.2.1/jquery.min.js/}"></script>
<script type="text/javascript" th:src="@{/webjars/bootstrap/3.3.7/js/bootstrap.min.js}"></script>
</body>
</html>
Demo
Access http://localhost:8080
and page is redirected to http://localhost:8080/login
.
When user and password are correctly filled, the page is redirected to http://localhost:8080/
.
When inspecting the cookies of our application, we can see Spring created the remember-me-cookie-name
which we configured earlier.
When you delete the JSESSIONID
cookie and refresh the page, you’re automatically logged in when the remember-me cookie isn’t expired.
References
- Spring Security Remember Me Documentation
- WebSecurityConfigurerAdapter JavaDoc
- RememberMeConfigurer JavaDoc
It’s neccesary to include the following annotations
@Configuration
@EnableWebSecurity
in the SecurityConfig class if no included don’t recognize the user we define in the same class and remains asking user and
password wihout accepting the values