Appearance
数据校验
元信息
- 目标:能把任意一条校验规则按三个维度定位(声明式/代码式、字段级/表单级、前端/后端),能说出前端校验的优势与两处缺失(可被绕过、答不了只有服务器知道的事实),并解释服务端校验与数据库约束为什么必须;能描述唯一性校验的跨端协作——前端发问与展示、服务端查证与裁决、提交时复查
- 关键概念:数据校验、声明式校验、代码校验、字段级校验、表单级校验、客户端校验、服务端校验、跨端校验、数据库约束
- 关联阶段:cp1
- 常见误区:以为有了客户端校验就够了(既可被绕过,有些事实也只有服务器知道);以为 required/pattern 能表达一切规则(跨字段的一致性表达不了,须写代码);以为校验只发生在表单里(数据库约束是最后一道防线);以为前端发请求做了唯一性预检就算前端校验(判断仍在服务端);以为预检通过就万事大吉(预检与提交之间注册表还在变,服务端提交时必须复查)
一条校验规则长什么样
先摆几条真实的规则,后面三个维度就是给任何一条规则定位的三把尺:
- 必填:邮箱不能为空
- 格式:邮箱必须是 somebody@example.com 的样子
- 长度:密码 8 到 20 位
- 值域:购买数量 1 到 99
- 一致性:两次输入的密码必须相同
- 存在性:用户名还没被别人注册过
维度一:声明式校验与代码校验
按规则怎么表达分。
声明式校验:只声明"什么算合法",执行交给环境。HTML 里 required、pattern、type="email" 都是声明式——HTML 写规则,浏览器当裁判。数据库的约束(NOT NULL 非空、UNIQUE 唯一)同理:建表时声明,数据库引擎执行。
也可以直接通过 HTML5 提供的新控件类型:
预期结果:在输入框留空或填一个非法邮箱后点提交,浏览器应弹出提示气泡并拒绝发送请求——声明写在那里,裁判是浏览器。
声明式与代码式怎么选
代码校验:自己写代码取值、判断、放行或报错。前端用 JavaScript,后端用 PHP 等语言都行。
| 声明式 | 代码式 | |
|---|---|---|
| 写法 | 一个属性一条规则 | 自己取值、判断、报错 |
| 谁执行 | 环境(浏览器、数据库) | 自己的代码 |
| 表达力 | 有限:只认单字段的常见规则 | 无上限:跨字段、查数据库、调外部接口都行 |
| 风险 | 写错一眼能看出(就一个属性) | 规则散在各处,每一处都得写对、改齐 |
声明式还有个隐藏好处:规则与执行者分离。同一条正则,浏览器执行一遍,服务端还能拿同一个表达式再执行一遍——一份规则,两处设防。
想一想:pattern 手写正则与 type="email" 内置类型,校验效果一样吗?各适合什么场景?
维度二:字段级校验与表单级校验
按规则管多大范围分。
- 字段级:单个字段自身的规则——必填、格式、长度、值域。声明式属性基本都住这一级
- 表单级(跨字段;到了数据库里对应一行记录,也叫记录级):字段各自合法还不够,字段之间还要一致——两次密码必须相同、结束日期必须晚于开始日期、选了"其他"才必填备注框
"两次密码一致"为什么 required/pattern 写不出来?因为它根本不是任何一个字段的规则,而是两个字段的关系。声明式属性只能描述单字段,跨字段的关系只能上代码。反过来记也成立:凡是住在校验属性里的规则,必然是字段级的。
维度三:前端校验与后端校验
按规则在哪一端执行分。
前端校验(在浏览器里执行)的优势:
- 即时反馈:填完立刻提示,不用等一趟网络
- 省资源:非法输入根本不发出请求,服务器白干的活全免了
但前端校验有两处缺失,缺的性质还不一样:
- 拦得住人,拦不住绕过。客户端校验只在浏览器里生效:用户按 F12 删掉
required再提交,或像第 3 周实训那样用 curl 直接发请求,校验就被整个绕过了。实训:服务端校验会亲手演示这一幕 - 答不了"只有服务器知道的事"。"用户名是否已被注册""库存还够不够""优惠码是否有效"——答案在服务器的数据库里,浏览器独自永远回答不了。所以有些校验不是前端"不可靠",是前端根本做不了。做不了的出路是跨端协作——下一节专门说
跨端的校验:唯一性
规则表最后那条"存在性:用户名还没被别人注册过"最特殊:它要查的事实(注册表)只在服务器的数据库里,浏览器独自答不了。那前端在这条规则里还有戏吗?有,但只能问。
很多网站的注册页,填完用户名不用点提交就提示"该用户名已被注册"——这不是浏览器算出来的,是浏览器发了一次请求去问服务器:服务器查数据库,答"已被注册/可用",前端把答案显示出来。一条规则,两端分工:前端管时机(输入完就问)与展示,服务端管查证与裁决——判断始终在服务端。
所以"前端发了请求做了预检"不算前端校验,而是前端触发的服务端校验:"前端做不了"的规则想要即时体验,正规解法就是替用户跑一趟腿去问。
更要注意:预检不能替代提交时的检查。预检说"可用"与真正点下注册之间隔着时间,用户名随时可能被别人抢注——两个人同时注册同一个名字,预检都显示可用。所以提交时服务端必须再查一次,最终裁决以这一查为准;真撞上了,还有数据库的 UNIQUE 约束在写入的最后一刻兜底。
想一想:预检明明显示"可用",提交却报"已被注册"——中间发生了什么?(提示:预检和提交是两次独立的检查,中间隔着时间)
服务端校验与三道防线
后端校验(服务端脚本收到数据后,用 php 等语言对各字段进行必填、格式等检查)补上这两处:请求绕不开服务器,事实也在服务器手里。所以数据是否可信,最终由服务端说了算。
再往后还有最后一道防线:数据库约束。即便服务端脚本写了漏洞漏掉了检查,NOT NULL、UNIQUE 这些声明式约束也会在写入的最后一刻拒绝脏数据。三道防线连起来是纵深防御:前端拦体验问题,后端拦恶意与错误,数据库防最坏。
思考:
- 在现实的Web应用中,为什么必须要进行服务端校验?
- 既然必须进行服务端校验,为什么还要进行客户端校验?
- 为什么"检查用户名是否已被注册"这一条,永远做不成纯前端校验?
小结
| 维度 | 分的是什么 | 一句话 |
|---|---|---|
| 声明式 / 代码式 | 规则怎么表达 | 说规则、环境执行 vs 写代码、自己执行 |
| 字段级 / 表单级 | 规则管多大范围 | 单字段自身 vs 跨字段的关系 |
| 前端 / 后端 | 规则在哪一端执行 | 浏览器里 vs 服务器上 |
- 前端校验的优势是即时与省资源;缺失有两处——可被绕过、答不了只有服务器知道的事
- 服务端校验不可省,数据库约束兜底:校验是纵深防御,不是单点
- 唯一性这类规则靠跨端协作拿到即时体验:前端挑时机发问、展示答案,查证与裁决始终在服务端,提交时还须复查
- 任何一条规则都能在三把尺上找到坐标,比如"两次密码一致":代码式、表单级、通常前端后端各做一遍;"用户名唯一":代码式、字段级、跨端——问在前端、裁决在服务端