SpringBoot之服務端數據校驗


對於任何一個應用而言,客戶端做的數據有效性驗證都不是安全有效的,而數據驗證又是一個企業級項目架構上最為基礎的功能模塊,這時候就要求我們在服務端接收到數據的時候也對數據的有效性進行驗證。為什么這么說呢?往往我們在編寫程序的時候都會感覺后台的驗證無關緊要,畢竟客戶端已經做過驗證了,后端沒必要在浪費資源對數據進行驗證了,但恰恰是這種思維最為容易被別人鑽空子。畢竟只要有點開發經驗的都知道,我們完全可以模擬 HTTP 請求到后台地址,模擬請求過程中發送一些涉及系統安全的數據到后台,后果可想而知....

驗證分兩種:對封裝的Bean進行驗證  或者  對方法簡單參數的驗證。

說明:SpringBoot 中使用了 Hibernate-validate 校驗框架作為支持

一、引入依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- 這兩個springboot包里面都包含hibernate-validator包,只要引入一個即可 -->
 <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

二、hibernate-validator常用注解

  • @Valid:被注釋的元素是一個對象,需要檢查此對象的所有字段值
  • @Validated :是@Valid 的一次封裝,是Spring提供的校驗機制使用。@Valid不提供分組功能@Null 被注釋的元素必須為 null
  • @NotNull:被注釋的元素必須不為 null
  • @Pattern(value) :被注釋的元素必須符合指定的正則表達式
  • @Size(min, max) :集合元素的數量必須在min和max之間
  • @CreditCardNumber(ignoreNonDigitCharacters=): 字符串必須是信用卡號,按照美國的標准驗證
  • @Email: 字符串必須是Email地址
  • @Length(min, max) :檢查字符串的長度
  • @NotBlank : 只能用於字符串不為null,並且字符串trim()以后length要大於0
  • @NotEmpty : 字符串不能為null, 集合必須有元素
  • @Range(min, max) :數字必須大於min, 小於max
  • @SafeHtml(whitelistType=,additionalTags=) :字符串必須是安全的html, classpath中要有jsoup包
  • @URL(protocol=,host=, port=, regexp=, flags=) : 字符串必須是合法的URL
  • @AssertTrue :被注釋的元素必須為 true
  • @AssertFalse :被注釋的元素必須為 false
  • @DecimalMax(value=, inclusive=) :值必須小於等於(inclusive=true)/小於(inclusive=false)屬性指定的值,也可以注釋在字符串類型的屬性上。
  • @DecimalMin(value=, inclusive=) :值必須大於等於(inclusive=true)/小於(inclusive=false)屬性指定的值,也可以注釋在字符串類型的屬性上。
  • @Digits (integer, fraction) :數字格式檢查。integer指定整數部分的最大長度,fraction指定小數部分的最大長度
  • @Future : 時間必須是未來的
  • @Past : 時間必須是過去的
  • @Max(value=) : 值必須小於等於value指定的值。不能注解在字符串類型屬性上。
  • @Min(value=) : 值必須小於等於value指定的值。不能注解在字符串類型屬性上。
  • @ScriptAssert(lang=, script=, alias=) : 要有Java Scripting API 即JSR 223("Scripting for the JavaTM Platform")的實現

三、@Valid和@Validated的區別

@Valid是使用Hibernate validation的時候。

@Validated是使用Spring Validator校驗機制(在spring-context依賴下)。

java的JSR303聲明了@Valid這類接口,而Hibernate-validator對其進行了實現,@Validation對@Valid進行了二次封裝,在使用上並沒有區別,但在分組、注解位置、嵌套驗證等功能上有所不同。

1. 注解位置上

@Validated:用在類型、方法和方法參數上。但不能用於成員屬性(field)。

@Valid:可以用在方法、構造函數、方法參數和成員屬性(field)上。

如果@Validated注解在成員屬性上,則會報  不適用於field錯誤。

2. 分組校驗

@Validated:提供分組功能,可以在參數驗證時,根據不同的分組采用不同的驗證機制。

@Valid:沒有分組功能。

(1) 定義分組接口

public interface IGroupA {
}

public interface IGroupB {
}

(2) 定義需要校驗的參數bean

public class Student implements Serializable {
    @NotBlank(message = "用戶名不能為空")
    private String name;
    //只在分組為IGroupB的情況下進行驗證
    @Min(value = 18, message = "年齡不能小於18歲", groups = {IGroupB.class})
    private Integer age;
    @Pattern(regexp = "^((13[0-9])|(14[5,7,9])|(15([0-3]|[5-9]))|(166)|(17[0,1,3,5,6,7,8])|(18[0-9])|(19[8|9]))\\d{8}$", message = "手機號格式錯誤")
    private String phoneNum;
    @Email(message = "郵箱格式錯誤")
    private String email;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getPhoneNum() {
        return phoneNum;
    }

    public void setPhoneNum(String phoneNum) {
        this.phoneNum = phoneNum;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}

(3) 檢驗分組為IGroupA的情況

@RestController
public class CheckController {

    @PostMapping("/stu")
    public String addStu(@Validated({IGroupA.class})  Student studentBean){
        return "add student success";
    }
}

很明顯,這里對IGroupA是不起作用的,修改為@Validated({IGroupB.class})或@Validated({IGroupA.class, IGroupB.class})。

說明:

不分配groups,默認每次都要進行驗證
對一個參數需要多種驗證方式時,也可通過分配不同的組達到目的。

3. 組序列

默認情況下 不同級別的約束驗證是無序的,但是在一些情況下,順序驗證卻是很重要。

一個組可以定義為其他組的序列,使用它進行驗證的時候必須符合該序列規定的順序。在使用組序列驗證的時候,如果序列前邊的組驗證失敗,則后面的組將不再給予驗證。

(1) 定義組序列

@GroupSequence({Default.class, IGroupA.class, IGroupB.class})
public interface IGroup {
}

(2) 需要校驗的Bean,分別定義IGroupA對age進行校驗,IGroupB對email進行校驗:

public class Student implements Serializable {
    @NotBlank(message = "用戶名不能為空")
    private String name;
    //只在分組為IGroupB的情況下進行驗證
    @Min(value = 18, message = "年齡不能小於18歲", groups = {IGroupA.class})
    private Integer age;
    @Pattern(regexp = "^((13[0-9])|(14[5,7,9])|(15([0-3]|[5-9]))|(166)|(17[0,1,3,5,6,7,8])|(18[0-9])|(19[8|9]))\\d{8}$", message = "手機號格式錯誤")
    private String phoneNum;
    @Email(message = "郵箱格式錯誤", groups = IGroupB.class)
    private String email;
}

(3) 測試

@RestController
public class CheckController {

    @PostMapping("/stu")
    public String addStu(@Validated({IGroup.class})  Student studentBean){
        return "add student success";
    }
}

測試發現,如果age出錯,那么對組序列在IGroupA后的IGroupB不進行校驗。

4. 嵌套校驗

一個待驗證的pojo類,其中還包含了待驗證的對象,需要在待驗證對象上注解@Valid,才能驗證待驗證對象中的成員屬性,這里不能使用@Validated。

(1) 需要約束的bean

public class TeacherBean {
    @NotEmpty(message = "老師姓名不能為空")
    private String teacherName;
    @Min(value = 1, message = "學科類型從1開始計算")
    private int type;
}
public class Student implements Serializable {
    @NotBlank(message = "用戶名不能為空")
    private String name;
    //只在分組為IGroupB的情況下進行驗證
    @Min(value = 18, message = "年齡不能小於18歲")
    private Integer age;
    @Pattern(regexp = "^((13[0-9])|(14[5,7,9])|(15([0-3]|[5-9]))|(166)|(17[0,1,3,5,6,7,8])|(18[0-9])|(19[8|9]))\\d{8}$", message = "手機號格式錯誤")
    private String phoneNum;
    @Email(message = "郵箱格式錯誤")
    private String email;

    @NotNull(message = "任課老師不能為空")
    @Size(min = 1, message = "至少有一個老師")
    private List<TeacherBean> teacherBeans;
}

上面這樣寫,對teacherBeans只校驗了NotNull, 和 Size,並沒有對teacher信息里面的字段進行校驗,如果需要多里面的屬性也進行校驗,可以在teacherBeans中加上 @Valid

@Valid
@NotNull(message = "任課老師不能為空")
@Size(min = 1, message = "至少有一個老師")
private List<TeacherBean> teacherBeans;

四、驗證結果接收

可以Controller的方法入參上添加BindingResult參數,用於接收校驗后的驗證結果,如果多個校驗對象,那么每個@Validated后面跟着的BindingResult就是這個@Validated的驗證結果,順序不能亂。

@Validated People p, BindingResult result, @Validated Person p2

可能用到的操作:

// result是BindingResult 實例
if(result.hasErrors()){
    List<ObjectError> allErrors = result.getAllErrors();
    for(ObjectError error : allErrors){
        FieldError fieldError = (FieldError)error;
        // 屬性
        String field = fieldError.getField();
        // 錯誤信息
        String message = fieldError.getDefaultMessage();
        System.out.println(field + ":" + message);
        
        
    }
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM