Spring Security 简明教程

Spring Security - Logout

注销是一项重要功能,以便用户在注销或签出后或出于任何原因其当前会话失效后,需要登录才能访问任何安全资源。

正如我们在 Spring Security - Form Login 章节中所看到的,Spring security 提供了一个默认登出功能。

登出功能执行以下重要的功能。

  1. 将 Http 会话失效,并解除与会话绑定的对象。

  2. 它清除记住我的 cookie。

  3. 从 Spring 的安全上下文中删除身份验证。

登出可以按如下所示明确配置 −

protected void configure(HttpSecurity http) throws Exception {
http
   // ...
   .logout(config -> config
      .logoutUrl("/logout")
      .logoutSuccessUrl("/login"))
   .build();
}

Important Methods

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

  1. logoutUrl ("/logout") * − This will be used to logout from our application. It has default value as *logout 并且可以更改为任何其他自定义值。

  2. *logoutSuccessUrl ("/login") * − 一旦用户成功登出,这将用于加载登录页面。

  3. *invalidateHttpSession ("true") * − 这用于使会话失效。默认情况下,它是 true,以便在用户登出时,其会话将失效。我们可以在登出后仍让会话保持活动状态,将该值标记为 false。

  4. *deleteCookies ("JSESSIONID") * − 这用于清除记住我的 cookie。

  5. *logoutSuccessHandler(logoutSuccessHandler()); * − 当我们需要在用户从应用程序登出时执行某些操作时,将使用此方法。

让我们从 Spring Security 开始实际编程。在您开始使用 Spring 框架编写第一个示例之前,您必须确保已按照 Spring Security - Environment Setup 章节中所述正确设置了 Spring 环境。我们还假设您对 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())
         .logout(config -> config
            .logoutUrl("/logout")
            .logoutSuccessUrl("/login")
            .invalidateHttpSession(true)
            .deleteCookies("JSESSIONID"))
         .build();
   }
}

这里我们提到了要用于登出的 logout url,这是 spring security 提供的默认 url。我们不需要为其创建专门的登出页面。同样,一旦用户登出,系统将向用户显示登录页面,这是标准做法。

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>

这里我们使用了带有提交按钮的表单来注销用户。

<form th:action="@{/logout}" method="post">
   <input type="submit" value="Sign Out"/>
</form>

我们也可以使用链接,如下所示:

<a href="/logout" alt="logout">Sign Out</a>

login.html

让我们在 /src/main/resources/templates 文件夹中创建 login.html,并添加以下内容以用作登录页面。我们对文本字段使用默认名称 usernamepassword 。对于其他名称,我们还需要在 spring security config 类中设置相同的名称。

<!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">
         <div>
            <h1>Please sign in</h1>
            <label for="username">
               <b>Username</b>
            </label>
            <input type="text" placeholder="Enter Username" name="username" id="username" required>
            <label for="password"><b>Password</b></label>
            <input type="password" placeholder="Enter Password" name="password" id="password" required>
            <input type="submit"  value="Sign In" />
         </div>
      </form>
   </body>
</html>

在 login.html 中,我们使用 ${param.error} 读取请求参数 error 。如果它为真,则会打印一条错误消息,如 Bad Credential 所示。类似地,我们使用 ${param.logout} 读取请求参数 logout 。如果它为真,则会打印退出消息。

在登录表单中,我们使用 POST 方法登录,同时使用名称和 ID 为 usernamepassword 的输入字段。

Running the Application

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

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

Output

现在打开 localhost:8080,您可以看到我们自定义的登录页面。

Login Page

custom formlogin

Home Page

输入有效的凭证,然后它将加载主页。

custom formlogin success

After Logout

现在点击注销按钮,这将再次加载登录页面。

custom formlogin