当前位置: 首页 > 科技观察

Node如何在Controller层进行数据校验

时间:2023-03-15 09:05:08 科技观察

一位幽默的后端程序员普遍自嘲是CURDBoy。CURD,即对某个存储资源的增删改查,是完全面向数据的编程。太好了,面向数据的编程通常对业务有更透彻的理解,这样才能编写出更高质量的代码,产生的错误也会更少。既然是面向数据的编程,就更需要避免脏数据的出现,加强数据校验。否则,我们应该信任前端数据验证吗?毕竟前端的数据校验是直接到用户那里去的,在UI层进行更友好的用户反馈。数据验证层的后端由于业务逻辑繁重,需要处理的数据种类繁多,所以分为多个层级。我经历过的后端项目分为Controller、Service、Model、Helper、Entity等,有各种命名层。但是这里肯定有一个层叫Controller,它站在后端的顶层,直接接收客户端传来的数据。由于Controller层是服务端与客户端数据交互的顶层,秉承FailFast的原则,肩负着数据过滤的功能,直接将非法数据发回,就像秦琼和尉迟恭的威严。数据验证还会生成半文档化的副产品。数据校验层只需要看一眼就知道要传递哪些字段,是什么格式。下面是常见的数据校验,本文介绍如何校验:required/optional基本数据校验,比如number,需要满足复杂数据校验的字符串、时间戳和值,比如IP、手机号、Email和域名constbody={id,name,mobilePhone,email}山月接触过一个后端项目,没有数据验证层。if/else层层泛滥,异常痛苦,分分钟需要重构。JSONSchemaJSONSchema是基于JSON的数据验证格式,附有规范json-schema.org[1],最新版本(2020-08)为7.0。各种服务器编程语言都实现了规范,比如go、java、php等,当然也有很棒的javascript,比如不温不火的ajv[2]。以下是验证用户信息的架构。可见语法复杂繁琐:{"$schema":"http://json-schema.org/draft-04/schema#","title":"User","description":"用户信息","type":"object","properties":{"id":{"description":"用户ID","type":"integer"},"name":{"description":"用户名","type":"string"},"email":{"description":"用户邮箱","type":"string","format":"email","maxLength":20},"mobilePhone":{"description":"用户电话号码","type":"string","pattern":"^(?:(?:\+|00)86)?1[3-9]\d{9}$","maxLength":15}},"required":["id","name"]}对于复杂的数据类型校验,JSONSchema内置了如下格式,方便快捷检查:日期和时间电子邮件地址主机名IP地址资源identifiersURItemplateJSONPointerRegularExpressions对于没有内置格式的手机号码,使用ajv.addFormat手动添加格式:ajv.addFormat('mobilePhone',(str)=>/^(?:(?:\+|00)86)?1[3-9]\d{9}$/.test(str));Joijoi号称是最强大的JS验证库,在github上也获得了16000星。与JSONSchema相比,它的语法更加简洁和强大。最强大的JS数据验证库做同样的验证,需要更少的代码,并且能够执行更强大的验证。以下仅为示例,更多示例请查看文档。constschema=Joi.object({id:Joi.number().required(),name:Joi.number().required(),email:Joi.string().email({minDomainSegments:2,tlds:{allow:['com','net']}}),手机:Joi.string().pattern(/^(?:(?:\+|00)86)?1[3-9]\d{9}$/),password:Joi.string().pattern(/^[a-zA-Z0-9]{3,30}$/),//校验同passwordrepeatPassword:Joi.ref('password'),})//密码和重复密码需要同时发送with('password','repeat_password');//可以提供邮箱和手机号。xor('email','mobilePhone')datacalibration校验和路由层集成由于数据直接从路由传过来,koajs官方基于joi实现了一个joi-router[4],将前置数据校验到路由层,对前端传过来的query、body、params进行校验。joi-router还根据co-body对前端传输的各种内容类型进行分析和限制。如果仅限于application/json,也能在一定程度上防止CSRF攻击。constrouter=require('koa-joi-router');constpublic=router();public.route({method:'post',path:'/signup',validate:{header:joiObject,query:joiObject,params:joiObject,body:joiObject,maxBody:'64kb',输出:{'400-600':{body:joiObject}},类型:'json',失败:400,continueOnError:false},pre:async(ctx,next)=>{awaitcheckAuth(ctx);returnext();},handler:async(ctx)=>{awaitcreateUser(ctx.request.body);ctx.status=201;},});正则表达式与安全正则表达式山月在排查性能问题时发现一个API在数据验证层耗时过长,这是我从来没有想到的。问题的根源在于不安全的正则表达式,那么什么是不安全的正则表达式呢?比如下面这个能挂CPU的正则表达式就是一个定时炸弹,回溯的次数进入了指数增长。constsafe=require('safe-regex')constre=/(x+x+)+y///可以杀死CPU的regexre.test('xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')//用safe-regex判断正则是否是safesafe(re)//假数据校验多为字符串校验,也会充斥着各种正则表达式。确保正则表达式的安全性非常重要。safe-regex[6]可以找出哪些不安全的正则表达式。综上所述,Controller层需要进行统一的数据校验。JSONSchema(Node实现了ajv)和JoiJSONSchema都有各种语言的官方规范和实现,但是语法比较繁琐。可以使用校验功能更强大的Joi来进行字符串校验。验证时,注意不安全正则化带来的性能问题