Appearance
元信息
- 目标:三幕验证"客户端校验管体验、服务端校验管安全"——声明式客户端校验、curl 绕过、服务端校验兜底,完整走一遍报错、重填、直到成功的闭环
- 关键概念:声明式校验(required)、客户端校验的边界、服务端校验、白名单
- 关联阶段:cp1
- 常见误区:以为表单上加个
required就万事大吉(它只拦得住浏览器里的正常提交);以为客户端规则可以照搬当服务端规则(客户端求快求简、少打扰用户,服务端是最后一关,规则只会更严)
第 2 步:用 curl 跳过客户端校验
客户端校验拦得住浏览器,拦得住别的客户端吗?用curl 实训里的工具,把同样的非法数据直接 POST 给后端 save.php:
bash
curl -d "nickname=&message=short&channel=hack" http://localhost/form/save.php返回的回显页里,空昵称、5 个字符的留言、不存在的栏目 hack——全部照单全收。(若你的 save.php 是表单提交第 6 步的存库版,这些脏数据已经写进 db.txt 了。)
看清这一幕说明什么:required 和 select 的选项,全都只是浏览器渲染页面时的约定;curl 不渲染页面,约定就不存在。后端此刻不设防,谁来都是一路绿灯——这就是"客户端校验不可靠"的确切含义。
第 3 步:服务端校验——建起绕不过的防线
现在把防线建在服务器上。新建 check.php,并把 check.html 的 action 改为 ./check.php:
php
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><title>留言处理</title></head>
<body>
<?php
$nickname = $_POST['nickname'] ?? '';
$message = $_POST['message'] ?? '';
$channel = $_POST['channel'] ?? '';
$errors = [];
if (mb_strlen($nickname) < 2)
$errors[] = "昵称至少 2 个字符";
if (mb_strlen($message) < 10)
$errors[] = "留言至少 10 个字符";
if (!in_array($channel, ['info', 'web']))
$errors[] = "栏目不合法";
if ($errors) {
echo "<h3>提交有问题,请修改后再交:</h3><ul>";
foreach ($errors as $e) { echo "<li>{$e}</li>"; }
echo "</ul>";
} else {
$file = fopen("db.txt", "a");
fputs($file, "nickname : {$nickname}\nmessage : {$message}\nchannel : {$channel}\n\n");
fclose($file);
echo "<h3>留言已保存</h3><p><a href='./db.txt'>查看数据文件</a></p>";
}
?>
<p><a href="./check.html">返回表单</a></p>
</body>
</html>三条规则三种写法,覆盖校验的三个基本型:
- 非空与长度:昵称、留言用
mb_strlen检查(按字符计数,中文一个字算一个字符;XAMPP 自带 mbstring 扩展) - 白名单:栏目的合法值只有
info和web两个,不在名单内一律拒绝——枚举类字段的标准做法 - 先收集全部错误,再一次报出:
$errors数组攒齐了再显示,用户改一次就能过,不用来回试
?? '' 的意思是"没收到这个字段就当空串",防止直接访问 check.php 时 PHP 报"未定义变量"的警告。注意服务端的规则严于客户端:客户端只要求"填了",服务端还要求"填够"——两道防线的分工正在于此。
第 4 步:验证防线,走完循环
先请 curl 再来一次(第 2 步那条原样重跑,地址换成 check.php):
bash
curl -d "nickname=&message=short&channel=hack" http://localhost/form/check.php同一份数据,这次换来的是三条错误的列表。绕过浏览器的办法有千百种,绕过服务端的办法一种也没有——数据想进 db.txt,必须过这一关。
再在浏览器里走完循环:
- 昵称填
t、留言填hello——都非空,过了客户端的required;提交,被服务器拦下:昵称不足 2 字符、留言不足 10 字符,一次列出 - 昵称补成
tom,留言补够 10 个字符,再提交——"留言已保存",db.txt里出现新记录
这一轮就是表单交互图里那个 loop 块的现实版:提交、检查、退回、再提交,直到合格。真实网站登录、注册、下单,走的都是同一个循环;而 devtools 的 Network 面板里,能看到这几条连着的 POST——每次报错都是一次完整的 HTTP 请求与响应。
收束:两道防线
- 客户端校验管体验:错误当场指出、省一次网络往返,正常的用户几乎不会见到服务端的报错页
- 服务端校验管安全:所有数据入库前的最后一关,不信任任何客户端
验收标准
- [ ] 声明式校验生效:空表单在浏览器被拦,devtools 里没有新请求发出
- [ ] curl 绕过成功过一次:非法数据被不设防的 save.php 照单全收
- [ ] check.php 上线后,同一 curl 被拒;浏览器里"过客户端、被服务端"的数据能看到错误列表
- [ ] 逐项修正后提交成功,
db.txt追加了新记录 - [ ] 能说出两道防线各自的分工
思考
- 既然服务端必须校验,客户端校验还有什么用?(提示:错误当场指出,省一次网络往返,体验与流量都划算)
- 有的网站报错后,你填过的内容还在框里——服务器"记得"你吗?(它不记得上一次请求;是后端把你交上来的数据填回了重新生成的表单。服务器如何真正认出"同一个人",归后续 cookie/session 专题)
- 除了非空与长度,存库前还应检查什么?(格式如邮箱、长度上限、内容里的脚本标记——留待安全专题)