Spring Security - 记住我



“记住我”是 Spring Security 的一项重要功能,即使会话过期,用户也可以保持登录应用程序状态。我们将在以下部分演示 Spring Security 提供的“记住我”功能的使用。

“记住我”功能执行以下重要功能。

  • 首先,它将在我们使用 formLogin() 生成的默认登录表单中添加一个“记住我”复选框。

  • 对于自定义登录表单,我们需要向表单添加一个名为“remember-me”的复选框。如果需要使用不同的名称,则需要在 Spring Security 配置期间配置新名称,如下所示

    .rememberMe().rememberMeParameter("remember")
    

  • 其次,选中复选框会生成“记住我”Cookie。Cookie 存储用户身份,浏览器会存储它。

  • Spring Security 在将来的会话中检测到 Cookie 以自动执行登录。因此,用户无需再次登录即可再次访问应用程序。

可以显式配置“记住我”,如下所示 -

protected void configure(HttpSecurity http) throws Exception {
http 
   // ... 
   // key should be unique
   .rememberMe(config -> config.key("123456")
   .tokenValiditySeconds(3600))   
   .build();
}

重要方法

以下是在 logout() 方法中可以配置的重要方法。

  • rememberMe () − 这将用于实现“记住我”功能。传递给“记住我”函数的密钥必须是唯一的且安全的。此密钥特定于应用程序,用于生成“记住我”令牌内容。

  • tokenValiditySeconds () − 这将用于设置“记住我”Cookie 的过期时间。默认情况下,有效期为 2 周。我们可以随时自定义它,如上面的代码片段所示,我们使用 3600 秒将其设置为 1 小时。

  • rememberMeParameter () − 这用于将输入复选框标记为“记住我”复选框。默认情况下,其值为 remember-me

让我们开始使用 Spring Security 进行实际编程。在开始使用 Spring 框架编写示例之前,您必须确保已正确设置 Spring 环境,如Spring Security - 环境设置章节中所述。我们还假设您对 Spring Tool Suite IDE 有些了解。

现在让我们继续编写一个基于 Spring MVC 的应用程序,该应用程序由 Maven 管理,它将提示用户登录、对用户进行身份验证,然后提供使用 Spring Security 表单登录功能注销的选项。

使用 Spring Initializr 创建项目

Spring Initializr 是开始 Spring Boot 项目的好方法。它提供了一个易于使用的用户界面来创建项目、添加依赖项、选择 Java 运行时等。它生成一个骨架项目结构,下载后可以导入到 Spring Tool Suite 中,然后我们可以继续使用我们的现成项目结构。

我们选择一个 Maven 项目,将项目命名为 formlogin,Java 版本为 21。添加了以下依赖项

  • Spring Web

  • Spring Security

  • Thymeleaf

  • Spring Boot DevTools

Spring Initializr

Thymeleaf 是 Java 的模板引擎。它允许我们快速开发静态或动态网页,以便在浏览器中呈现。它具有极强的可扩展性,允许我们详细定义和自定义模板的处理。此外,我们可以通过点击此链接了解更多关于 Thymeleaf 的信息。

让我们继续生成我们的项目并下载它。然后,我们将它解压缩到我们选择的文件夹中,并使用任何 IDE 打开它。我将使用Spring Tools Suite 4。它可以从https://springframework.org.cn/tools网站免费下载,并且针对 Spring 应用程序进行了优化。

包含所有相关依赖项的 pom.xml

让我们看看我们的 pom.xml 文件。它应该看起来类似于以下内容 -

pom.xml

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>
   <parent>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-parent</artifactId>
      <version>3.3.1</version>
      <relativePath/> <!-- lookup parent from repository -->
   </parent>
   <groupId>com.tutorialspoint.security</groupId>
   <artifactId>formlogin</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <name>formlogin</name>
   <description>Demo project for Spring Boot</description>
   <url/>
   <licenses>
      <license/>
   </licenses>
   <developers>
      <developer/>
   </developers>
   <scm>
      <connection/>
      <developerConnection/>
      <tag/>
      <url/>
   </scm>
   <properties>
      <java.version>21</java.version>
   </properties>
   <dependencies>
      <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.springframework.boot</groupId>
         <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
      <dependency>
         <groupId>org.thymeleaf.extras</groupId>
         <artifactId>thymeleaf-extras-springsecurity6</artifactId>
      </dependency>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-devtools</artifactId>
         <scope>runtime</scope>
         <optional>true</optional>
      </dependency>
      <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 配置类

在我们的 config 包中,我们创建了 WebSecurityConfig 类。我们将使用此类进行安全配置,因此让我们使用 @Configuration 注解和 @EnableWebSecurity 对其进行注释。因此,Spring Security 知道将此类视为配置类。正如我们所看到的,Spring 使配置应用程序变得非常容易。

WebSecurityConfig

package com.tutorialspoint.security.formlogin.config; 

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.configurers.AbstractHttpConfigurer;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;  

@Configuration 
@EnableWebSecurity
public class WebSecurityConfig  { 

   @Bean 
   protected UserDetailsService userDetailsService() {
      UserDetails user = User.builder()
         .username("user")
         .password(passwordEncoder().encode("user123"))
         .roles("USER")
         .build();
      UserDetails admin = User.builder()
         .username("admin")
         .password(passwordEncoder().encode("admin123"))
         .roles("USER", "ADMIN")
         .build();
      return new InMemoryUserDetailsManager(user, admin);
   }

   @Bean 
   protected PasswordEncoder passwordEncoder() { 
      return new BCryptPasswordEncoder(); 
   }

   @Bean
   protected SecurityFilterChain filterChain(HttpSecurity http) throws Exception { 
      return http
         .csrf(AbstractHttpConfigurer::disable)
         .authorizeHttpRequests(
            request -> request.requestMatchers("/login").permitAll()
            .requestMatchers("/**").authenticated()
         )
         .formLogin(form -> form.loginPage("/login")
            .defaultSuccessUrl("/")
            .failureUrl("/login?error=true")
            .permitAll())       
         .rememberMe(config -> config.key("123456")
         .tokenValiditySeconds(3600))   
         .logout(config -> config  
            .logoutUrl("/logout") 
            .logoutSuccessUrl("/login")
			.invalidateHttpSession(true)
            .deleteCookies("JSESSIONID"))
         .build();
   }   
}

在这里,我们提到了带有安全密钥的 rememberMe() 以供 Spring Security 使用。

控制器类

在此类中,我们为“/”端点和“/login”创建了映射,分别用于此应用程序的索引页面和登录页面。

AuthController.java

package com.tutorialspoint.security.formlogin.controllers; 

import org.springframework.stereotype.Controller; 
import org.springframework.web.bind.annotation.GetMapping; 

@Controller 
public class AuthController { 
   @GetMapping("/") 
   public String home() { 
      return "index"; 
   }
   @GetMapping("/login") 
   public String login() { 
      return "login"; 
   }
}

视图

让我们在 /src/main/resources/templates 文件夹中创建 index.html,其内容如下,作为主页并显示已登录用户的名称。

index.html

<!DOCTYPE html> 
<html xmlns="http://www.w3.org/1999/xhtml" 
   xmlns:th="https://www.thymeleaf.org" 
   xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3"> 
   <head> 
      <title>
         Hello World!
      </title> 
   </head>
   <body> 
      <h1 th:inline="text">Hello <span sec:authentication="name"></span>!</h1> 
      <form th:action="@{/logout}" method="post"> 
         <input type="submit" value="Sign Out"/> 
      </form>
   </body> 
<html> 

login.html

让我们在 /src/main/resources/templates 文件夹中创建 login.html,其内容如下,作为登录页面。我们使用默认名称 usernamepasswordremember-me 作为文本字段。如果使用其他名称,则也需要在 Spring Security 配置类中设置相同的名称。

<!DOCTYPE html> 
<html xmlns="http://www.w3.org/1999/xhtml"      
   xmlns:th="https://www.thymeleaf.org" 
   xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3"> 
   <head> 
      <title>Spring Security Example</title> 
   </head> 
   <body> 
      <div th:if="${param.error}"> 
         <p>Bad Credentials</p>
      </div> 
      <div th:if="${param.logout}">You have been logged out.</div> 
      <form th:action="@{/login}" method="post">
         <h1>Please sign in</h1>
         <table>
            <tr>
               <td><label for="username"><b>Username</b></label></td>
               <td><input type="text" placeholder="Enter Username" name="username" id="username" required></td>
            </tr>
            <tr>
               <td><label for="password"><b>Password</b></label></td>
               <td><input type="password" placeholder="Enter Password" name="password" id="password" required></td>
            </tr>
            <tr>
               <td><label for="remember-me"><b>Remember Me</b></label> </td>
               <td><input type="checkbox" name="remember-me" /></td>
            </tr>
            <tr>
               <td> </td>
               <td><input type="submit"  value="Sign In" /></td>
            </tr>		
         </table>       
      </form> 
   </body>
</html>

在登录表单中,我们使用 POST 方法登录,同时使用名称和 ID 为 usernamepasswordremember-me 复选框的输入字段。如果选中复选框,则用户登录应用程序时将创建一个“记住我”Cookie。

运行应用程序

由于我们所有组件都已准备就绪,让我们运行应用程序。右键单击项目,选择 Run As,然后选择 Spring Boot App

它将启动应用程序,并且应用程序启动后,我们可以运行 localhost:8080 以检查更改。

输出

现在打开 localhost:8080,您可以在其中看到我们的登录页面,其中包含“记住我”复选框。

带有“记住我”复选框的登录页面

Login Form with Remember Me

未选中“记住我”的主页

输入有效凭据,不要选中“记住我”复选框。

Login Page without RememberMe checked

它将加载主页。我们可以验证“记住我”Cookie 是否不可用。

Home Page without RememberMe cookie

选中“记住我”登录

现在点击注销按钮,这将再次加载登录页面。选中“记住我”复选框并登录。

Login Form with Checked Remember Me

选中“记住我”的主页

现在我们可以检查“记住我”Cookie 是否可用。

Home Page without RememberMe cookie

“记住我”Cookie

“记住我”Cookie 包含以下详细信息

  • username - 用于识别已登录的用户。可用于检索用户名

  • expirationTime - Cookie 的过期时间。默认为 2 周。

  • Hash - 用户名、expirationTime、密码和用于创建 Cookie 的私钥的 MD5 编码哈希。

如果用户名或密码更改,则 Cookie 将失效,必须重新创建。

如果未设置“记住我”Cookie,则会话超时后刷新页面将加载登录页面。而如果“记住我”Cookie 已设置并处于活动状态,则刷新页面将仅刷新页面并使用“记住我”Cookie 中的令牌创建新的会话。

广告

© . All rights reserved.