白名单校验仅对明确定义的合法输入格式有效,需配合上下文、锚定正则、参数化查询及动态配置使用,且必须在参数绑定前完成。

白名单校验只对已知安全格式生效
输入白名单不是万能盾,它只在你能明确定义“合法输入长什么样”时才起作用。比如用户名只能是字母、数字、下划线且长度 4–20,这种场景适合白名单;但如果是用户自由输入的搜索关键词(含标点、空格、中文),硬套白名单反而会破坏功能或引发绕过。
白名单必须配合上下文使用:同一字段在不同接口中可能有不同规则。例如 user_id 在查询接口应为纯数字,在导出接口可能允许逗号分隔的多个 ID,不能共用一套正则。
- 用
String.matches()或Pattern.compile()做校验,避免手写contains()检查黑名单关键字(容易被编码、大小写、注释符绕过) - 校验应在参数绑定前完成,否则即使后续用了
PreparedStatement,非法输入仍可能污染业务逻辑或日志 - 不要对所有字段统一用同一个正则,
email、phone、nickname的规则必须分开定义和维护
常见白名单正则写法与陷阱
正则写法看似简单,但边界和锚点漏掉就等于没写。比如 "^[a-zA-Z0-9_]{4,20}$" 是安全的,而 "[a-zA-Z0-9_]{4,20}"(缺 ^ 和 $)会被 "xxx'; DROP TABLE users; -- abc" 中的 abc 部分匹配成功,导致校验失效。
-
username:建议用"^[a-zA-Z0-9_]{4,20}$",拒绝开头/结尾空格、Unicode 字符、连字符 -
phone:国内手机号用"^1[3-9]\d{9}$",别信"\d{11}"—— 它会放过"00000000000" -
email:RFC 5322 太复杂,生产环境用"^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$"已足够,重点是前后锚定 - 禁止在白名单里留“通配”余地,如
".*"、"[^;]+"这类写法实际等价于无校验
白名单不能替代参数化查询
哪怕你把 user_input 用白名单筛得干干净净,只要后续还用字符串拼接构造 SQL,就依然存在风险。比如用户输入 "admin" 符合白名单,但代码里写成 "SELECT * FROM users WHERE role = '" + role + "' AND status = 1",攻击者仍可控制 role 字段内容来闭合引号并注入。
- 白名单只管“输入像不像好人”,不管“它被怎么用”
-
PreparedStatement才真正切断数据与语句的执行关系,这是不可绕过的底线 - ORM 如 MyBatis 的
#{}、JPA 的@Param也依赖底层预编译,白名单只是前置过滤,不参与 SQL 绑定过程 - 如果业务要求动态表名或字段名(如多租户分表),白名单+运行时校验是唯一选择,但必须严格限制可选值范围,不能从请求参数直接取
白名单配置易被忽略的细节
白名单规则常写死在代码里,上线后难以动态调整。一旦规则变更(比如用户名允许短横线),就得发版,响应慢还容易出错。更隐蔽的问题是:开发、测试、生产环境的校验规则不一致,导致测试通过的输入在线上被拦截,或线上放行的输入在测试环境被误杀。
- 把白名单正则抽成配置项,通过 Spring
@Value或配置中心加载,避免硬编码 - 校验失败时返回明确错误码(如
400 BAD_REQUEST)和通用提示(“输入格式不正确”),绝不暴露正则内容或字段名 - 日志中记录被拦截的原始输入(脱敏后),用于分析绕过模式,但禁止打印完整 SQL 或堆栈
- 前端做的校验只是体验优化,后端必须重复校验 —— 用户可以禁用 JS 或直接发 curl
白名单真正起效的前提,是它和参数化查询、最小权限一起落地,而不是单独拿出来当“已防护”的心理安慰。最容易被跳过的环节,是校验位置——它必须卡在 Controller 入口或 DTO 绑定之后、Service 调用之前,晚一秒都可能让脏数据进入业务流。











