kotlin编译器能比运行时更早防sql注入,因其扩展函数签名、可空类型和实化泛型可在编码阶段强制分离sql结构与用户数据,使危险拼接写法无法通过编译。

NamedParameterJdbcTemplate 扩展函数 + String.toSafeOrderBy() 这类编译期约束,能直接让危险写法在 IDE 里标红、编译失败——不是靠文档提醒,而是靠 Kotlin 编译器拦住你。
为什么 Kotlin 编译器能比运行时更早防 SQL 注入
Java 的 PreparedStatement 是运行时防护,但 Kotlin 的扩展函数签名、可空类型、实化泛型这些特性,能在写代码时就卡住漏洞路径。比如:safeSelectOne 要求参数必须是 Map<string any></string>,而你传一个拼接好的 String 就根本过不了编译;又比如 String?.toSafeOrderBy() 返回非空 String,后续拼进 SQL 时不会因 null 导致逻辑跳过校验。
NamedParameterJdbcTemplate 扩展函数怎么写才真正起作用
关键不是“加了扩展”,而是函数签名是否强制分离结构与数据:
- SQL 字符串参数必须含命名占位符(
:status),禁止出现+或StringBuilder拼接痕迹 - 参数类型必须显式限定为
Map<string any></string>,不能是Any或MutableMap—— 后者容易被误塞原始请求字段 - 不提供
String版本的query重载,否则开发者会下意识选错 - 内部调用
queryForObject时,必须传SqlParameterSource或Map,不能 fallback 到裸Object[]
动态列名/排序字段的白名单校验必须编译期可见
像 ORDER BY、GROUP BY 这种没法参数化的部分,不能只靠运行时 if-else 判断,得让 IDE 和编译器知道哪些值是合法的:
-
String.toSafeOrderBy()必须返回非空String,避免调用方再做 null check 绕过 - 白名单枚举化更安全:定义
enum class SortField { id, name, created_at },再写SortField.toString()扩展,而不是字符串硬编码 - 如果从请求里取字段名,必须先转成枚举:
SortField.valueOf(requestSort),异常由 Kotlin 的IllegalArgumentException捕获,不吞掉 - 禁止在
Op.build或SqlExpressionBuilder.custom里直接插变量,Exposed 的这类逃逸通道一旦混入用户输入,防护就失效
Exposed DSL 里最容易漏掉的三个危险点
Exposed 天然安全,但它的“例外路径”恰恰是开发者最常踩坑的地方:
-
Op.build { "$colName = $value" }→ 错误:变量直接拼进字符串;正确写法是Users.columns[colName] eq value -
inList传入未校验的列表:空列表生成WHERE false,超长列表(如 >500 项)可能拖垮 PostgreSQL 查询计划;必须提前require(userInputIds.size in 1..500) - 表名/列名来自反射或配置文件:比如
Table.forName(requestTable),这等于把元数据注入入口打开;应只允许从已声明的object Users : Table()中取
真正的防护不在“用了什么框架”,而在每一处字符串拼接是否被编译器盯死——Kotlin 的类型系统和扩展函数不是装饰,是第一道闸机。











