確保資料安全!使用SpringBoot實現強大的API引數驗證

👉 這是一個或許對你有用的社群
🐱 一對一交流/面試小冊/簡歷最佳化/求職解惑,歡迎加入芋道快速開發平臺知識星球。下面是星球提供的部分資料:
👉這是一個或許對你有用的開源專案
國產 Star 破 10w+ 的開源專案,前端包括管理後臺 + 微信小程式,後端支援單體和微服務架構。
功能涵蓋 RBAC 許可權、SaaS 多租戶、資料許可權、商城、支付、工作流、大屏報表、微信公眾號、ERPCRMAI 大模型等等功能:
  • Boot 多模組架構:https://gitee.com/zhijiantianya/ruoyi-vue-pro
  • Cloud 微服務架構:https://gitee.com/zhijiantianya/yudao-cloud
  • 影片教程:https://doc.iocoder.cn
【國內首批】支援 JDK 17/21 + SpringBoot 3.3、JDK 8/11 + Spring Boot 2.7 雙版本 

我們在專案開發中,出於對資料完整性的考慮,基本上每個介面都需要引數校驗,引數校驗可以自己手動校驗,也可以用工具校驗,今天和大家分享如何利用 Spring Boot 自帶的工具實現引數校驗。

一 前端 or 後端?

引數校驗應該在前端完成還是後端完成?
正常來說,前後端都是需要校驗的,但是前後端校驗的目的各不相同。
一般來說,前端校驗可以滿足兩個需求:
  1. 使用者體驗:前端校驗可以即時反饋給使用者,減少等待伺服器響應的時間,提高使用者體驗。
  2. 減輕伺服器負擔:透過前端校驗可以過濾掉一些明顯無效的請求,減少不必要的伺服器負載。
真正要確保資料完整性,還得要靠後端,後端校驗可以起到如下作用:
  1. 安全性:由於前端程式碼可以被繞過或修改。後端校驗是安全的必要保障,確保即使前端校驗被繞過,資料的安全性和完整性也能得到保證。
  2. 資料一致性:後端校驗可以確保所有透過的請求都符合業務邏輯和資料模型的要求,保持資料的一致性。
  3. 容錯性:後端校驗可以處理那些前端未能覆蓋到的異常情況,作為最後一道防線。
  4. 跨平臺一致性:後端校驗確保了無論使用者透過何種客戶端(Web、移動應用、第三方 API 等)訪問服務,資料校驗的標準都是一致的。
  5. 維護和可擴充套件性:後端校驗邏輯通常更容易維護和更新,因為它們集中在伺服器端,而不是分散在多個客戶端。
  6. 日誌和監控:後端可以記錄校驗失敗的請求,這對於監控系統安全和進行問題診斷非常有用。
因此,後端校驗才能真正確保資料的完整性,今天也是要和大家聊一聊後端資料校驗。
基於 Spring Boot + MyBatis Plus + Vue & Element 實現的後臺管理系統 + 使用者小程式,支援 RBAC 動態許可權、多租戶、資料許可權、工作流、三方登入、支付、簡訊、商城等功能
  • 專案地址:https://github.com/YunaiV/ruoyi-vue-pro
  • 影片教程:https://doc.iocoder.cn/video/

二 引數校驗註解

2.1 引數校驗依據

在 Spring Boot 中,資料校驗是透過 JSR303/JSR380 規範的 Bean Validation 實現的。
這裡涉及到兩個概念,和大家簡單說下。
JSR303 是 Bean Validation 的 1.0 版本,正式名稱為《Bean Validation》。它提供了一套註解和 API 來定義 Java 物件(Bean)的驗證規則。這些註解可以直接用於 Bean 的屬性上,以宣告式的方式定義驗證邏輯。JSR303 定義了一組標準的驗證註解,如 @NotNull@Size@Email 等,用於校驗物件的屬性是否滿足特定的條件。
而 JSR380 則是 Bean Validation 的 2.0 版本,也稱為《Jakarta Bean Validation 2.0》。隨著 JavaEE 向 JakartaEE 的遷移,JSR380 成為了新的規範。JSR380 在 JSR303 的基礎上進行了擴充套件和改進,增加了新的註解、改進了 API,並提供了更好的整合方式。JSR380 的註解與 JSR303 相容,但增加了一些新的註解,如 @Emailmessage 屬性支援國際化,以及 @PositiveOrZero@NegativeOrZero 等。
下面案例主要和小夥伴們分享最新的 JSR380 規範中的引數校驗註解。

2.2 程式碼實踐

現在我們建立一個 Spring Boot 專案,使用當前最新版,並且引入引數校驗依賴,最終建立好的工程依賴如下:

dependencies {

    implementation 

'org.springframework.boot:spring-boot-starter-validation'

    implementation 

'org.springframework.boot:spring-boot-starter-web'

    testImplementation 

'org.springframework.boot:spring-boot-starter-test'

    testRuntimeOnly 

'org.junit.platform:junit-platform-launcher'

}

假設我現在有一個 UserDto 類,需要進行引數校驗,那麼我可以按照如下方式定義 UserDto:
publicclassUserDto

{

@NotNull

(message = 

"使用者名稱不能為空"

)

private

 String username;

@NotBlank

(message = 

"密碼不能為空"

)

private

 String password;

@NotEmpty

(message = 

"郵箱不能為空"

)

private

 String email;

//省略 getter/setter

public String getUsername()

{

return

 username;

    }

publicvoidsetUsername(String username)

{

this

.username = username;

    }

public String getPassword()

{

return

 password;

    }

publicvoidsetPassword(String password)

{

this

.password = password;

    }

public String getEmail()

{

return

 email;

    }

publicvoidsetEmail(String email)

{

this

.email = email;

    }

}

接下來在 Controller 的方法引數前使用 @Validated 註解來開啟校驗。
@RestController
publicclassUserController

{

@GetMapping

(

"/hello"

)

public String hello(@Validated UserDto userDto, BindingResult bindingResult)

{

if

 (bindingResult.hasErrors()) {

// 處理校驗失敗情況

        }

return"200"

;

    }

}

當引數校驗失敗時,會丟擲 MethodArgumentNotValidException 異常。可以在全域性異常處理器中捕獲該異常並進行統一處理。
@RestControllerAdvice
publicclassGlobalException

{

@ExceptionHandler

(MethodArgumentNotValidException

.

class

)

publicStringhandleValidationExceptions

(

MethodArgumentNotValidExceptionex

{

// 獲取校驗結果的錯誤資訊

        String message = ex.getBindingResult().getAllErrors().get(

0

).getDefaultMessage();

return

 message;

    }

}

如此就大功告成了~是不是非常 Easy?

2.3 異常提示最佳化

上面引數校驗註解中的異常提示都是在 Java 程式碼裡邊硬編碼的,我們也可以提前定義好異常提示文字,然後在程式碼裡引用即可,這樣更加方便,也好維護。
在 Spring Boot 專案中,可以透過在 messages.properties 檔案中定義異常提示文字,並在程式碼中透過 @Message 註解引用這些文字來實現國際化和自定義錯誤訊息。
具體步驟是這樣的:
  1. 建立 messages.properties 檔案:在 src/main/resources 目錄下建立一個 messages.properties 檔案(對於不同語言版本,可以建立如 messages_en.propertiesmessages_fr.properties 等檔案)。
  2. 定義異常提示文字:在 messages.properties 檔案中定義鍵值對,鍵用於在程式碼中引用,值是實際的錯誤訊息。

NotEmpty.username=使用者名稱不能為空

NotBlank.password=密碼不能為空

Email.email=郵箱格式不正確

  1. 在實體類或 DTO 上使用校驗註解。
import

 javax.validation.constraints.Email;

import

 javax.validation.constraints.NotBlank;

import

 javax.validation.constraints.NotNull;

import

 org.hibernate.validator.constraints.NotEmpty;

publicclassUserDto

{

@NotNull

(message = 

"{NotEmpty.username}"

)

private

 String username;

@NotBlank

(message = 

"{NotBlank.password}"

)

private

 String password;

@Email

(message = 

"{Email.email}"

)

private

 String email;

// Getters and setters

}

  1. 配置國際化:如果你的應用需要支援多語言,可以在 application.propertiesapplication.yml 中配置訊息源。

spring.messages.basename=messages

spring.messages.encoding=UTF-

8

這樣,當校驗失敗時,Spring 將自動從 messages.properties 檔案中查詢對應的錯誤訊息,並將其返回給客戶端。這種方法不僅可以使錯誤訊息更加靈活和可維護,還可以方便地實現國際化。
基於 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 實現的後臺管理系統 + 使用者小程式,支援 RBAC 動態許可權、多租戶、資料許可權、工作流、三方登入、支付、簡訊、商城等功能
  • 專案地址:https://github.com/YunaiV/yudao-cloud
  • 影片教程:https://doc.iocoder.cn/video/

三 什麼是分組校驗

為什麼需要分組校驗呢?
假設我們有一個使用者實體 User,它包含使用者名稱、密碼和郵箱三個欄位。在使用者註冊時,我們需要校驗使用者名稱和密碼非空,郵箱格式正確。但在使用者資訊更新時,我們只需要校驗使用者名稱和郵箱,密碼可能不會被修改,因此不需要校驗。對於這種需求,我們可以使用分組校驗來實現這一需求。
透過一個具體的案例來和小夥伴們演示下。
首先,我們定義兩個校驗分組,一個用於註冊,一個用於更新:
publicinterfaceRegisterGroup

{}

publicinterfaceUpdateGroup

{}

分組其實就是兩個空介面,用來做標記用。
然後,我們在 User 實體上應用這些分組:
import

 javax.validation.constraints.Email;

import

 javax.validation.constraints.NotBlank;

import

 javax.validation.constraints.NotNull;

import

 javax.validation.constraints.Size;

publicclassUser

{

@NotBlank

(message = 

"使用者名稱不能為空"

, groups = {RegisterGroup

.

class

UpdateGroup

.

class

})

privateStringusername

;

@NotBlank

(message = 

"密碼不能為空"

, groups = RegisterGroup

.

class

)

privateStringpassword

;

@Email

(message = 

"郵箱格式不正確"

, groups = {RegisterGroup

.

class

UpdateGroup

.

class

})

privateStringemail

;

// Getters and setters

}

上面程式碼中,username 和 email 即屬於註冊分組也屬於更新分組,而 password 則只屬於註冊分組。
接下來,在註冊介面中,我們使用 @Validated 註解並指定 RegisterGroup 分組:
@RestController
publicclassUserController

{

@GetMapping

(

"/hello"

)

public String hello(@Validated UserDto userDto, BindingResult bindingResult)

{

if

 (bindingResult.hasErrors()) {

// 處理校驗失敗情況

        }

return"200"

;

    }

@PostMapping

(

"/register"

)

public String register(@Validated(RegisterGroup.class) @RequestBody UserDto user) 

{

// 註冊邏輯
return"註冊成功"

;

    }

@PostMapping

(

"/update"

)

public String update(@Validated(UpdateGroup.class) @RequestBody UserDto user) 

{

// 更新邏輯
return"更新成功"

;

    }

}

在這個例子中,當呼叫註冊介面時,User 物件會根據 RegisterGroup 分組進行校驗,而呼叫更新介面時,則會根據 UpdateGroup 分組進行校驗。這樣,我們就可以根據不同的業務需求來應用不同的校驗規則了。
分組校驗這種方式提供了一種靈活的方式來應對不同的校驗場景,使得我們的程式碼更加清晰和易於維護。

四 校驗註解

上面和大家演示了幾個常用的引數校驗註解,完整的校驗註解如下:
好啦,這就是和大家介紹的引數校驗~

歡迎加入我的知識星球,全面提升技術能力。
👉 加入方式,長按”或“掃描”下方二維碼噢
星球的內容包括:專案實戰、面試招聘、原始碼解析、學習路線。
文章有幫助的話,在看,轉發吧。
謝謝支援喲 (*^__^*)

相關文章