Validator注解不防SQL注入,仅校验格式;它不干预SQL构建,对${}拼接、动态表名、嵌套JSON等场景完全无效,真正防御须依赖参数化查询与白名单校验。

Validator 注解本身不防 SQL 注入——它只校验数据格式,不干预 SQL 构建过程。把 @NotBlank、@Pattern 当成“SQL 防御手段”是典型误用,真这么干,攻击者填个 admin' OR '1'='1 依然能进数据库。
为什么 Validator 和 SQL 注入是两回事
Validator 的作用域在 Controller 层参数绑定之后、业务逻辑执行之前,它只检查字段是否为空、长度是否合规、正则是否匹配。它不会碰 SQL 字符串,也不影响 MyBatis 或 JdbcTemplate 怎么拼 SQL。
- @Pattern(regexp = "^[a-zA-Z0-9_]+$") 只拦不住
user_id,但拦不住user_id; DROP TABLE users--(如果后续还用 ${} 拼表名) - 即使用户名被 @Size(min=2, max=20) 卡住,攻击者仍可在排序字段传
id ASC, (SELECT password FROM admins) DESC——只要后端用 ${sortField} 就破防 - Validator 对 JSON Body 中的嵌套对象、文件上传字段、Header 里的 token 全无效,而这些都可能是 SQL 参数来源
JdbcTemplate 场景下 Validator 能帮什么忙
它唯一有用的位置,是配合「白名单校验」提前拦截非法结构参数,比如动态表名或排序字段——但这不是靠注解自动完成的,得手动编码。
- 对
tableName字段加 @Pattern(regexp = "users|products|orders"),只是第一步;你还得在 service 里显式查白名单:if (!ALLOWED_TABLES.contains(tableName)) { throw new IllegalArgumentException(); } - 对
sortField用 @Pattern 只能防简单注入,但挡不住created_at ASC, id这类合法字符串绕过;必须配sortOrder的双白名单(仅允许"ASC"或"DESC") - 别信
@Pattern(".+")或模糊正则——sortField.matches("[a-zA-Z_]+")会被id ASC, version绕过
MyBatis 中 Validator 的真实边界
Validator 能约束传给 #{} 的值,但对 ${} 完全无感。而一旦你写了 ${tableName},再严的 @Valid 也救不了。
- DTO 里写
@NotBlank String username,只保证#{username}不为 null,不防止username = "admin' --"被原样塞进预编译语句——但 PreparedStatement 本身会把它当字符串字面量,所以其实安全 - 真正危险的是你同时用了
@NotBlank String tableName+${tableName}:Validator 校验通过了,SQL 却已裸奔 - 如果非要用 ${},必须放弃注解驱动,改用硬编码白名单:
Map.of("user", "t_user", "order", "t_order").get(tableName),然后判 null 抛异常
别让 Validator 成为虚假安全感的来源
最常被忽略的一点:Validator 默认不校验 Map、List、JSON 嵌套结构里的字段。比如前端传 {"filter": {"table": "users; DROP TABLE config"}},哪怕你 DTO 有 @Valid,若没开级联校验(@Valid Filter filter),这个恶意 table 值根本不会触发任何 Pattern 检查。
更麻烦的是,很多团队把 Validator 日志打满屏幕,却从不审计它放行了哪些高危字符组合——比如 or、union、select 出现在 nickname 字段里,既不该拦(用户真叫 Orson),也不该放(可能用于布尔盲注)。这种边界问题,Validator 解决不了,得靠分层防御:参数化查询兜底 + 请求体 Filter 清洗 + 数据库权限最小化。











