Next.js 教程系列(二十八)Next.js 的安全最佳实践

前言

大家好,我是鲫小鱼。是一名不写前端代码的前端工程师,热衷于分享非前端的知识,带领切图仔逃离切图圈子,欢迎关注我,微信公众号:《鲫小鱼不正经》。欢迎点赞、收藏、关注,一键三连!!


第二十八章:Next.js 的安全最佳实践

理论讲解(30%)

1. Web 安全的本质与挑战

在企业级 Web 应用开发中,安全性始终是重中之重。随着 Next.js 全栈能力的增强,前后端一体化带来了更多攻击面。常见的安全威胁包括:

  • XSS(跨站脚本攻击):攻击者注入恶意脚本,窃取用户信息或劫持会话。
  • CSRF(跨站请求伪造):利用用户已登录状态,伪造请求执行敏感操作。
  • SQL/NoSQL 注入:通过构造恶意输入,操控数据库查询,窃取或篡改数据。
  • 敏感信息泄露:环境变量、API 密钥、用户数据等暴露。
  • 依赖漏洞:第三方包存在安全缺陷。
为什么 Next.js 项目更需重视安全?
  • 服务端渲染:SSR 代码运行在服务端,若处理不当,易暴露服务端资源。
  • API Routes/Server Actions:全栈开发带来更多后端接口,需防护注入、认证等问题。
  • 静态资源与 CDN:需防止资源劫持、缓存投毒等攻击。
  • 移动端适配:移动端用户更易受钓鱼、劫持等攻击。

2. Next.js 安全防护的核心原则

  • 最小权限原则:只暴露必要的 API 和数据。
  • 输入输出校验:所有用户输入都需严格校验和过滤。
  • 安全头部配置:合理设置 HTTP 头部,防止常见攻击。
  • 依赖安全管理:定期审计依赖库,及时修复漏洞。
  • 环境变量保护:敏感信息仅在服务端可用,绝不暴露到客户端。
  • 日志与监控:实时监控异常行为,快速响应安全事件。

代码示例(40%)

1. 防止 XSS:内容转义与 CSP

1.1 服务端内容转义
tsx 复制代码
// utils/sanitize.ts
import DOMPurify from 'isomorphic-dompurify';

export function sanitize(html: string) {
  return DOMPurify.sanitize(html);
}
1.2 在页面中安全渲染富文本
tsx 复制代码
// app/components/SafeHtml.tsx
import { sanitize } from '../utils/sanitize';

export default function SafeHtml({ html }: { html: string }) {
  return (
    <div
      dangerouslySetInnerHTML={{ __html: sanitize(html) }}
      style={{ wordBreak: 'break-all' }}
    />
  );
}
1.3 设置 CSP(Content Security Policy)

next.config.js 配置安全头部:

js 复制代码
// next.config.js
module.exports = {
  async headers() {
    return [
      {
        source: '/(.*)',
        headers: [
          {
            key: 'Content-Security-Policy',
            value:
              "default-src 'self'; img-src 'self' data: https:; script-src 'self'; style-src 'self' 'unsafe-inline'; object-src 'none';",
          },
        ],
      },
    ];
  },
};

2. 防止 CSRF:Token 校验

2.1 生成 CSRF Token
ts 复制代码
// utils/csrf.ts
import { randomBytes } from 'crypto';

export function generateCsrfToken() {
  return randomBytes(32).toString('hex');
}
2.2 在 API Route 校验 CSRF
ts 复制代码
// pages/api/secure-action.ts
import type { NextApiRequest, NextApiResponse } from 'next';

export default function handler(req: NextApiRequest, res: NextApiResponse) {
  const csrfToken = req.headers['x-csrf-token'];
  if (csrfToken !== process.env.CSRF_SECRET) {
    return res.status(403).json({ error: 'Invalid CSRF token' });
  }
  // ...安全操作
  res.status(200).json({ success: true });
}

3. 防止 SQL 注入:参数化查询

以 Prisma 为例:

ts 复制代码
// lib/db.ts
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();

export async function getUserByEmail(email: string) {
  return prisma.user.findUnique({
    where: { email }, // 参数化,防注入
  });
}

4. 环境变量安全

  • .env.local 只在服务端读取,绝不在客户端代码中引用 process.env
  • NEXT_PUBLIC_ 前缀的变量才会暴露到前端,敏感信息绝不能加此前缀。

5. 依赖安全审计

在 CI/CD 流程中加入依赖检查:

json 复制代码
// package.json
"scripts": {
  "audit": "npm audit --production"
}

CI 配置(GitHub Actions):

yaml 复制代码
# .github/workflows/security.yml
name: Security Audit

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Install dependencies
        run: npm ci
      - name: Run npm audit
        run: npm audit --production

6. API Routes 错误处理与日志

ts 复制代码
// pages/api/secure-data.ts
import type { NextApiRequest, NextApiResponse } from 'next';

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  try {
    // ...业务逻辑
    res.status(200).json({ data: 'secure' });
  } catch (error) {
    console.error('API Error:', error);
    res.status(500).json({ error: 'Internal Server Error' });
  }
}

实战项目(30%)

场景:为电商网站实现全方位安全加固

1. 需求分析
  • 用户登录、下单、评论等接口需防护 XSS、CSRF、注入等攻击。
  • 移动端需适配安全头部,防止 WebView 劫持。
  • 敏感操作需日志记录与异常告警。
2. 项目结构
graph TD A[前端页面] -->|表单提交| B[API Routes] B -->|数据存储| C[数据库] B -->|日志| D[监控系统] A -->|静态资源| E[CDN]
3. 关键代码实现
3.1 登录接口安全加固
ts 复制代码
// pages/api/login.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import { getUserByEmail } from '../../lib/db';
import bcrypt from 'bcryptjs';

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  try {
    const { email, password } = req.body;
    if (!email || !password) {
      return res.status(400).json({ error: '参数缺失' });
    }
    const user = await getUserByEmail(email);
    if (!user || !(await bcrypt.compare(password, user.password))) {
      return res.status(401).json({ error: '认证失败' });
    }
    // 生成安全 Session
    // ...
    res.status(200).json({ success: true });
  } catch (error) {
    console.error('Login Error:', error);
    res.status(500).json({ error: '服务器错误' });
  }
}
3.2 评论区防 XSS
tsx 复制代码
// components/CommentList.tsx
import SafeHtml from './SafeHtml';

export default function CommentList({ comments }) {
  return (
    <ul>
      {comments.map((c) => (
        <li key={c.id}>
          <SafeHtml html={c.content} />
        </li>
      ))}
    </ul>
  );
}
3.3 移动端安全头部适配
js 复制代码
// next.config.js
module.exports = {
  async headers() {
    return [
      {
        source: '/(.*)',
        headers: [
          { key: 'X-Frame-Options', value: 'DENY' },
          { key: 'X-Content-Type-Options', value: 'nosniff' },
          { key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
          // ...CSP 如前
        ],
      },
    ];
  },
};
3.4 日志与异常监控

集成 Sentry:

ts 复制代码
// sentry.server.config.ts
import * as Sentry from '@sentry/nextjs';

Sentry.init({
  dsn: process.env.SENTRY_DSN,
  tracesSampleRate: 1.0,
});

在 API Route 中捕获异常:

ts 复制代码
try {
  // ...
} catch (error) {
  Sentry.captureException(error);
  res.status(500).json({ error: 'Internal Server Error' });
}
4. 完整项目源码

可参考:github.com/vercel/next...


配图说明

Web 安全防护流程

flowchart LR A[用户请求] --> B[Next.js 服务端] B --> C{输入校验} C -- 合法 --> D[业务处理] C -- 非法 --> E[拒绝请求] D --> F[安全输出] F --> G[响应用户]

在线资源链接


感谢大家阅读到这里,本系列的 Next.js 学习已经结束,希望对大家有所帮助。

最后感谢阅读!欢迎关注我,微信公众号:《鲫小鱼不正经》。欢迎点赞、收藏、关注,一键三连!!!

相关推荐
吃奥特曼的饼干1 分钟前
React useEffect 清理函数:别让依赖数组坑了你!
前端·react.js
烛阴8 分钟前
TypeScript 函数重载入门:让你的函数签名更精确
前端·javascript·typescript
Keya29 分钟前
MacOS端口被占用的解决方法
前端·后端·设计模式
moyu8437 分钟前
解密Vue组件中的`proxy`:此Proxy非彼Proxy
前端
用户849137175471640 分钟前
为什么大模型都离不开SSE?带你搞懂第1章〈SSE技术基础与原理〉
前端·网络协议·llm
随笔记43 分钟前
react中函数式组件和类组件有什么区别?新建的react项目用函数式组件还是类组件?
前端·react.js·typescript
在星空下1 小时前
Fastapi-Vue3-Admin
前端·python·fastapi
FogLetter1 小时前
面试官问我Function Call,我这样回答拿到了Offer!
前端·面试·openai
Juchecar1 小时前
CSS布局模式详解 - 初学者完全指南
前端
emojiwoo1 小时前
React 状态管理:useState 与 useDatePersistentState 深度对比
前端·javascript·react.js