exposed的eq、like等dsl操作符天然安全,因其底层统一使用jdbc preparedstatement,严格分离sql结构与用户参数,值仅作为绑定参数传递而不参与sql字符串拼接。

Exposed 的 DSL 查询默认就防 SQL 注入,只要不手动拼接字符串,就几乎不可能中招。
为什么 eq、like 这类操作符天然安全
Exposed 的 eq、like、inList 等 DSL 操作符底层全部走 JDBC PreparedStatement,参数和 SQL 结构完全分离。你传进去的值不会出现在最终生成的 SQL 字符串里,而是作为独立参数绑定到语句上。
常见错误现象:手写 "name = '$userName'" 这种字符串拼接,哪怕用 String.format 或 """ """ 三引号模板,也照样被注入。
-
Users.select { Users.name eq userName }✅ 安全:值进 bind 参数 -
Users.select { SqlExpressionBuilder.eq(Users.name, userName) }✅ 同样安全,是底层 DSL 封装 -
Users.select { Op.build { "name = '$userName'" } }❌ 危险:绕过参数化,直接拼进 SQL
哪些 DSL 写法会意外绕过参数化
Exposed 提供了极少数“逃逸通道”,比如 Op.build、SqlExpressionBuilder.custom、或直接调用 Query.prepareSQL,这些地方如果混入用户输入,就会破坏防护。
使用场景:一般只在需要动态列名、函数名、或数据库特定语法(如 MySQL 的 JSON_CONTAINS)时才用到,不是常规查询路径。
- 避免在
Op.build里插变量:Op.build { "$colName = $value" }→ 改用Users.columns[colName] eq value - 自定义函数必须用
CustomFunction类封装,而不是拼字符串 - 所有表名、列名应来自已定义的
Table对象,不要从请求里读取后反射查找
inList 和批量参数的安全边界
inList 看似简单,但传入空列表、null、或超长列表时行为不同,容易引发隐性问题。
性能影响:PostgreSQL 对 IN 子句长度有限制(默认 10000 项),MySQL 更早触发准备语句缓存压力;Exposed 不做自动分批,得自己切片。
-
Users.select { Users.id inList listOf(1, 2, 3) }✅ 安全且高效 -
Users.select { Users.id inList emptyList() }→ 生成WHERE false,逻辑正确但需确认业务是否接受 -
Users.select { Users.id inList userInputIds }→ 必须先校验userInputIds.size ,否则可能拖慢 DB 或触发超时
协程环境下超时与取消不影响参数化安全性
设置 query.timeout = 10 或用 withTimeout 取消查询,只是控制执行生命周期,不改变参数绑定机制。即使查询被 cancel,之前传入的参数仍全程受 PreparedStatement 保护。
容易被忽略的地方:很多人以为“加了超时就更安全”,其实超时解决的是可用性问题,和注入防护正交。真正该盯住的,是 DSL 中任何带字符串插值、raw、custom 关键字的位置——那里才是防线缺口。











