美文网首页java
总结篇-后台参数验证的几种方式(转载)

总结篇-后台参数验证的几种方式(转载)

作者: 星钻首席小管家 | 来源:发表于2020-07-03 15:23 被阅读0次

    转载自:https://blog.csdn.net/m0_37499059/article/details/81431562?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.nonecase

    1.前言
    参数验证是一个常见的问题,无论是前端还是后台,都需对用户输入进行验证,以此来保证系统数据的正确性。对于web来说,有些人可能理所当然的想在前端验证就行了,但这样是非常错误的做法,前端代码对于用户来说是透明的,稍微有点技术的人就可以绕过这个验证,直接提交数据到后台。无论是前端网页提交的接口,还是提供给外部的接口,参数验证随处可见,也是必不可少的。前端做验证只是为了用户体验,比如控制按钮的显示隐藏,单页应用的路由跳转等等。后端才是最终的保障。总之,一切用户的输入都是不可信的。

    2.常见的验证的方式
    前端的校验是必须的,这个很简单,因为客户体验。后台的校验更是必须的,关键在于如何与目前我们的分层思想(控制层、业务层、持久层)综合起来考虑。在每层都要进行校验吗?还是只在是某个特定层做就可以了?是否有好的校验框架(如前端的jquery校验框架、springmvc校验框架)?总之校验框架还是有很多的,原理不就是对后端接收的数据进行特定规则的判断,那我们怎么制定规则,有怎么去检验呢?

    1、表现层验证:SpringMVC提供对JSR-303的表现层验证;
    2、业务逻辑层验证:Spring3.1提供对业务逻辑层的方法验证(当然方法验证可以出现在其他层,但笔者觉得方法验证应该验证业务逻辑);
    3、DAO层验证:Hibernate提供DAO层的模型数据的验证(可参考hibernate validator参考文档的7.3. ORM集成)。
    4、数据库端的验证:通过数据库约束来进行;
    5、客户端验证支持:JSR-303也提供编程式验证支持。

    1)通过 if-if 判断

    if(string.IsNullOrEmpty(info.UserName))
    {
        return FailJson("用户名不能为空");
    }
    

    逐个对参数进行验证,这种方式最粗暴.如果参数一多,就要写n多的if-if,相当繁琐,更重要的是这部分判断没法重用,另一个方法又是这样判断.。

    2) 自定义注解实现参数校验
    切面拦截controller方法,然后捕获带@CheckParam注解方法参数实例,最后反射实例校验。
    controller:

    @RequestMapping(value = "update" )
    @ResponseBody
    public ResultBean update(@CheckParam User user){
        return ResultBean.ok();
    }
    

    model:

    public class User implements Serializable{
        @CheckParam(notNull = true)
        private String username;
    }
    

    annotation:

    @Target(value={ElementType.PARAMETER,ElementType.FIELD,ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface CheckParam {
        boolean notNull()  default false;
    }
    

    aspect:

    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Pointcut;
    import org.aspectj.lang.reflect.MethodSignature;
    import org.springframework.core.GenericTypeResolver;
    import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
    import org.springframework.core.MethodParameter;
    import org.springframework.stereotype.Component;
    import org.springframework.util.StringUtils;
    import java.lang.reflect.Field;
    import java.lang.reflect.Method;
    
    
    @Component
    @Aspect
    public class CheckParamAspect {
        @Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping)")
        public void methodPointCut() {}
    
        /**
         * 环绕切入方法
         **/
        @Around("methodPointCut()")
        public Object around(ProceedingJoinPoint point) throws Throwable {
            MethodSignature msig =  (MethodSignature) point.getSignature();
            Method method = msig.getMethod();
            LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
            Object[] args = point.getArgs();
            for (int i = 0; i < args.length; i++) {
                Object obj = args[i];
                MethodParameter mp = new MethodParameter(method,i);
                mp.initParameterNameDiscovery(u);
                GenericTypeResolver.resolveParameterType(mp, method.getClass());//Spring处理参数
                //String paramName = mp.getParameterName();//参数名
                CheckParam anno = mp.getParameterAnnotation(CheckParam.class);//参数注解
                if(anno != null){
                    check(obj);
                }
            }
            return point.proceed();
    
        }
    
        /**
         * 校验成员变量
         **/
        private void check(Object obj) throws IllegalAccessException {
            Class clazz  = obj.getClass();
            for(Field field : clazz.getDeclaredFields()){
                CheckParam cp = field.getAnnotation(CheckParam.class);
                if(cp != null){
                     check(obj,clazz, field,cp);
                }
            }
        }
    
        /**
         * 取出注解,校验变量
         **/
        private void check(Object obj, Class clazz, Field field, CheckParam cp) throws IllegalAccessException {
            if(cp.notNull()){
                field.setAccessible(true);
                Object f = field.get(obj);
                if(StringUtils.isEmpty(f)){
                    throw  new IllegalArgumentException("类" + clazz.getName() + "成员" + field.getName() + "检测到非法参数");
                }
            }
        }
    }
    

    3.自定义ValidationUtils
    表单验证工具类ValidationUtils,依赖包commons-lang

    import org.apache.commons.lang3.StringUtils;
    import org.apache.commons.lang3.math.NumberUtils;
    
    import java.util.HashMap;
    import java.util.Map;
    import java.util.regex.Pattern;
    
    public class ValidateUtils {
        /**
         * @param fields
         * @param params
         * @return
         * 不存在的校验规则:返回true
         * 关键字不按要求写:返回true
         */
        public static SKResult validate(ValidField[] fields, Map<String, String> params){
    
            try {
                for(ValidField field : fields){
                    String name = field.getName();
                    String desc = field.getDes();
                    boolean isValid = field.isValid();
                    String[] rules = field.getRules();
                    String value = params.get(name); // 对应请求参数值
                    if(!isValid){
                        return new SKResult(true, "");
                    }
                    for(String rule : rules){
                        String[] arr = rule.replaceAll(" ", "").split(":");
                        String arr1 = arr[0]; // required
                        String arr2 = arr[1]; // true
                        switch (arr1) {
                        case "required": // 必须项 required:true|false
                            if(Boolean.parseBoolean(arr2)){
                                if(value==null || value.trim().length()==0){
                                    return new SKResult(false, desc+"不能为空");
                                }
                            }
                            break;
                        case "number": // 必须输入合法的数字(负数,小数) number:true|false
                            if(Boolean.parseBoolean(arr2)){
                                try{
                                    Double.valueOf(value);
                                }catch(Exception e){
                                    return new SKResult(false, desc+"数值类型不合法");
                                }
                            }
                            break;
                        default:
                            break;
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
                System.out.println("===ValidField格式不合法,请注意检查!");
                return new SKResult(true, "ValidField格式不合法");
            }
            return new SKResult(true, "校验通过");
        }
    
        public static void main(String[] args) {
            Map<String, String> params = new HashMap<String, String>();
            params.put("username", "18702764599");
            params.put("password", "123");
            ValidField[] fields = {
                    new ValidField("username", "手机号", true, new String[]{
                        "required:true",
                        "isTel:true"
                        "min:5"
                        "max:5"
                    }),
                    new ValidField("password", "密码", true, new String[]{
                        "required:true",
                        "isPassword:true",
                        "equalTo:#username"
                        "max:2"
                    })
            };
    
            SKResult sk = ValidateUtils.validate(fields, params);
            System.out.println(sk);
            //SKResult [result=true, respMsg=校验通过, obj=null, type=null]
        }
    }
    

    SKResult :

    public class SKResult {
        // 返回代码
        private boolean result;
        // 错误信息
        private String respMsg;
        private Object obj;
    
        //set.get方法
        @Override
        public String toString() {
            return "SKResult [result=" + result + ", respMsg=" + respMsg + ", obj="
                    + obj + ", type=" + type + "]";
        }
    }
    

    ValidField :

    public class ValidField {   
        /**
         * 字段名
         */
        private String name;
        /**
         * 字段描述
         */
        private String des;
        /**
         * 为true必须校验
         */
        private boolean isValid = false; 
        /**
         * 校验规则
         */
        private String[] rules;
    
        public String[] getRules() {
            return rules;
        }
        public void setRules(String[] rules) {
            this.rules = rules;
        }
    
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public String getDes() {
            return des;
        }
        public void setDes(String des) {
            this.des = des;
        }
        public boolean isValid() {
            return isValid;
        }
        public void setValid(boolean isValid) {
            this.isValid = isValid;
        }
        public ValidField(String name, String des, boolean isValid, String[] rules) {
            super();
            this.name = name;
            this.des = des;
            this.isValid = isValid;
            this.rules = rules;
        }
    }
    
    1. JSR-303规范,Bean Validation
      JSR 303(Java Specification Requests 规范提案)是JAVA EE 6中的一项子规范,一套JavaBean参数校验的标准,叫做Bean Validation。JSR 303用于对Java Bean中的字段的值进行验证,Spring MVC 3.x之中也大力支持 JSR-303,可以在控制器中对表单提交的数据方便地验证。
           <!--jsr 303-->
            <dependency>
                <groupId>javax.validation</groupId>
                <artifactId>validation-api</artifactId>
                <version>1.1.0.Final</version>
            </dependency>
            <!-- hibernate validator-->
            <dependency>
                <groupId>org.hibernate</groupId>
                <artifactId>hibernate-validator</artifactId>
                <version>5.2.0.Final</version>
            </dependency>
    
    package com.example.demo;
    
    import javax.validation.ConstraintViolation;
    import javax.validation.Validation;
    import javax.validation.ValidationException;
    import javax.validation.Validator;
    import javax.validation.constraints.NotNull;
    import javax.validation.constraints.Pattern;
    import java.util.Iterator;
    import java.util.Set;
    
    /**
     * @author lanxinghua
     * @date 2018/08/05 15:51
     * @description
     */
    public class ValidateTestClass {
        @NotNull(message = "reason信息不可以为空")
        @Pattern(regexp = "[1-7]{1}", message = "reason的类型值为1-7中的一个类型")
        private String reason;
    
        public void setReason(String reason) {
            this.reason = reason;
        }
    
        public void validateParams() {
            //调用JSR303验证工具,校验参数
            Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
            Set<ConstraintViolation<ValidateTestClass>> violations = validator.validate(this);
            Iterator<ConstraintViolation<ValidateTestClass>> iter = violations.iterator();
            if (iter.hasNext()) {
                String errMessage = iter.next().getMessage();
                throw new ValidationException(errMessage);
            }
        }
    }
    
    package com.example.demo;
    
    /**
     * @author lanxinghua
     * @date 2018/08/05 15:56
     * @description
     */
    public class ValidateTestClassValidateTest {
        public static void main(String[] args) {
            ValidateTestClass validateTestClass = new ValidateTestClass();
            validateTestClass .setReason(null);
            validateTestClass .validateParams(); //调用验证的方法
        }
    }
    
    1. JSR-303规范,Bean Validation在SSM项目中使用
      JSR和Hibernate validator的校验只能对Object的属性进行校验。
      5.1 Model 中添加校验注解
    public class Book {
       private long id;
       @NotEmpty(message = "书名不能为空")
       private String bookName;
       @NotNull(message = "ISBN号不能为空")
       private String bookIsbn;
       @DecimalMin(value = "0.1",message = "单价最低为0.1")
    private doubleprice; // getter setter .......  }
    

    5.2 在controller中使用此校验

    @RequestMapping(value = "/book",method = RequestMethod.POST)
       public void addBook(@RequestBody @Valid Book book) {
           System.out.println(book.toString());
    }
    

    5.3 分组验证
    对同一个Model,我们在增加和修改时对参数的校验也是不一样的,这个时候我们就需要定义分组验证,步骤如下:

    定义两个空接口,分别代表Person对象的增加校验规则和修改校验规则

    //可以在一个Model上面添加多套参数验证规则,此接口定义添加Person模型修改时的参数校验规则

    public interface PersonAddView {}
    
    public interface PersonModifyView {}
    

    Model上添加注解时使用指明所述的分组

    public class Person {
       private long id;
       /**
        * 添加groups 属性,说明只在特定的验证规则里面起作用,不加则表示在使用Deafault规则时起作用
        */
    
       @NotNull(groups = {PersonAddView.class, PersonModifyView.class}, message= "添加、修改用户时名字不能为空",payload = ValidateErrorLevel.Info.class)
    
       @ListNotHasNull.List({
               @ListNotHasNull(groups = {PersonAddView.class}, message = "添加上Name不能为空"),
               @ListNotHasNull(groups = {PersonModifyView.class}, message = "修改时Name不能为空")})
       private String name;
    
       @NotNull(groups = {PersonAddView.class}, message = "添加用户时地址不能为空")
       private String address;
    
       @Min(value = 18, groups = {PersonAddView.class}, message = "姓名不能低于18岁")
       @Max(value = 30, groups = {PersonModifyView.class}, message = "姓名不能超过30岁")
       private int age;
     //getter setter 方法......
    
    }
    

    此时启用校验和之前的不同,需要指明启用哪一组规则

    /**
     * 备注:此处@Validated(PersonAddView.class)表示使用PersonAndView这套校验规则,若使用@Valid 则表示使用默认校验规则,若两个规则同时加上去,则只有第一套起作用
     * 修改Person对象
     * 此处启用PersonModifyView这个验证规则
    */
    @RequestMapping(value = "/person", method = RequestMethod.PUT)
    public void modifyPerson(@RequestBody @Validated(value ={PersonModifyView.class}) Person person) {
           System.out.println(person.toString());
    }
    
    1. Spring validator 方法级别的校验
      JSR和Hibernate validator的校验只能对Object的属性进行校验,不能对单个的参数进行校验,spring 在此基础上进行了扩展,添加了MethodValidationPostProcessor拦截器,可以实现对方法参数的校验
    public @NotNull UserModel get2(@NotNull @Size(min = 1) Integer uuid) {  
        //获取 User Model  
        UserModel user = new UserModel(); //此处应该从数据库获取  
        return user;  
    }  
    
    1. java开源验证框架OVAL
      我发现我们公司dubbo服务暴露的接口用这套框架来验证。
    //下单支付预处理
    @Validator({@Check(name = "orderDTO", adapter = NotNull.class, message = "订单详情不能为空", errorCode = "10")})
    Result<BossOrderDTO> orderPayPrepare(BossOrderDTO orderDTO);
    

    hibernater-validator依赖于validation-api,说明这个框架是实现了bean validation规范的,从测试中也可以看出,既可以使用javax.validation包下的注解来做校验,也可以使用自身的注解;而oval不依赖于validation-api.两者大同小异,实现的原理也差不多. Java开源验证框架Oval是一个可扩展的Java对象数据验证框架,功能强大使用简单,验证规则可通过配置文件、注解等方式进行设置,规则的编写可以使用纯Java、JavaScript 、Groovy 、BeanShell等语言。

    <dependency>
        <groupId>net.sf.oval</groupId>
        <artifactId>oval</artifactId>
        <version>1.81</version>
    </dependency>
    

    实现Oval实体对象类,用户的年龄和名字进行校验,具体代码如下:

    public class OvalTest {
        @Min(18)
        private int age;
        @Length(min = 6, max = 12)
        private String name;
    
        public static void main(String[] args) {
            OvalTest ovalTest = new OvalTest();
            ovalTest.age = 12;
            ovalTest.name = "yoodb";
            Validator validator = new Validator(); 
            List<ConstraintViolation> ret = validator.validate(ovalTest);
            System.out.println(ret);
        }
    }
    

    JSR提供的校验注解:

    @Null 被注释的元素必须为 null
    @NotNull 被注释的元素必须不为 null
    @AssertTrue 被注释的元素必须为 true
    @AssertFalse 被注释的元素必须为 false
    @Min(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
    @Max(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
    @DecimalMin(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
    @DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
    @Size(max=, min=) 被注释的元素的大小必须在指定的范围内
    @Digits (integer, fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内
    @Past 被注释的元素必须是一个过去的日期
    @Future 被注释的元素必须是一个将来的日期
    @Pattern(regex=,flag=) 被注释的元素必须符合指定的正则表达式

    Hibernate Validator提供的校验注解:

    @NotBlank(message =) 验证字符串非null,且长度必须大于0
    @Email 被注释的元素必须是电子邮箱地址
    @Length(min=,max=) 被注释的字符串的大小必须在指定的范围内
    @NotEmpty 被注释的字符串的必须非空
    @Range(min=,max=,message=) 被注释的元素必须在合适的范围内

    7.捕获异常

    @Slf4j
    @ControllerAdvice
    @RestControllerAdvice
    public class MyExceptionHander {
    
        /**
         * Bean Validation参数校验异常处理器
         * @param e 参数验证异常
         * @return ResultObject
         */
        @ExceptionHandler({MethodArgumentNotValidException.class})
        public JsonResult parameterExceptionHandler(MethodArgumentNotValidException e) {
            int errCode = 1006;
            // 获取异常信息
            BindingResult exceptions = e.getBindingResult();
            // 这里列出了全部错误参数,这里用List传回
            List<ObjectError> errors = exceptions.getAllErrors();
            // 判断异常中是否有错误信息,如果存在就使用异常中的消息,否则使用默认消息
            if(ObjectUtils.isNotEmpty(errors)){
                return new JsonResult(errCode, errors.get(0).getDefaultMessage());
            }
            return new JsonResult(errCode,"请求参数校验错误");
        }
    
    }
    

    ————————————————
    版权声明:本文为CSDN博主「蓝星花」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/m0_37499059/java/article/details/81431562

    相关文章

      网友评论

        本文标题:总结篇-后台参数验证的几种方式(转载)

        本文链接:https://www.haomeiwen.com/subject/xqpsqktx.html