spring boot:使用validator做接口的參數、表單、類中多字段的參數驗證(spring boot 2.3.1)


一,為什么要做參數驗證?

永遠不要相信我們在后端接收到的數據,

1,防止別人通過接口亂刷服務:有些不懷好意的人或機構會亂刷我們的服務,例如:短信接口,

  相信大家可能很多人在工作中遇到過這種情況

2,防止sql注入等行為:如果對數據會行嚴格的驗證,可以過濾掉大量的攻擊行為

3,防止客戶端出錯后的生成數據錯誤

所以,后端必須進行參數校驗,

即使前端已經校驗過,因為我們不能保證我們收到的請求都是由我們的前端程序發出

 

說明:劉宏締的架構森林是一個專注架構的博客,地址:https://www.cnblogs.com/architectforest

         對應的源碼可以訪問這里獲取: https://github.com/liuhongdi/

說明:作者:劉宏締 郵箱: 371125307@qq.com

 

二,演示項目的相關信息

1,演示項目的地址:

https://github.com/liuhongdi/validator

 

2,演示項目的原理:

   演示了三種情況:

   直接針對controller的參數校驗

   針對一個表單校驗

   針對通用的參數用攔截器進行校驗

 

3,項目結構

如圖:

 

三, 如何使用validation庫?

1,pom.xml中引入validation

        <!--validation begin-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
        <!--validation   end-->

 

2,validation有哪些現成的注解可用?

2.1 空檢查 @Null 驗證對象是否為空 @NotNull 驗證對象不為空 @NotBlank 驗證字符串不為空或不是空字符串 @NotEmpty 驗證對象不為Null,或者集合不為空
2.2 長度檢查 @Size 驗證對象長度,支持字符串,集合 @Length 驗證字符串大小(於 org.hibernate.validator.constraints 包中)
2.3 數值檢查 @Min 驗證數字是否大於等於指定值 @Max 驗證數字是否小於等於指定值 @Digits 驗證數字是否符合指定的格式 @Range 驗證數字是否在指定的范圍內 @Negative 驗證數字是否為負數 @NegativeOrZero 驗證數字是否小於等於0 @Positive 驗證數字是否為正數 @PositiveOrZero驗證數字是否大於等於0 @DecimalMin 驗證數字是否大於指定值 @DecimalMax 驗證數字是否小於等於指定值 2.4 時間檢查 @Future 檢查時間是否晚於現在 @FutureOrPresent 檢查時間是否非早於現在 @Past 檢查時間是否早於現在 @PastOrPresent 檢查時間是否非晚於現在
2.5 其他 @Email 檢查是否一個合法的郵箱地址 @Pattern 檢查是否符合指定的正則規則

 

3,如何配置使validator匹配到一個錯誤時立即返回,而不是等所有字段驗證完?

ValidatorConfig.java

@Configuration
public class ValidatorConfig {
    /*
    *@author:liuhongdi
    *@date:2020/7/12 上午10:48
    *@description:遇到第一個錯誤后立即返回,而不是遍歷完全部錯誤
    */
    @Bean
    public Validator validator() {
        ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
                .configure()
                .addProperty("hibernate.validator.fail_fast", "true") //快速驗證模式,有第一個參數不滿足條件直接返回
                .buildValidatorFactory();
        return validatorFactory.getValidator();
    }

    @Bean
    public MethodValidationPostProcessor methodValidationPostProcessor() {
        MethodValidationPostProcessor postProcessor = new MethodValidationPostProcessor();
        postProcessor.setValidator(validator());
        return postProcessor;
    }
}

 

四,例一:自定義一個validator

1,controller中直接用注解驗證參數:(適用於參數少的情況)

@Validated
@Controller
@RequestMapping("/home")
public class HomeController {

    @GetMapping("/age")
    @ResponseBody
    public ResultUtil age(@Min(value = 10,message = "年齡最小為10")@Max(value = 100,message = "年齡最大為100") @RequestParam("age") Integer age) {
        return ResultUtil.success("this is age");
    }

說明:使用以下url可以測試效果

http://127.0.0.1:8080/home/age
http://127.0.0.1:8080/home/age?age=1
http://127.0.0.1:8080/home/age?age=60
http://127.0.0.1:8080/home/age?age=101

 

2, 自定義validator要用的注解

功能說明:傳遞的v參數必須是我們在常量中定義的值

VersionValid.java:用來定義注解

//驗證版本號是否符合系統定義
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER,ElementType.FIELD})
@Constraint(validatedBy = VersionValidator.class)
public @interface VersionValid {
    //用value傳遞的值
    //String values();
    //version無效時的提示內容
    String message() default "version必須屬於預定義的值";

    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

VersionValidator.java  (override這個方法:isValid,用來驗證數據是否合法)

public class VersionValidator implements ConstraintValidator<VersionValid,Object> {

    //預定義傳遞的值
    //private String values;
    @Override
    public void initialize(VersionValid versionValidator) {
        //this.values = versionValidator.values();
    }

    //version是否符合定義
    @Override
    public boolean isValid(Object value, ConstraintValidatorContext constraintValidatorContext) {
        // 切割獲取值
        String[] value_array = Constants.APP_VERSION_LIST.split(",");
        Boolean isFlag = false;
        for (int i = 0; i < value_array.length; i++){
            // 存在一致就跳出循環
            if (value_array[i] .equals(value)){
                isFlag = true; break;
            }
        }
        return isFlag;
    }
}

在controller中調用VersionValid注解

    @GetMapping("/home")
    @ResponseBody
    public ResultUtil home(@VersionValid(message = "v取值錯誤")@RequestParam("v") String version) {
        return ResultUtil.success("this is home");
    }

 

3,測試效果:

錯誤

http://127.0.0.1:8080/home/home
http://127.0.0.1:8080/home/home?v=1

正確:

http://127.0.0.1:8080/home/home?v=1.0

 

五,例二:針對一個表單中多字段的validator

1,說明:有多個字段要驗證的表單,

           如果寫到controller中會使代碼過於龐大而不便管理 

           通常我們會定義一個專門類進行處理

這里要說明的是:類中的驗證是針對每個字段的,

如果我們要比較類中兩個或以上的字段值,(例如:注冊頁面:驗證兩次輸入的密碼是否一致)

則需要針對整個類定義一個validator

 

2,定義一個注解:

PassValid.java

//用來驗證類中多個字段的validator的注解
@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PassValidator.class)
@Documented
public @interface PassValid {
         //報錯信息
        String message() default "confirmPassword:兩次輸入密碼需一致";
        Class<?>[] groups() default {};
        Class<? extends Payload>[] payload() default {};
          //密碼字段
          String password();
          //確認密碼字段
          String password_confirm();
}

PassValidator.java 判斷兩次輸入的密碼是否一致的validator

//validator,判斷兩個密碼是否一致
public class PassValidator implements ConstraintValidator<PassValid, Object> {

     //密碼
     private String passFieldName;
     //確認密碼
     private String confirmFieldName;

     @Override
     public void initialize(final PassValid constraintAnnotation) {
         passFieldName = constraintAnnotation.password();
         confirmFieldName = constraintAnnotation.password_confirm();
     }

     @Override
     public boolean isValid(final Object src, final ConstraintValidatorContext context) {
         BeanWrapperImpl wrapper = new BeanWrapperImpl(src);
        Object passObj = wrapper.getPropertyValue(passFieldName);
         Object confirmObj = wrapper.getPropertyValue(confirmFieldName);
         return passObj != null && passObj.equals(confirmObj);
     }
 }

應用上面創建的validator,實現針對整個表單的驗證

@PassValid(password = "password", password_confirm = "confirmPassword")
public class UserVo {

    @Size(min = 2,max = 10,message = "name:姓名長度必須為1到10")
    private String name;
    public String getName() {
        return this.name;
    }
    public void setName(String name) {
        this.name = name;
    }

    //@Range(min=10, max=100,message = "年齡需位於10到100之間")
    @Min(value = 10,message = "age:年齡最小為10")
    @Max(value = 100,message = "age:年齡最大為100")
    private int age;
    public int getAge() {
        return this.age;
    }
    public void setAge(int age) {
        this.age = age;
    }

    @NotNull(message = "mobile:手機號碼不能為空")
    @Size(min = 11, max = 11, message = "mobile:手機號碼必須為11位")
    @Pattern(regexp="^[1]\\d{10}$", message="mobile:手機號碼格式錯誤")
    private String mobile;
    public String getMobile() {
        return this.mobile;
    }
    public void setMobile(String mobile) {
        this.mobile = mobile;
    }

    @NotBlank(message = "email:郵箱不能為空")
    @Email(message = "email:郵箱格式錯誤")
    private String email;
    public String getEmail() {
        return this.email;
    }
    public void setEmail(String email) {
        this.email = email;
    }

    @NotBlank(message = "password:密碼不能為空")
    String password;
    public String getPassword() {
        return this.password;
    }
    public void setPassword(String password) {
        this.password = password;
    }

    @NotBlank(message = "confirmPassword:確認密碼不能為空")
    String confirmPassword;
    public String getConfirmPassword() {
        return this.confirmPassword;
    }
    public void setConfirmPassword(String confirmPassword) {
        this.confirmPassword = confirmPassword;
    }

    // @Pattern(regexp="^[0-9]{4}-[0-9]{2}-[0-9]{2}$",message="出生日期格式不正確")
    //@Pattern(regexp="^(\\d{18,18}|\\d{15,15}|(\\d{17,17}[x|X]))$", message="身份證格式錯誤")

}

在controller中應用:

    @GetMapping("/user")
    //@ResponseBody
    public String user() {
        return "user/user";
    }

    @PostMapping("/usersaveed")
    @ResponseBody
    //public ResultUtil usersaveed(@Validated UserVo userVo) {
    public ResultUtil usersaveed(@Validated UserVo userVo) {
        System.out.println("----------email:"+userVo.getEmail());
        return ResultUtil.success("this is in usersaveed");
    }

 

3,測試效果:

http://127.0.0.1:8080/home/user

如圖:

 

六,例三:針對通用的參數,用interceptor做校驗

1,說明:

我們在訪問接口時,通常有一些通用的參數要傳遞,

例如:

appid:通常是所在平台

version:客戶端的版本

uuid:客戶端的唯一id

 

2,定義interceptor要匹配的地址

DefaultMvcConfig.java

@Configuration
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
public class DefaultMvcConfig implements WebMvcConfigurer {

    @Resource
    private ValidatorInterceptor validatorInterceptor;

    /**
     * 添加Interceptor
     * 檢驗參數不能全部覆蓋,因為可能有供第三方訪問的接口地址,例如支付的回調接口
     * 所以需要把不用的排除掉
     */

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(validatorInterceptor)
                .addPathPatterns("/**")                    //所有請求都需要進行報文簽名sign
                .excludePathPatterns("/home/age**","/home/home**","/home/user**","/js/**","/");   //排除age/home...url
    }

}

ValidatorInterceptor.java :實現通用參數驗證的interceptor

@Component
public class ValidatorInterceptor implements HandlerInterceptor {
    /*
    *@author:liuhongdi
    *@date:2020/7/1 下午4:00
    *@description:檢查通用的變量是否存在,是否合法
     * @param request:請求對象
     * @param response:響應對象
     * @param handler:處理對象:controller中的信息   *
     * *@return:true表示正常,false表示被攔截
    */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //檢查appid是否存在?
        String appId = request.getParameter("appid");
        if (appId == null) {
            throw new BusinessException(ResponseCode.ARG_NO_APPID);
        }
        //appid是否符合定義
        if (!Constants.APP_ID_LIST.contains(appId)) {
            throw new BusinessException(ResponseCode.ARG_APPID_VALID);
        }
        //version參數是否存在
        String version = request.getParameter("version");
        if (version == null) {
            throw new BusinessException(ResponseCode.ARG_NO_VERSION);
        }
        //當appid是ios時,version是否符合定義
        if (appId.equals("IOS")) {
            if (!Constants.IOS_VERSION_LIST.contains(version)) {
                throw new BusinessException(ResponseCode.ARG_VERSION_VALID);
            }
        }
        //uuid參數是否存在
        String uuid = request.getParameter("uuid");
        if (uuid == null) {
            throw new BusinessException(ResponseCode.ARG_NO_UUID);
        }
        //sign校驗無問題,放行
        return true;
    }
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    }
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    }
}

 

在controller中調用:

    @GetMapping("/category")
    @ResponseBody
    public ResultUtil category(@Pattern(regexp="^[a-zA-Z]{4}$", message="分類取值錯誤")@RequestParam("cate") String category) {
        return ResultUtil.success("this is in category");
    }

 

3,測試效果:

錯誤的訪問:

http://127.0.0.1:8080/home/category

正確的訪問:

http://127.0.0.1:8080/home/category?appid=IOS&version=1.1&uuid=06C58F98-51F7-4C35-AC4C-B56D265CD3E9&cate=abcd

 

七,查看spring boot的版本

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.3.1.RELEASE)

 


免責聲明!

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



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