Nuxt.js SSR中防SQL注入的关键与纯Node.js一致:必须在数据库查询层使用参数化或ORM,useFetch等API不提供防护;所有用户输入(含params、query、headers)须经占位符绑定或白名单校验,框架本身无SQL安全中间件。

在 Nuxt.js 服务端渲染(SSR)中,数据库查询防 SQL 注入的关键和纯 Node.js 后端完全一致:所有用户输入进 SQL 前必须走参数化或 ORM 模型层,useFetch 或 defineEventHandler 本身不提供任何防护能力。
为什么 Nuxt 的 SSR 上下文不改变 SQL 安全逻辑
Nuxt 的 useServerSeoMeta、useAsyncData、defineEventHandler 这些 API 只是封装了请求生命周期和数据获取方式,底层数据库操作仍由你写的 Node.js 代码执行。一旦你在 server/api/users.get.ts 里写 db.query(`SELECT * FROM users WHERE id = ${id}`),攻击者直接调用该 API 就能注入——和是否 SSR 无关。
常见误解包括:
- 以为
useAsyncData自动校验参数 → 实际它只做缓存和响应格式处理,不碰 SQL 构造 - 以为 Nuxt 3 的
event.context会过滤用户输入 → 它只是透传req对象,event.req.query.id和 Express 的req.query.id完全等价 - 以为服务端
fetch()调用后端 API 就“多一层保护” → 如果后端 API 本身拼接 SQL,这层调用毫无意义
Sequelize 在 Nuxt server routes 中的正确用法
在 server/api/posts.get.ts 里使用 Sequelize,必须避开 literal() 和手动拼接,且所有动态结构需白名单控制:
使用约定式提交(Conventional Commits)生成标准化的 Git 提交信息。适用于以下场景: (1) 暂存更改时需要提交信息; (2) 审查 diff 时希望获得结构化摘要; (3) 编写 PR 描述或变更日志(changelog)条目; (4) 用户明确要求“为本次更改编写提交信息”。 该功能会分析已暂存/未暂存的 diff,并生成按类型(type)划分范围的提交信息。
- ✅ 安全写法:
User.findAll({ where: { email: event.req.query.email } })—— ORM 自动参数化 - ✅ 安全写法:
sequelize.query("SELECT * FROM posts WHERE status = :status", { replacements: { status: event.req.query.status } })—— 命名绑定 - ❌ 危险写法:
sequelize.query(`SELECT * FROM posts WHERE title LIKE '%${event.req.query.q}%'`)—— 字符串拼接 + 无转义 - ❌ 危险写法:
order: [[literal(`${event.req.query.sort} ${event.req.query.dir}`)]]——literal()直接破防
排序字段必须硬编码白名单:
const allowedSorts = { title: 'title', createdAt: 'created_at', id: 'id' }
const sortField = allowedSorts[event.req.query.sort as keyof typeof allowedSorts] || 'id'
const sortOrder = ['ASC', 'DESC'].includes(event.req.query.dir as string) ? event.req.query.dir : 'ASC'
// 再传入 order: [[sortField, sortOrder]]
动态表名、分页参数、请求头值怎么处理
这些常被忽略的输入源,在 SSR 场景下一样危险:
-
event.req.params.slug查文章?→ 必须作为参数传给WHERE slug = ?,不能拼进表名或列名 -
event.req.headers['x-tenant-id']多租户隔离?→ 若用于WHERE tenant_id = ?,直接参数化;若用于切换表(如logs_${tenantId}),必须严格白名单校验后才拼接 -
event.req.query.page和limit分页?→pool.execute('SELECT * FROM items LIMIT ?, ?', [offset, limit]),不能用OFFSET ${page * 10} -
event.req.query.search用于全文检索?→ MySQL 的MATCH AGAINST不支持占位符,需先用正则剥离非字母数字字符,再进replacements绑定
别信“Nuxt 自带安全中间件”这种说法
Nuxt 官方没有提供 SQL 注入防护中间件。所谓“自动校验”只存在于开发者自己写的 server/middleware/auth.ts 之类逻辑里。真正卡住漏洞的,永远是那一行 ? 或 replacements,不是框架版本号或 SSR 模式开关。
最容易被绕过的点是:把 parseInt(event.req.query.id) 当成安全动作。它只防止 NaN 错误,不阻止 id=1%20OR%201=1 这类 payload——只要后续还拼 SQL,就等于没防。

















