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 the username, password and key 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 the persistent 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.
Download
From:一号门
COMMENTS