不能直接拼接 ORDER BY 字段,因用户输入的排序字段名未经校验会导致 SQL 注入,且 SQL 标准禁止在 ORDER BY 中使用参数化占位符;安全做法是白名单校验+显式映射,仅接受预定义字段名和 asc/desc 方向,校验通过后才用可信值拼接。

为什么不能直接拼接 ORDER BY 字段?
因为用户输入的排序字段名(比如 user_input := r.URL.Query().Get("sort"))若未经校验就拼进 SQL,会直接导致 SQL 注入。哪怕用 database/sql 的参数占位符(? 或 $1)也无效——SQL 标准不允许在 ORDER BY 子句中使用参数化值来替代列名或关键字。
安全做法:白名单校验 + 显式映射
核心是拒绝一切“自由输入”,只接受预定义的合法字段名和方向。不要试图用正则过滤、黑名单或转义,那都不可靠。
- 定义允许的字段名和对应数据库列名的映射,例如:
var validSortFields = map[string]string{ "name": "user_name", "email": "email_address", "age": "age", } - 同时限制排序方向,只接受
"asc"或"desc",不接受"ASCENDING"、"DESC "(注意空格)等变体 - 校验逻辑必须在拼接前完成,且失败时返回明确错误(如 HTTP 400),而不是静默 fallback 到默认字段
如何拼接出安全的 ORDER BY 子句?
校验通过后,用字符串拼接是安全的——因为所有参与拼接的值都来自你控制的常量或白名单映射结果,不是原始用户输入。
- 先查白名单:
dbCol, ok := validSortFields[userSort];若!ok,直接拒掉请求 - 再校验方向:
if dir != "asc" && dir != "desc" { /* error */ } - 最终拼接:
orderByClause := fmt.Sprintf("ORDER BY %s %s", dbCol, dir)——这里dbCol和dir都已确定为可信值 - 注意:不要用
fmt.Sprintf("ORDER BY %s", userSort),哪怕你刚校验过,也要用映射后的列名,防止白名单键名和实际列名不一致引发语义错误
容易被忽略的边界点
很多团队加了白名单却还是出问题,往往栽在这些细节上:
- 白名单映射里漏掉了大小写变体,比如用户传
"Name",但白名单只收"name",结果校验失败又没报错,fallback 成了无序或默认字段 - 排序方向校验没 trim 空格,
"desc "过不了== "desc",但可能被某些代码误判为合法 - 把白名单定义在函数内或硬编码在 handler 里,导致多个地方维护多份,后续新增字段时只改了一处,埋下不一致隐患
- 忘记对多字段排序(如
sort=name,email&order=asc,desc)做额外校验——这种场景需拆分、逐个查白名单,并限制最大字段数(防 DoS)

















