【使用篇二】SpringBoot服务端数据校验(8)

时间:2023-03-08 18:42:35

对于任何一个应用而言,客户端做的数据有效性验证都不是安全有效的,而数据验证又是一个企业级项目架构上最为基础的功能模块,这时候就要求我们在服务端接收到数据的时候也对数据的有效性进行验证。为什么这么说呢?往往我们在编写程序的时候都会感觉后台的验证无关紧要,毕竟客户端已经做过验证了,后端没必要在浪费资源对数据进行验证了,但恰恰是这种思维最为容易被别人钻空子。毕竟只要有点开发经验的都知道,我们完全可以模拟 HTTP 请求到后台地址,模拟请求过程中发送一些涉及系统安全的数据到后台,后果可想而知....

验证分两种:对封装的Bean进行验证  或者  对方法简单参数的验证。

说明:SpringBoot 中使用了 Hibernate-validate 校验框架作为支持

1. 创建项目Maven Project,修改pom.xml

<properties>
        <java.version>1.8</java.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
        <!-- jstl -->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>jstl</artifactId>
    </dependency>
    <!-- jasper:jsp引擎 -->
    <dependency>
        <groupId>org.apache.tomcat.embed</groupId>
        <artifactId>tomcat-embed-jasper</artifactId>
        <scope>provided</scope>
    </dependency>
</dependencies>

2. 在src/main/resources目录下新建application.properies,并添加全局配置

#jsp视图映射配置
spring.mvc.view.prefix=/WEB-INF/jsp/
spring.mvc.view.suffix=.jsp

3. 创建实体类并添加校验注解

import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import org.hibernate.validator.constraints.Length;

public class User {

    private Integer userId;
    @NotBlank
    @Length(min=2,max=10)
    private String userName;
    @NotBlank
    private String password;
    private Integer age;
    @Email
    private String email;
    public User() {

    }
    public User(Integer userId, String userName, String password, Integer age, String email) {
        this.userId = userId;
        this.userName = userName;
        this.password = password;
        this.age = age;
        this.email = email;
    }
    public Integer getUserId() {
        return userId;
    }
    public void setUserId(Integer userId) {
        this.userId = userId;
    }
    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    public void setEmail(String email) {
        this.email = email;
    }

    public String getEmail() {
        return email;
    }
}

4. 编写Controller

import javax.validation.Valid;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;

@RestController
@RequestMapping("/user")
public class UserController {

    /**
     * 页面跳转
     */
    @RequestMapping("/{page}")
    public Object showPage(Model model,@PathVariable String page, User user){
        ModelAndView view = new ModelAndView();
        view.addObject(model);
        view.setViewName(page);
        return view;
    }

    /*
     * @Validated 开启对 User对象的数据校验 (User对象需要添加注解)
     * BindingResult:封装了校验的结果,会自动添加到model对象进行传递
     * 注意:@Validated 和 BindingResult 是一一对应的,如果有多个@Valid,那么每个@Validated后面跟着的BindingResult就是这个@Validated的验证结果,顺序不能乱
     */
    @RequestMapping("/addUser")
    public Object addUser(Model model, @Validated User user, BindingResult result){
        ModelAndView view = new ModelAndView();
        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);
                //model的错误信息暂时不知怎么获取对应的消息,所以解析BindingResult后,单独放入model,在页面再获取
                model.addAttribute(field, message);

            }
            view.addObject(model);
            view.setViewName("input");
            return view;
        }
        view.setViewName("ok");
        return view;
    }
}

5. 在src/main目录下创建webapp/WEB-INF/jsp目录,在jsp文件夹下编写视图jsp

(1) 表单输入页面:input.jsp

<%@ page language="java" contentType="text/html; charset=utf-8"
    pageEncoding="utf-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>添加用户</title>
</head>
<body>
    <h3>添加用户:</h3>
    <form action="/user/addUser" method="post">
        用户姓名:<input type="text" name="userName" /><font color="red">${userName }</font><br/>
        用户密码:<input type="password" name="password" /><font color="red">${password }</font><br/>
        用户年龄:<input type="text" name="age" /><font color="red">${age }</font><br/>
        用户邮箱:<input type="text" name="email" /><font color="red">${email }</font><br/>
        <input type="submit" value="确定" /><br />
    </form>
</body>
</html>

(2) 操作成功页面:ok.jsp

<%@ page language="java" contentType="text/html; charset=utf-8"
    pageEncoding="utf-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>操作成功</title>
</head>
<body>
    <h5>操作 成功</h5>
</body>
</html>

6. 自定义错误信息提示

注:此处也可不在配置文件中配置,直接在验证的massage中写。

(1) 在resources 目录下新建提示信息配置文件ValidationMessages.properties,文件名是固定的,因为SpringBoot自动读取classpath中的ValidationMessages.properties里的错误信息。

ValidationMessages.properties 文件的编码为ASCII,格式为 key=value 。

#用户名不能为空
user.userName.notBlank=\u7528\u6237\u540D\u4E0D\u80FD\u4E3A\u7A7A
#密码不能为空
user.password.notBlank=\u5BC6\u7801\u4E0D\u80FD\u4E3A\u7A7A

(2) 修改实体类

private Integer userId;
@NotBlank(message="{user.userName.notBlank}")
@Length(min=2,max=10)
private String userName;
@NotBlank(message="{user.password.notBlank}")
private String password;
private Integer age;
@Email
private String email;

7. 方法参数中简单类型的校验

(1) 在Controller类上添加@Validated注解,只有添加这个,方法参数中的@Email等注解才能起作用

(2) 在Controller的方法参数前加上注解,可以加多个

@RequestMapping("/addUser2")
    public Object addUser(@NotBlank(message = "name 不能为空") @Length(min = 2, max = 10, message = "name 长度必须在 {min} - {max} 之间")String userName){
        ModelAndView view = new ModelAndView();
        view.setViewName("ok");
        return view;
    }

以上方法会抛出ConstraintViolationException异常:

javax.validation.ConstraintViolationException: addUser.userName: name 不能为空, addUser.userName: name 长度必须在 2 - 10 之间

无论是对象校验还是简单类型校验,如果存在需要校验多个,就会很麻烦,可用异常的方式进行统一处理,后续再讲。

8. 常用的Validation注解

@NotNull			值不能为空
@Null				值必须为空
@Pattern(regex=)	字符串必须匹配正则表达式
@Size(min, max)		集合元素的数量必须在min和max之间
@CreditCardNumber(ignoreNonDigitCharacters=)	字符串必须是信用卡号,按照美国的标准验证
@Email				字符串必须是Email地址
@Length(min, max)	检查字符串的长度
@NotBlank			字符串不能为空串(去掉首尾空格)
@NotEmpty			字符串不能为null, 集合必须有元素
@Range(min, max)	数字必须大于min, 小于max
@SafeHtml			字符串必须是安全的html
@URL				字符串必须是合法的URL
@AssertFalse		值必须是false
@AssertTrue			值必须是true
@DecimalMax(value=, inclusive=)	值必须小于等于(inclusive=true)/小于(inclusive=false)属性指定的值,也可以注释在字符串类型的属性上。
@DecimalMin(value=, inclusive=)	值必须大于等于(inclusive=true)/小于(inclusive=false)属性指定的值,也可以注释在字符串类型的属性上。
@Digist(integer=,fraction=)	数字格式检查。integer指定整数部分的最大长度,fraction指定小数部分的最大长度
@Future				时间必须是未来的
@Past				时间必须是过去的
@Max(value=)		值必须小于等于value指定的值。不能注解在字符串类型属性上。
@Min(value=)		值必须小于等于value指定的值。不能注解在字符串类型属性上。

9. 总结

需要注意的:

(1) 对象的校验,错误信息在jsp页面如何获取?

(2) 方法参数中简单类型的校验,Controller类上必须添加@Validated注解。

(3) 自定义校验的提示信息,ValidationMessages.properties的名称及文件的编码