Spring Security - 表單登入



基於表單的登入是 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 環境,如Spring Security - 環境設定章節中所述。我們還假設您對 Spring Tool Suite IDE 有一定的瞭解。

現在讓我們繼續編寫一個由 Maven 管理的基於 Spring MVC 的應用程式,該應用程式將要求使用者登入、驗證使用者身份,然後使用 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.tw/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.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.provisioning.UserDetailsManager;
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();
   }   
}

配置類詳細資訊

讓我們看一下我們的配置類。

  • 首先,我們將使用 userDetailsService() 方法建立 UserDetailsService 類的 bean。我們將使用此 bean 來管理此應用程式的使用者。在這裡,為了簡單起見,我們將使用 InMemoryUserDetailsManager 例項來建立使用者。這些使用者以及我們給定的使用者名稱和密碼分別對映到 User 和 Admin 角色。
  • 密碼編碼器

  • 現在,讓我們看看我們的 PasswordEncoder。我們將使用 BCryptPasswordEncoder 例項作為此示例。因此,在建立使用者時,我們使用了 passwordEncoder 來編碼我們的純文字密碼,如下所示
.password(passwordEncoder().encode("user123"))

Http 安全配置

完成以上步驟後,我們繼續進行下一個配置。在這裡,我們定義了 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();

這裡需要注意幾點:

  • 我們停用了csrf跨站點請求偽造保護。由於這只是一個用於演示目的的簡單應用程式,因此我們可以暫時安全地停用它。
  • 然後我們新增配置,要求對所有請求進行身份驗證。正如我們稍後將看到的,為了簡單起見,我們將有一個用於此應用程式索引頁面的單個“/”端點。
  • 之後,我們將使用 Spring Security 的 formLogin() 功能,如上所述。這將生成一個預設的登入頁面。
  • 最後,我們有 logout() 功能。對於此功能,Spring Security 也提供了預設功能。在這裡,它執行兩個重要功能:
    • 使 Http 會話失效,並取消繫結繫結到會話的物件。
    • 從 Spring 的 Security 上下文中刪除身份驗證。

    我們還提供了 logoutSuccessUrl(),以便應用程式在登出後返回到登入頁面。這完成了我們的應用程式配置。

控制器類

在此類中,我們為該應用程式的索引頁面的單個“/”端點建立了對映,為了簡單起見。這將重定向到 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"; 
   }
}

檢視

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

執行應用程式

由於我們已準備好所有元件,因此讓我們執行應用程式。右鍵單擊專案,選擇以...方式執行,然後選擇Spring Boot App,如下面的影像所示

Run as Spring Boot 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)

輸出

現在開啟 localhost:8080,您會看到一個漂亮的預設登入頁面。

登入頁面

Login Form

錯誤憑據的登入頁面

輸入任何無效的憑據,它將顯示錯誤。

Login Form Error

主頁

輸入有效的憑據,它將載入主頁。

Login Form Success

登出後

現在單擊登出按鈕,它將再次載入登入頁面。

Login Form
廣告

© . All rights reserved.