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有一定的使用經驗。

現在讓我們繼續編寫一個基於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.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.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 Security 配置

完成上述步驟後,我們繼續進行下一個配置。在這裡,我們定義了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的安全上下文中刪除身份驗證。

我們還提供了一個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-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> 

執行應用程式

由於我們已經準備好所有元件,讓我們執行應用程式。右鍵單擊專案,選擇Run As,然後選擇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 Valid Credentials

它將載入主頁。

Login Form Success

登出後

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

Login Form
廣告