Nuxt4 安全防护体系

Web 安全不是可选项,而是底线。Nuxt4 作为全栈框架,攻击面比纯前端更大——既有浏览器端的 XSS、CSRF,也有服务端的注入、信息泄露。好消息是 Nuxt4 的架构天然规避了许多安全问题(Vue 的模板自动转义、Nitro 的请求隔离),再配合 nuxt-security 模块和正确的编码习惯,可以构建一个坚固的安全防护体系。

1. Web 安全威胁全景

1.1 Nuxt4 应用的攻击面

攻击类型攻击层目标危害等级
XSS客户端注入恶意脚本🔴 高
CSRF客户端→服务端伪造用户请求🔴 高
注入攻击服务端SQL/NoSQL/命令注入🔴 高
信息泄露全栈敏感数据暴露🟡 中
SSRF服务端利用服务端发起内部请求🟡 中
点击劫持客户端iframe 嵌套钓鱼🟡 中
中间人攻击网络层窃听/篡改通信🔴 高

1.2 nuxt-security 模块

nuxt-security 是 Nuxt 生态中最全面的安全模块,一行配置开启多种防护:

// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['nuxt-security'],
  security: {
    headers: {
      contentSecurityPolicy: {
        'script-src': ["'self'", "'unsafe-inline'"],
      },
      crossOriginEmbedderPolicy: 'require-corp',
      xContentTypeOptions: 'nosniff',
      xFrameOptions: 'DENY',
      xXSSProtection: '1; mode=block',
    },
    requestSizeLimiter: { maxRequestSizeInBytes: 2000000 },  // 2MB
    rateLimiter: { tokensPerInterval: 150, interval: 300000 },
    corsHandler: { origin: ['https://example.com'] },
  },
})

它默认开启了安全响应头、请求限流、CORS 控制等,显著提升安全基线。

2. XSS 防御

2.1 Vue 的天然防护

Vue 的模板系统默认对所有插值表达式做 HTML 转义

<template>
  <!-- ✅ 安全:自动转义,<script> 变为 &lt;script&gt; -->
  <p>{{ userInput }}</p>
 
  <!-- ✅ 安全:属性绑定也会转义 -->
  <div :title="userInput"></div>
</template>

即使 userInput 包含 &lt;script&gt;alert('XSS')&lt;/script&gt;,Vue 会将其转义为纯文本显示,不会执行。

2.2 v-html 的风险

v-html 直接插入原始 HTML,绕过了 Vue 的转义机制——这是 Nuxt 应用中 XSS 的头号风险:

<!-- ❌ 极度危险:如果 content 来自用户输入 -->
<div v-html="content"></div>

使用 v-html 的安全规则

  1. 永远不要直接渲染用户输入
  2. 如果必须渲染富文本,用 DOMPurify 过滤
// app/composables/useSanitize.ts
import DOMPurify from 'isomorphic-dompurify'
 
export function useSanitize() {
  function sanitize(dirty: string): string {
    return DOMPurify.sanitize(dirty, {
      ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p', 'br', 'ul', 'li'],
      ALLOWED_ATTR: ['href', 'target', 'rel'],
    })
  }
  return { sanitize }
}
<template>
  <!-- ✅ 过滤后的 HTML -->
  <div v-html="sanitize(article.content)"></div>
</template>
 
<script setup>
const { sanitize } = useSanitize()
</script>

isomorphic-dompurify 同时支持 SSR 和 CSR,解决了原版 DOMPurify 依赖 window 的问题。

2.3 其他 XSS 风险点

风险点场景防护
URL 注入&lt;a :href="userUrl"&gt;验证 URL scheme(只允许 http/https)
动态组件&lt;component :is="userInput"&gt;白名单校验组件名
内联样式:style="userStyle"不接受用户输入的 CSS
eval / Function动态执行代码永远不用
SSR HTML 注入服务端拼接 HTML使用 Vue 模板或 h() 函数

2.4 URL 校验

function isSafeUrl(url: string): boolean {
  try {
    const parsed = new URL(url)
    return ['http:', 'https:'].includes(parsed.protocol)
  } catch {
    return false
  }
}
<a :href="isSafeUrl(link) ? link : '#'">{{ linkText }}</a>

3. CSRF 防护

3.1 CSRF 攻击原理

跨站请求伪造(CSRF)利用浏览器自动携带 Cookie 的机制:

1. 用户登录 example.com(浏览器存储了认证 Cookie)
2. 用户访问恶意网站 evil.com
3. evil.com 的页面自动发送请求到 example.com/api/transfer
4. 浏览器自动携带 example.com 的 Cookie → 请求以用户身份执行

3.2 防护策略

策略一:SameSite Cookie

// server/api/auth/login.post.ts
setCookie(event, 'auth-token', token, {
  httpOnly: true,    // JS 不可读取
  secure: true,      // 仅 HTTPS
  sameSite: 'lax',   // 同站请求才携带 Cookie
  maxAge: 60 * 60 * 24 * 7,  // 7 天
  path: '/',
})

SameSite: Lax 是最佳平衡——允许导航链接携带 Cookie(用户从搜索引擎跳转过来仍保持登录),但阻止跨站 POST/PUT/DELETE 请求携带 Cookie。

策略二:CSRF Token

nuxt-security 提供了内置的 CSRF Token 机制:

// nuxt.config.ts
export default defineNuxtConfig({
  security: {
    csrf: {
      enabled: true,
      cookie: { name: '__csrf' },
    },
  },
})

请求时自动在 Header 中携带 CSRF Token,服务端验证 Token 匹配性。

策略三:检查 Origin / Referer

// server/middleware/csrf-check.ts
export default defineEventHandler((event) => {
  if (['POST', 'PUT', 'DELETE', 'PATCH'].includes(event.method)) {
    const origin = getRequestHeader(event, 'origin')
    const allowedOrigins = ['https://example.com']
 
    if (origin && !allowedOrigins.includes(origin)) {
      throw createError({ statusCode: 403, message: 'CSRF check failed' })
    }
  }
})

3.3 API Token 认证不受 CSRF 影响

如果你使用 Authorization: Bearer &lt;token&gt; 而非 Cookie 认证,CSRF 攻击天然无效——恶意网站无法获取和设置 Authorization Header。但 Bearer Token 需要存储在客户端(通常 localStorage),面临 XSS 窃取风险。

安全权衡

方案CSRF 风险XSS 风险推荐场景
HttpOnly Cookie需要 CSRF 防护Token 安全Web 应用(推荐)
localStorage + Bearer天然免疫Token 可能被 XSS 窃取移动端/API 客户端
Cookie + Bearer 双重最安全复杂度高高安全要求

4. CSP 内容安全策略

4.1 CSP 是什么

Content Security Policy(CSP)通过 HTTP 响应头告诉浏览器只允许加载指定来源的资源。即使攻击者成功注入了恶意脚本,CSP 也能阻止脚本执行。

4.2 CSP 指令详解

指令作用推荐值
script-src允许的脚本来源'self' + nonce
style-src允许的样式来源'self' 'unsafe-inline'
img-src允许的图片来源'self' data: https:
connect-src允许的 XHR/Fetch 目标'self' https://api.example.com
frame-ancestors允许嵌入的父页面'none'(防止点击劫持)
base-uri允许的 &lt;base&gt; URL'self'
form-action允许的表单提交目标'self'

4.3 Nuxt4 CSP 配置

// nuxt.config.ts
export default defineNuxtConfig({
  security: {
    headers: {
      contentSecurityPolicy: {
        'default-src': ["'self'"],
        'script-src': ["'self'", "'nonce-{{nonce}}'"],  // nonce 模式
        'style-src': ["'self'", "'unsafe-inline'"],      // Vue SFC 需要
        'img-src': ["'self'", 'data:', 'https://images.example.com'],
        'connect-src': ["'self'", 'https://api.example.com'],
        'font-src': ["'self'", 'https://fonts.gstatic.com'],
        'frame-ancestors': ["'none'"],
        'base-uri': ["'self'"],
        'form-action': ["'self'"],
      },
    },
    nonce: true,  // 自动为内联脚本添加 nonce
  },
})

4.4 CSP 与 Nuxt 的兼容挑战

问题原因解决
内联脚本被阻止script-src 不允许 unsafe-inline使用 nonce 模式(nuxt-security 自动处理)
内联样式被阻止Vue SFC 的 &lt;style&gt; 会生成内联样式style-src 允许 unsafe-inline
第三方脚本被阻止GA、Sentry 等外部脚本添加对应域名到 script-src
SSR Payload 被阻止Nuxt 的 &lt;script&gt; payloadnonce 自动覆盖

5. HTTPS 与传输安全

5.1 强制 HTTPS

// server/middleware/https-redirect.ts
export default defineEventHandler((event) => {
  if (import.meta.prod) {
    const proto = getRequestHeader(event, 'x-forwarded-proto')
    if (proto === 'http') {
      const host = getRequestHeader(event, 'host')
      return sendRedirect(event, `https://${host}${event.path}`, 301)
    }
  }
})

5.2 HSTS 头

// nuxt.config.ts
export default defineNuxtConfig({
  security: {
    headers: {
      strictTransportSecurity: {
        maxAge: 31536000,        // 1 年
        includeSubdomains: true,
        preload: true,
      },
    },
  },
})

HSTS 告诉浏览器在未来一段时间内只使用 HTTPS 访问该域名,即使用户输入 http://preload 可以提交到浏览器的 HSTS 预加载列表,首次访问也强制 HTTPS。

5.3 安全响应头汇总

响应头作用推荐值
Strict-Transport-Security强制 HTTPSmax-age=31536000; includeSubDomains
X-Content-Type-Options禁止 MIME 类型嗅探nosniff
X-Frame-Options防止点击劫持DENY
X-XSS-Protection浏览器 XSS 过滤1; mode=block
Referrer-Policy控制 Referer 发送strict-origin-when-cross-origin
Permissions-Policy控制浏览器 API 权限按需配置

6. 敏感数据处理边界

6.1 Nuxt4 的数据边界

Nuxt4 全栈架构中最关键的安全概念:server/ 目录是安全边界

┌─────────────────────────────────┐
│  客户端(不安全)                │
│  app/, pages/, components/      │
│  ⚠️ 代码和数据对用户完全可见     │
├─────────────────────────────────┤
│  ━━━━━━ 安全边界 ━━━━━━         │
├─────────────────────────────────┤
│  服务端(安全)                  │
│  server/api/, server/utils/     │
│  ✅ 代码和数据用户不可见         │
└─────────────────────────────────┘

6.2 runtimeConfig 的安全分区

// nuxt.config.ts
export default defineNuxtConfig({
  runtimeConfig: {
    // ✅ 私有:只在 server/ 中可访问
    openaiApiKey: process.env.OPENAI_API_KEY,
    dbPassword: process.env.DB_PASSWORD,
    jwtSecret: process.env.JWT_SECRET,
 
    public: {
      // ⚠️ 公开:会暴露到客户端 JS bundle
      apiBase: process.env.API_BASE,
      appName: 'AI Video Platform',
    },
  },
})

黄金法则:API Key、数据库密码、JWT Secret 等永远放在 runtimeConfig 顶层(私有),不放在 public 中。

6.3 Payload 数据泄露

Nuxt SSR 的 Payload 会序列化到 HTML 中,客户端可以看到:

// ❌ 危险:useFetch 的数据会进入 Payload
const { data: user } = await useFetch('/api/user/me')
// user.value 包含 { id, name, email, passwordHash, internalNotes }
// passwordHash 和 internalNotes 会暴露到客户端!
// ✅ 安全:API 层只返回必要字段
// server/api/user/me.get.ts
export default defineEventHandler(async (event) => {
  const user = await getUserFromToken(event)
  return {
    id: user.id,
    name: user.name,
    email: user.email,
    avatar: user.avatar,
    // 不返回 passwordHash, internalNotes 等敏感字段
  }
})

6.4 环境变量安全

# .env — 不提交到 Git
OPENAI_API_KEY=sk-xxx
DB_PASSWORD=xxx
JWT_SECRET=xxx
 
# .env.example — 提交到 Git(不含真实值)
OPENAI_API_KEY=
DB_PASSWORD=
JWT_SECRET=

确保 .env.gitignore 中,生产环境通过部署平台的环境变量管理(Vercel Environment Variables、Docker secrets 等)。

7. 注入攻击防护

7.1 SQL 注入

使用 ORM(Drizzle、Prisma)而非拼接 SQL 字符串:

// ❌ SQL 注入风险
const query = `SELECT * FROM videos WHERE title LIKE '%${userInput}%'`
 
// ✅ 参数化查询(Drizzle)
const videos = await db.select().from(videosTable)
  .where(like(videosTable.title, `%${userInput}%`))

7.2 输入校验

所有用户输入都应该在服务端校验:

// server/api/videos/index.post.ts
import { z } from 'zod'
 
const createVideoSchema = z.object({
  title: z.string().min(1).max(200),
  description: z.string().max(5000).optional(),
  tags: z.array(z.string().max(50)).max(10),
})
 
export default defineEventHandler(async (event) => {
  const body = await readValidatedBody(event, createVideoSchema.parse)
  // body 已经过校验和类型安全
  return createVideo(body)
})

readValidatedBody 是 Nitro 提供的校验辅助函数,配合 Zod 实现声明式的输入校验。

7.3 限流防护

防止暴力破解和 DDoS:

// nuxt.config.ts
export default defineNuxtConfig({
  security: {
    rateLimiter: {
      tokensPerInterval: 100,   // 每个时间窗口 100 次请求
      interval: 60000,          // 60 秒窗口
    },
  },
  routeRules: {
    '/api/auth/login': {
      security: {
        rateLimiter: {
          tokensPerInterval: 5,   // 登录接口更严格
          interval: 300000,       // 5 分钟 5 次
        },
      },
    },
  },
})

8. 安全 Checklist

8.1 基础防护

  • 安装并配置 nuxt-security 模块
  • 启用 CSP(至少 script-src: 'self' + nonce)
  • 设置 X-Frame-Options: DENY
  • 设置 X-Content-Type-Options: nosniff
  • 启用 HSTS(Strict-Transport-Security

8.2 数据安全

  • API Key/Secret 放在 runtimeConfig 私有区域
  • .env 文件在 .gitignore
  • API 只返回必要字段,不泄露内部数据
  • 所有用户输入在服务端校验(Zod)

8.3 认证安全

  • Cookie 设置 HttpOnly + Secure + SameSite
  • 密码使用 bcrypt/argon2 哈希(不可逆)
  • 登录接口限流(防暴力破解)
  • JWT Secret 足够长(至少 256 位)

8.4 代码安全

  • 禁止 v-html 直接渲染用户输入(必须 DOMPurify)
  • 使用 ORM 而非拼接 SQL
  • URL 绑定校验 scheme
  • 不使用 eval()new Function()

本章小结

  • XSS:Vue 模板自动转义是第一道防线,v-html 必须配合 DOMPurify,URL 绑定校验 scheme
  • CSRFSameSite: Lax Cookie + CSRF Token 双重防护,Bearer Token 天然免疫
  • CSP:限制资源加载来源,nonce 模式兼容 Nuxt 内联脚本
  • HTTPS:HSTS + 301 重定向 + Secure Cookie,全链路加密
  • 数据边界server/ 是安全边界,敏感数据不进 runtimeConfig.public,API 只返回必要字段
  • 注入防护:ORM 参数化查询 + Zod 输入校验 + 接口限流
  • nuxt-security:一个模块覆盖安全头、CSRF、限流、CORS