Nuxt4 安全防护体系

安全不是功能上线后的补丁,而是架构设计的第一性原则。Nuxt4 作为全栈框架,攻击面天然比纯前端更大——浏览器端有 XSS、CSRF、点击劫持,服务端有注入攻击、SSRF、信息泄露。好消息是,Nuxt4 的架构(Vue 模板自动转义、Nitro 请求隔离、server/ 目录物理隔离)天然规避了相当一部分风险。本章系统梳理 Nuxt4 全栈应用的安全威胁模型,讲清每种攻击的底层原理防御思路,帮你建立一套可落地的安全防护体系。

1. 威胁模型:理解你的攻击面

安全防护的前提是知道敌人从哪里进攻。Nuxt4 应用的攻击面可以按「层」来划分:

层级攻击类型核心风险Nuxt4 天然防护
浏览器层XSS、点击劫持、Open Redirect窃取用户凭证、劫持会话Vue 模板自动转义
网络层中间人攻击、Cookie 窃听数据被窃取或篡改需手动配置 HTTPS/HSTS
请求层CSRF、请求伪造以用户身份执行恶意操作需 nuxt-security 模块
服务端层SQL 注入、SSRF、命令注入数据库泄露、内网渗透Nitro 请求隔离
数据层信息泄露、Payload 暴露敏感数据被前端看到server/ 物理隔离

一个关键认知:安全是分层的,没有银弹。每一层的防护都可能被绕过,真正安全的系统靠的是纵深防御(Defense in Depth)——多层防护叠加,任何一层被突破后还有下一层兜底。

2. XSS 防御:从原理到纵深

2.1 XSS 的三种形态

XSS(Cross-Site Scripting)的本质是攻击者的代码在受害者的浏览器中执行。根据注入方式不同分为三类:

  • 反射型 XSS:恶意脚本通过 URL 参数传入,服务端原样返回到页面中。用户点击一个精心构造的链接就中招。搜索框回显搜索词是经典场景。
  • 存储型 XSS:恶意脚本被保存到数据库(比如评论、文章内容),所有浏览该内容的用户都受影响。危害最大,因为影响范围广。
  • DOM 型 XSS:恶意脚本不经过服务端,完全在客户端通过 JavaScript 操作 DOM 注入。比如 document.innerHTML = location.hash

2.2 Vue 的第一道防线

Vue 的模板系统默认对 {{ }} 插值和 :attr 绑定做 HTML 实体转义。这意味着 <script>alert(1)</script> 会被渲染为纯文本 <script>alert(1)</script>,而不是执行。

这层防护覆盖了绝大多数场景,但有三个"逃逸口"需要特别警惕:

逃逸口一:v-html。它直接将字符串作为 HTML 插入 DOM,完全绕过转义。如果内容来源于用户输入(文章、评论、用户昵称中的富文本),必须在渲染前通过 isomorphic-dompurify(同时支持 SSR 和 CSR)做白名单过滤,只保留 <b><i><a> 等安全标签。

逃逸口二:URL 绑定<a :href="userInput"> 如果 userInputjavascript:alert(1),点击链接就会执行脚本。防护方式是校验 URL 的 protocol 只允许 http:https:

逃逸口三:动态组件<component :is="userInput"> 如果 userInput 可控,攻击者可能注入意外组件。必须做白名单校验。

2.3 CSP:第二道防线

即使 XSS 代码成功注入了页面,Content Security Policy(CSP) 可以阻止它执行。CSP 通过 HTTP 响应头告诉浏览器"只允许执行来自指定来源的脚本"。

CSP 的核心指令:

指令控制对象安全策略
script-srcJavaScript'self' + nonce(最安全)或 'self'(次之)
style-srcCSS'self' 'unsafe-inline'(Vue SFC 需要内联样式)
img-src图片'self' data: https://your-cdn.com
connect-srcXHR/Fetch/WebSocket'self' https://api.your-domain.com
frame-ancestors谁能嵌入你'none'(防点击劫持,替代 X-Frame-Options)

Nonce 模式是 CSP 与 Nuxt SSR 兼容的关键。nuxt-security 模块会自动为每次 SSR 响应生成一个随机 nonce 值,注入到内联 <script> 标签和 CSP 头中。攻击者注入的脚本没有这个 nonce,就无法执行。

CSP 落地的难点不在于配置本身,而在于第三方脚本。Google Analytics、Sentry、在线客服等第三方脚本的域名都需要加到白名单里,否则会被 CSP 拦截。建议先用 Content-Security-Policy-Report-Only 模式上线观察,收集违规报告,确认无误后再切换到强制模式。

3. CSRF 防护:理解浏览器的"好心之恶"

3.1 攻击原理

CSRF(Cross-Site Request Forgery)利用了浏览器的一个"好心"行为:跨站请求时自动携带目标站点的 Cookie

攻击链条:用户登录了 bank.com,浏览器存了认证 Cookie → 用户访问恶意网站 evil.comevil.com 的页面自动向 bank.com/api/transfer 发起 POST 请求 → 浏览器自动携带 bank.com 的 Cookie → 请求以用户身份成功执行。

关键前提:攻击者看不到响应内容(同源策略限制),但写操作已经生效。所以 CSRF 主要威胁 POST/PUT/DELETE 等变更操作。

3.2 三层防护策略

第一层:SameSite Cookie(最重要)

SameSite 属性告诉浏览器"跨站请求时不要携带这个 Cookie":

  • SameSite=Strict:任何跨站请求都不携带。最安全但体验差——用户从搜索引擎点链接进来也不带 Cookie,等于登录状态丢失。
  • SameSite=Lax(推荐):导航性请求(GET 链接跳转)携带,但 POST/PUT/DELETE 等跨站请求不携带。兼顾安全和体验。
  • SameSite=None:始终携带,必须配合 Secure 属性。仅在需要跨站 Cookie 时使用(如嵌入 iframe 的场景)。

第二层:CSRF Token

nuxt-security 的 CSRF 模块会在每次请求中注入一个随机 Token(通过 Cookie 传给客户端,请求时放在 Header 中回传)。服务端验证 Header 中的 Token 和 Cookie 中的是否匹配。攻击者的跨站请求无法读取你站点的 Cookie 来获取 Token 值,因此验证失败。

第三层:Origin / Referer 检查

验证请求的 OriginReferer 头是否来自允许的域名。这是一层额外保险——即使前两层都没配好,Origin 检查也能拦住大部分 CSRF。

3.3 一个重要推论

如果你的认证方案用的是 Authorization: Bearer <token>(Token 存在 localStorage / 内存中,而非 Cookie),CSRF 攻击天然无效——因为跨站请求无法自动携带 Authorization Header。但这引入了另一个风险:Bearer Token 存在 localStorage 中,一旦被 XSS 窃取就 game over。

安全选型的本质是权衡

方案CSRF 风险XSS 窃取风险推荐场景
HttpOnly Cookie需要 CSRF 防护Token 安全(JS 不可读)Web 应用(推荐)
localStorage + Bearer天然免疫 CSRFToken 可被 XSS 窃取原生 App / API 客户端

大多数 Nuxt4 Web 应用应该选择 HttpOnly Cookie + SameSite=Lax + CSRF Token 的组合。

4. 传输安全:HTTPS 不只是加个锁

4.1 HTTPS 解决什么问题

HTTP 是明文传输,在任何中间节点(路由器、WiFi 热点、ISP)都可以被窃听篡改。HTTPS 通过 TLS 加密解决三个问题:

  • 机密性:第三方看不到通信内容
  • 完整性:通信内容不能被篡改
  • 真实性:确认你访问的确实是目标服务器(证书验证)

4.2 HSTS:防止降级攻击

仅配置 HTTPS 还不够。攻击者可以在用户首次访问时(输入 http://example.com)劫持请求,伪造一个 HTTP 页面(SSL Stripping 攻击)。

HSTS(HTTP Strict Transport Security) 通过响应头 Strict-Transport-Security: max-age=31536000; includeSubDomains; preload 告诉浏览器:"未来一年内,所有对这个域名的请求都必须走 HTTPS,不要尝试 HTTP"。

preload 指令更进一步——可以将域名提交到浏览器的 HSTS 预加载列表(内置在 Chrome、Firefox 等浏览器中),这样首次访问也强制 HTTPS,彻底消灭降级攻击。

Cookie 也需要配合 HTTPS:

  • Secure 属性:Cookie 只在 HTTPS 连接中传输,HTTP 下不发送
  • HttpOnly 属性:JavaScript 不能读取这个 Cookie,防止 XSS 窃取
  • SameSite 属性:防止 CSRF(上文已讲)
  • Path 属性:限制 Cookie 只在特定路径下发送(比如 Refresh Token 只在 /api/auth/refresh 路径发送)

这四个属性应该同时设置,缺一不可。

5. 安全响应头:一行配置的纵深防御

现代浏览器提供了一系列安全响应头,每个头解决一类特定问题:

响应头防御目标原理
Content-Security-PolicyXSS限制资源来源,阻止未授权脚本执行
Strict-Transport-Security降级攻击强制 HTTPS
X-Content-Type-Options: nosniffMIME 嗅探攻击阻止浏览器猜测文件类型(把 JSON 当 JS 执行)
X-Frame-Options: DENY点击劫持禁止页面被嵌入 iframe
Referrer-Policy信息泄露控制跳转时发送的 Referer 信息量
Permissions-Policy隐私侵犯禁止页面使用摄像头、麦克风、地理位置等 API
X-DNS-Prefetch-Control: offDNS 泄露禁止浏览器预解析页面中链接的 DNS

nuxt-security 模块的价值在于:它默认开启了上述所有安全头,并提供了合理的默认值。安装即防护,而不是靠开发者记住每个头的细节。

6. Nuxt4 的数据安全边界

6.1 server/ 是唯一的安全边界

这是 Nuxt4 安全中最重要的概念,没有之一。

Nuxt4 全栈项目中,app/pages/components/ 下的所有代码都会被打包发送到浏览器——用户可以通过 DevTools 看到完整源码。而 server/ 目录下的代码只在 Node.js 进程中执行,永远不会到达浏览器。

这条边界决定了一切敏感逻辑的归属:

内容应该在哪原因
API Key、数据库密码server/ + runtimeConfig 私有区泄露即被盗用
业务逻辑(价格计算、权限判断)server/api/防止客户端篡改
数据过滤(返回哪些字段)server/api/防止 Payload 泄露内部数据
第三方 API 调用server/api/隐藏 API Key
表单校验server/ + 客户端双重客户端校验可被绕过

6.2 runtimeConfig 的安全分区

runtimeConfig 顶层是私有的(只在 server/ 可访问),runtimeConfig.public公开的(会被序列化到客户端 JS bundle)。

一个常见错误是把 API Key 放在 public 里——这等于把钥匙插在门上。黄金法则:如果这个值泄露会造成损失,就不要放在 public

6.3 SSR Payload 泄露

Nuxt SSR 会将 useFetch / useAsyncData 的返回数据序列化到 HTML 中(__NUXT_DATA__),供客户端 hydration 使用。如果 API 返回了 passwordHashinternalScoreadminNotes 等字段,这些都会暴露在 HTML 源码中。

防护原则:API 层只返回前端需要的字段。在 server/api/ 中做字段过滤,而不是在前端忽略多余字段。前端"不显示"不等于"安全"——数据已经到了浏览器,右键查看源码就能看到。

7. 注入攻击防护

7.1 SQL 注入

SQL 注入的本质是用户输入被当作 SQL 代码执行。防护只有一条规则:永远使用参数化查询,永远不要拼接 SQL 字符串

使用 Drizzle ORM 或 Prisma 时,ORM 内部自动使用参数化查询,开发者不需要关心转义。但如果你使用原始 SQL(db.execute()),就必须使用占位符语法。

7.2 输入校验

所有来自客户端的数据都不可信——URL 参数、请求体、Header、Cookie,全部需要在服务端校验。Nuxt4 / Nitro 提供了 readValidatedBodygetValidatedQuery,配合 Zod schema 做声明式校验,拒绝不合规的输入进入业务逻辑。

一个被忽视的点:客户端校验是体验优化,不是安全措施。攻击者可以直接用 curl 绕过前端校验直接调用 API。真正的安全校验必须在 server/ 层。

7.3 SSRF 防护

SSRF(Server-Side Request Forgery)是利用服务端代码发起内部请求。比如你有一个"获取 URL 预览"的 API,用户传入 http://169.254.169.254/latest/meta-data/(AWS 元数据接口),服务端就会泄露云服务器的 IAM 凭证。

防护要点:

  • 对用户提供的 URL 做白名单校验(只允许特定域名)
  • 禁止访问内网 IP 段(10.x172.16-31.x192.168.x169.254.x
  • 使用 DNS 解析后再验证 IP(防止 DNS Rebinding)

8. nuxt-security 模块实战

nuxt-security 是 Nuxt 生态中最全面的安全模块,一次安装覆盖多种防护:

功能默认行为
安全响应头自动设置 CSP、HSTS、X-Frame-Options 等
请求限流Token Bucket 算法,可按路由配置
请求体大小限制默认 2MB,防止大文件攻击
CORS 控制配置允许的 Origin
CSRF Token自动生成和验证
CSP Nonce每次 SSR 响应自动生成 nonce

路由级别覆盖nuxt-security 的精髓。全局设置一套基线安全策略,然后通过 routeRules 对特定路由做调整。比如:全局限流 100 次/分钟,但登录接口限流 5 次/5 分钟(防暴力破解);全局启用 CSRF Token,但 Webhook 接收接口关闭(第三方回调不会带你的 CSRF Token)。

9. 安全审计 Checklist

基础设施

  • HTTPS 全站启用,HTTP 301 重定向到 HTTPS
  • HSTS 头设置 max-age >= 31536000
  • .env 文件在 .gitignore 中,不提交到代码仓库
  • 生产环境的 Secret 通过部署平台环境变量管理

客户端安全

  • 禁止 v-html 直接渲染用户输入,必须经过 DOMPurify
  • URL 绑定(:href)校验 protocol 白名单
  • 不使用 eval()new Function()document.write()
  • CSP 配置为 script-src: 'self' + nonce

认证与会话

  • Cookie 同时设置 HttpOnly + Secure + SameSite=Lax
  • Refresh Token 的 Cookie Path 限定为续期接口路径
  • 登录接口启用限流(防暴力破解)

服务端安全

  • 所有用户输入在 server/ 层用 Zod 校验
  • 使用 ORM 参数化查询,不拼接 SQL 字符串
  • API 只返回必要字段,不泄露内部数据到 Payload
  • API Key 放在 runtimeConfig 顶层(私有),不在 public

安全模块

  • nuxt-security 已安装并配置
  • CSP、X-Frame-Options、X-Content-Type-Options 等安全头已验证
  • 全局限流 + 敏感接口加强限流

本章小结

  • 纵深防御是安全的核心思想——不依赖单一防护,多层叠加才可靠
  • XSS:Vue 模板自动转义 → DOMPurify 过滤 v-html → CSP nonce 兜底,三层防线
  • CSRFSameSite=Lax Cookie 拦截跨站写操作 → CSRF Token 二次验证 → Origin 检查兜底
  • 传输安全:HTTPS + HSTS + Cookie 四属性(HttpOnly/Secure/SameSite/Path)
  • 数据边界server/ 是唯一的安全边界,敏感数据不过 runtimeConfig.public,API 层做字段过滤
  • 注入防护:ORM 参数化查询 + Zod 输入校验 + SSRF IP 白名单
  • nuxt-security:一个模块覆盖安全头 + CSRF + 限流 + CORS + CSP nonce