KAIROS.WORKSPACE
技术宇宙 / ONLINE
返回文章列表
前端工程intermediate7 分钟

表单校验的分层:HTML 属性、JS 增强与服务端最终判定

邮箱订阅、评论这类表单如何做三层校验——原生属性守底线、JS 提升体验、服务端权威判定,避免"前端校验即安全"的常见误区。

#表单#校验#安全#UX
排版
字号
行宽
目录 · 6 节

一个提交邮箱的表单,“对不对”由谁说了算?正确答案是三层各管一段,谁都不能替代谁。把它们混为一谈,就会同时得到糟糕的安全和糟糕的体验。

第一层:HTML 属性——零 JS 的底线

<input
  name="email"
  type="email"
  required
  maxlength="254"
  autocomplete="email"
/>
  • type="email":浏览器原生拦截“明显不是邮箱”的输入,无需任何 JS;
  • required:空提交被表单原生阻止;
  • maxlength:把超长输入挡在客户端,防止无意义大包;
  • autocomplete:填表体验和自动填充的正确性。

这层的价值是它在 JS 完全不可用时仍然工作(渐进增强基线)。但它只能保证“格式像”,不能保证“真实、合法、没滥用”。

第二层:JS 增强——体验,而非安全

客户端脚本做的是“更早、更准、更友好”:

  • 失焦即校验,实时红字提示(aria-live 播报,别只靠变色);
  • 提交时 fetch JSON 接口,把结果渲染在行内(成功/限流/服务不可用分别给不同文案,且透传服务端真实 message);
  • 提交瞬间禁用按钮 + “正在发送…”,防重复提交;
  • fetch 失败自动回退原生整页提交(增强挂了退回第一层)。

关键认知:这一层是给正常用户用的,不是防坏人用的。 任何人关掉 JS 或直接用 curl 打接口,第二层形同虚设。把“邮箱格式校验”只写在这一层,等于没写。

第三层:服务端——唯一权威

服务端必须假设前两层都不存在,独立完成全部判定:

// 1) 再次校验格式(不信任客户端)
if (!isValidEmail(email)) return respond(request, 'invalid', {}, 400);
// 2) 限流:按 IP + 账号维度,防刷
if (await isRateLimited(...)) return respond(request, 'rate-limited', {...}, 429);
// 3) 业务规则:topic 白名单、去重、幂等
// 4) 数据落库/投递

服务端还负责定义错误语义:哪些是“用户可修复”(格式错、已订阅),哪些是“用户不可修复”(服务暂不可用)。前端要如实透传这个区分——把 503 翻译成“邮箱格式不对”是真实发生过的反例(详见踩坑《订阅接口 503 被前端翻译成了“邮箱格式不对”》)。

三层如何协作而不互相甩锅

关注点 归属层 反例
必填/格式像不像 HTML 只做 JS 校验 → 关 JS 就绕过
即时友好反馈 JS 只在服务端返回整页错误 → 体验差
真实合法性 服务端 信任客户端 valid=true → 可被伪造
防滥用/限流 服务端 只做前端“按钮防连点” → 脚本秒绕过
错误分类与文案 服务端定、前端透传 前端自己瞎猜错误原因

一个常被忽略的点:可访问的校验

错误提示不能只靠红色——色盲用户看不见。正确做法:

  • 出错字段 aria-invalid="true" + aria-describedby 指向错误文案;
  • 错误文案容器 role="alert"aria-live="polite",让读屏器播报;
  • 提交失败后焦点移到第一个错误字段。

原生 :invalid 伪类也能做视觉标记,但同样要配文本,不能只变色。

小结

表单校验不是“要不要加前端校验”的二选一,而是三层的明确分工:HTML 守无 JS 底线、JS 提升正常用户体验、服务端做不可绕过的权威判定,并把错误语义交给前端如实透传。任何一层的缺失,都会以“安全洞”或“体验坑”的形式,在你看不到的地方兑现。

Conversation

评论与互动

正在加载评论…

提交后需审核,不会立即公开。

Keep exploring

继续探索