⚠️ Alpha内测版本警告:此为早期内部构建版本,尚不完整且可能存在错误,欢迎大家提Issue反馈问题或建议
Skip to content

6.4.5 输入验证:防止注入攻击

一句话破题

注入攻击的本质是:攻击者的输入被当作代码执行了。防御的关键是:把数据和代码严格分开。

核心价值

掌握输入验证能让你:

  • 防止 SQL 注入导致数据泄露
  • 防止命令注入导致服务器被控
  • 通过安全审计

快速上手

使用 Zod 验证输入

typescript
import { z } from 'zod'

const UserSchema = z.object({
  email: z.string().email(),
  name: z.string().min(1).max(100),
  age: z.number().int().min(0).max(150),
})

export async function POST(request: Request) {
  const body = await request.json()
  
  const result = UserSchema.safeParse(body)
  if (!result.success) {
    return Response.json({ error: result.error.issues }, { status: 400 })
  }
  
  // 使用 result.data,类型安全
}

使用 Prisma 防止 SQL 注入

typescript
// ✅ 安全:Prisma 自动参数化
const user = await prisma.user.findUnique({
  where: { email: userInput }
})

// ✅ 安全:使用 $queryRaw 的模板字符串
const users = await prisma.$queryRaw`
  SELECT * FROM users WHERE name = ${userInput}
`

// ❌ 危险:使用 $queryRawUnsafe
const users = await prisma.$queryRawUnsafe(
  `SELECT * FROM users WHERE name = '${userInput}'`
)

常见验证规则

typescript
const schemas = {
  // ID 格式
  id: z.string().uuid(),
  
  // 只允许字母数字
  slug: z.string().regex(/^[a-zA-Z0-9-]+$/),
  
  // 手机号
  phone: z.string().regex(/^1[3-9]\d{9}$/),
  
  // 限制长度
  bio: z.string().max(500),
  
  // 枚举值
  role: z.enum(['user', 'admin', 'editor']),
  
  // 数字范围
  page: z.coerce.number().int().min(1).default(1),
}

文件上传验证

typescript
const allowedTypes = ['image/jpeg', 'image/png', 'image/webp']
const maxSize = 5 * 1024 * 1024 // 5MB

export async function POST(request: Request) {
  const formData = await request.formData()
  const file = formData.get('file') as File
  
  if (!allowedTypes.includes(file.type)) {
    return Response.json({ error: '不支持的文件类型' }, { status: 400 })
  }
  
  if (file.size > maxSize) {
    return Response.json({ error: '文件太大' }, { status: 400 })
  }
  
  // 处理上传
}

避坑指南

新手最容易犯的错

  1. 只在前端验证,后端不验证——前端验证可以被绕过
  2. 使用正则匹配"危险字符"——容易遗漏,应该用白名单
  3. 拼接 SQL 语句——永远使用参数化查询
  4. 信任文件扩展名——应该验证 MIME 类型