Spring Security 简明教程

Spring Security - Remember Me

记住我 (Remember Me) 是 Spring Security 的一项重要功能,因此即使会话过期,用户仍可以保持登录状态。我们将在以下部分演示 Spring Security 提供的记住我功能的使用。

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

  1. 首先,它会向我们使用 formLogin() 生成的默认登录表单中添加一个“记住我”(Remember Me) 复选框。

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

  3. 其次,勾选该复选框将生成记住我 cookie。Cookie 存储用户的身份,并且浏览器会对其进行存储。

  4. Spring Security 在将来的会话中检测 Cookie,以便自动化登录。因此,用户无需再次登录即可再次访问该应用程序。

记住我功能可以按如下所示进行明确配置 −

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

Important Methods

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

  1. *rememberMe () * − 这将用于实现记住我功能。传递给记住我功能的密钥应该是唯一而保密的。此密钥特定于应用程序,用于生成记住我令牌内容。

  2. *tokenValiditySeconds () * − 这将用于设置记住我 cookie 的到期时间。默认有效期为 2 周。我们可以随时对其进行自定义,就像在上面的代码片段中一样,我们使用 3600 秒将其设置为 1 小时。

  3. rememberMeParameter () * − This is used to mark an input check box to be remember-me checkbox. By default, its value is *remember-me

让我们使用 Spring Security 开始实际编程。在开始使用 Spring 框架编写示例之前,您必须确保已正确设置 Spring 环境,如 Spring Security - Environment Setup 章节所述。我们还假设您具备 Spring Tool Suite IDE 方面的部分工作知识。

现在,让我们开始编写一个由 Maven 管理的基于 Spring MVC 的应用程序,该应用程序将要求用户登录、认证用户,然后提供使用 Spring Security 表单登录功能注销的选项。

Create Project using Spring Initializr

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

我们选择了一个 maven 项目,将项目命名为 formlogin,并将 java 版本指定为 21。添加了以下依赖项:

  1. Spring Web

  2. Spring Security

  3. Thymeleaf

  4. Spring Boot DevTools

spring initializr

Thymeleaf 是 Java 的模板引擎。它使我们能够快速开发用于在浏览器中渲染的静态或动态网页。它已得到极大的扩展,它允许我们定义和定制精细处理的模板。除此之外,我们可以通过点击 link 来了解更多有关 Thymeleaf 的信息。

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

pom.xml with all relevant dependencies

让我们看一看我们的 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 Configuration Class

在我们的 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()。

Controller Class

在此类中,我们为该应用程序的索引页和登录页创建了 "/" 端点和 "/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";
   }
}

Views

让我们在 /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 安全性配置类中设置相同的名称。

<!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。

Running the Application

因为我们所有的组件都已就绪,让我们来运行此 Application。右击项目,选择 Run As ,然后选择 Spring Boot App

它将启动该应用,并且一旦应用启动,我们就可以运行 localhost:8080 来查看更改。

Output

现在打开 localhost:8080,可以看到我们的登录页面,里面有一个记住我复选框。

Login Page with Remember Me Checkbox

rememberme formlogin

Home Page without Remember Me Checked

输入有效的证书,不要勾选记住我复选框。

rememberme formlogin unchecked

它会加载主页。我们可以验证记住我的 Cookie 不可用的情况。

homepage without rememberme cookie

Login with Remember Me Checked

现在点击注销按钮,它将重新加载登录页面。勾选记住我复选框并登录。

rememberme formlogin checked

Home Page with Remember Me Checked

我们现在可以检查记住我的 Cookie 现在可用。

homepage with rememberme cookie

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

  1. username - 识别已登录用户。可用于检索用户名

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

  3. Hash - 用户名、过期时间、密码和用于创建 Cookie 的私钥的 MD5 编码哈希。

万一用户名或密码已更改,Cookie 将失效,必须重新创建。

如果未设置记住我 Cookie,那么在会话超时后刷新页面将加载登录页面。但是,如果记住我 Cookie 已设置且处于活动状态,那么刷新页面只会刷新页面,并使用记住我 Cookie 中的令牌创建新的会话。