Spring Security 简明教程

Spring Security - Form Login

基于表单的登录是 Spring Security 提供支持的用户名/密码验证的一种形式。这是通过 HTML 表单提供的。

每当用户请求受保护的资源时,Spring Security 都会检查该请求的验证。如果请求未经验证/授权,用户将被重定向到登录页面。登录页面必须以某种方式由应用程序呈现。Spring Security 默认情况下将提供该登录表单。

此外,如果需要任何其他配置,必须明确提供,如下所示:

protected void configure(HttpSecurity http) throws Exception {
http
   // ...
   .authorizeHttpRequests(
      request -> request.requestMatchers("/login").permitAll()
      .requestMatchers("/**").authenticated()
   )
   .formLogin(Customizer.withDefaults())
}

让我们从 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.Customizer;
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(Customizer.withDefaults())
         .logout(config -> config
         .logoutUrl("/logout")
         .logoutSuccessUrl("/login"))
         .build();
   }
}

Configuration Class Details

让我们看一看我们的配置类。

  1. 首先,我们将通过使用 userDetailsService() 方法创建 UserDetailsService 类的 bean。我们将使用此 bean 来管理该应用的用户。在此,为了让操作简单,我们将使用 InMemoryUserDetailsManager 实例来创建用户。这些用户连同我们给定的用户名和密码分别被映射到用户和管理员角色。

Password Encoder

  1. 现在,我们来看一下我们的 PasswordEncoder。在这个示例中,我们将使用 BCryptPasswordEncoder 实例。因此,在创建用户时,我们使用 passwordEncoder 对明文密码进行编码,如下所示:.password(passwordEncoder().encode("user123"))

Http Security Configuration

在以上步骤之后,我们将继续进行下一步配置。在此,我们定义了 filterChain 方法。此方法接受 HttpSecurity 作为参数。我们将配置它来使用我们的表单登录和注销功能。

我们可以观察到所有这些功能均在 Spring Security 中提供。让我们详细研究以下部分 −

http
   .csrf(AbstractHttpConfigurer::disable)
   .authorizeHttpRequests(
      request -> request.requestMatchers("/login").permitAll()
      .requestMatchers("/**").authenticated()
    )
   .formLogin(Customizer.withDefaults())
   .logout(config -> config
      .logoutUrl("/logout")
      .logoutSuccessUrl("/login"))
   .build();

此处有一些需要说明的点 −

  1. 我们禁用了 csrfCross-Site Request Forgery 保护。因为这只是出于演示目的的一个简单应用,我们现在可以安全地禁用它。

  2. 然后,我们添加了要求对所有请求进行身份验证的配置。如我们稍后将看到的,出于简单性,我们为这个应用程序的首页准备了一个 "/" 端点。

  3. 之后,我们将按照上面提到的方法使用 Spring Security 的 formLogin() 功能。该功能会生成一个默认的登录页面。

  4. 最后,我们有 logout() 功能。对于此功能,Spring security 也提供了一个默认的功能。此功能执行两个重要函数,包括:使 Http 会话失效,解除绑定到会话的对象。从 Spring 的 Security 上下文中移除身份验证。

我们还提供了一个 logoutSuccessUrl(),以便应用程序在注销后返回到登录页面。这完成了我们的应用程序配置。

Controller Class

在这个类中,我们为该应用的首页为一个“/”端点创建了映射,这为了简单起见。这将重定向到 index.html。

AuthController

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";
   }
}

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-springsecurity6">
   <head>
      <title>
         Hello World!
      </title>
   </head>
   <body>
      <h1 th:inline="text">Hello World!</h1>
      <form th:action="@{/logout}" method="post">
         <input type="submit" value="Sign Out"/>
      </form>
   </body>
<html>

Running the Application

既然所有组件都准备好了,接下来运行该应用程序。右键单击项目,然后按照下图所示,选择“ Run As ”,然后选择“ Spring Boot App ”:

run as springboot app

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

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/

 [32m :: Spring Boot ::  [39m               [2m (v3.3.1) [0;39m

 [2m2024-07-10T11:53:08.438+05:30 [0;39m  [32m INFO [0;39m  [35m13252 [0;39m  [2m--- [0;39m  [2m[formlogin] [  restartedMain] [0;39m  [2m [0;39m [36mc.t.s.formlogin.FormloginApplication     [0;39m  [2m: [0;39m Starting FormloginApplication using Java 21.0.3 with PID 13252 (E:\security\formlogin\target\classes started by Tutorialspoint in E:\security\formlogin)
 [2m2024-07-10T11:53:08.441+05:30 [0;39m  [32m INFO [0;39m  [35m13252 [0;39m  [2m--- [0;39m  [2m[formlogin] [  restartedMain] [0;39m  [2m [0;39m [36mc.t.s.formlogin.FormloginApplication     [0;39m  [2m: [0;39m No active profile set, falling back to 1 default profile: "default"
 [2m2024-07-10T11:53:08.495+05:30 [0;39m  [32m INFO [0;39m  [35m13252 [0;39m  [2m--- [0;39m  [2m[formlogin] [  restartedMain] [0;39m  [2m [0;39m [36m.e.DevToolsPropertyDefaultsPostProcessor [0;39m  [2m: [0;39m Devtools property defaults active! Set 'spring.devtools.add-properties' to 'false' to disable
 [2m2024-07-10T11:53:08.496+05:30 [0;39m  [32m INFO [0;39m  [35m13252 [0;39m  [2m--- [0;39m  [2m[formlogin] [  restartedMain] [0;39m  [2m [0;39m [36m.e.DevToolsPropertyDefaultsPostProcessor [0;39m  [2m: [0;39m For additional web related logging consider setting the 'logging.level.web' property to 'DEBUG'
 [2m2024-07-10T11:53:09.395+05:30 [0;39m  [32m INFO [0;39m  [35m13252 [0;39m  [2m--- [0;39m  [2m[formlogin] [  restartedMain] [0;39m  [2m [0;39m [36mo.s.b.w.embedded.tomcat.TomcatWebServer  [0;39m  [2m: [0;39m Tomcat initialized with port 8080 (http)
 [2m2024-07-10T11:53:09.407+05:30 [0;39m  [32m INFO [0;39m  [35m13252 [0;39m  [2m--- [0;39m  [2m[formlogin] [  restartedMain] [0;39m  [2m [0;39m [36mo.apache.catalina.core.StandardService   [0;39m  [2m: [0;39m Starting service [Tomcat]
 [2m2024-07-10T11:53:09.407+05:30 [0;39m  [32m INFO [0;39m  [35m13252 [0;39m  [2m--- [0;39m  [2m[formlogin] [  restartedMain] [0;39m  [2m [0;39m [36mo.apache.catalina.core.StandardEngine    [0;39m  [2m: [0;39m Starting Servlet engine: [Apache Tomcat/10.1.25]
 [2m2024-07-10T11:53:09.451+05:30 [0;39m  [32m INFO [0;39m  [35m13252 [0;39m  [2m--- [0;39m  [2m[formlogin] [  restartedMain] [0;39m  [2m [0;39m [36mo.a.c.c.C.[Tomcat].[localhost].[/]       [0;39m  [2m: [0;39m Initializing Spring embedded WebApplicationContext
 [2m2024-07-10T11:53:09.452+05:30 [0;39m  [32m INFO [0;39m  [35m13252 [0;39m  [2m--- [0;39m  [2m[formlogin] [  restartedMain] [0;39m  [2m [0;39m [36mw.s.c.ServletWebServerApplicationContext [0;39m  [2m: [0;39m Root WebApplicationContext: initialization completed in 956 ms
 [2m2024-07-10T11:53:09.751+05:30 [0;39m  [32m INFO [0;39m  [35m13252 [0;39m  [2m--- [0;39m  [2m[formlogin] [  restartedMain] [0;39m  [2m [0;39m [36mr$InitializeUserDetailsManagerConfigurer [0;39m  [2m: [0;39m Global AuthenticationManager configured with UserDetailsService bean with name userDetailsService
 [2m2024-07-10T11:53:09.811+05:30 [0;39m  [32m INFO [0;39m  [35m13252 [0;39m  [2m--- [0;39m  [2m[formlogin] [  restartedMain] [0;39m  [2m [0;39m [36mo.s.b.a.w.s.WelcomePageHandlerMapping    [0;39m  [2m: [0;39m Adding welcome page template: index
 [2m2024-07-10T11:53:10.092+05:30 [0;39m  [32m INFO [0;39m  [35m13252 [0;39m  [2m--- [0;39m  [2m[formlogin] [  restartedMain] [0;39m  [2m [0;39m [36mo.s.b.d.a.OptionalLiveReloadServer       [0;39m  [2m: [0;39m LiveReload server is running on port 35729
 [2m2024-07-10T11:53:10.124+05:30 [0;39m  [32m INFO [0;39m  [35m13252 [0;39m  [2m--- [0;39m  [2m[formlogin] [  restartedMain] [0;39m  [2m [0;39m [36mo.s.b.w.embedded.tomcat.TomcatWebServer  [0;39m  [2m: [0;39m Tomcat started on port 8080 (http) with context path '/'
 [2m2024-07-10T11:53:10.133+05:30 [0;39m  [32m INFO [0;39m  [35m13252 [0;39m  [2m--- [0;39m  [2m[formlogin] [  restartedMain] [0;39m  [2m [0;39m [36mc.t.s.formlogin.FormloginApplication     [0;39m  [2m: [0;39m Started FormloginApplication in 2.051 seconds (process running for 2.795)

Output

现在打开 localhost:8080,可以看到一个漂亮的默认登录页面。

Login Page

formlogin

Login Page for Bad Credentials

输入任何无效的凭据,它将显示错误。

formlogin error

Home Page

输入有效的凭据。

它将载入主页。

formlogin success

After Logout

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

formlogin