3. 安全架构:验证与防御

从"邮箱验证"到"多层机器人防御",从"IP 访问"到"域名路由",理解安全的层层防线。


3.1 身份认证体系(NextAuth + Prisma)

身份认证不是单一插件,而是一个协同系统:

组件 角色 类比 存储位置
PostgreSQL 用户数据存储 保险柜 Docker db 容器
Prisma 数据库操作层 会计(查账记账) Next.js 服务端
NextAuth.js 身份校验 酒店前台(核对证件/发房卡) Next.js API 层

NextAuth 的"邮箱验证"流程

用户输入邮箱 → NextAuth 生成加密 Token → 写入数据库 VerificationToken 表
                    ↓
            调用邮件服务(Resend)发送验证链接
                    ↓
            用户点击链接 → NextAuth 校验 Token
                    ↓
            Prisma 更新 User.emailVerified 字段

原理说明

NextAuth 提供验证流程,但不提供"发件箱"。你需要自备 SMTP 服务(如 Resend)。验证流程:

  1. 用户注册,NextAuth 生成加密 Token
  2. 调用你配置的邮件服务发送验证链接
  3. 用户点击链接,NextAuth 校验 Token
  4. Prisma 更新数据库中 emailVerified 字段

为什么自建认证而非用第三方?

维度 NextAuth + Resend Clerk / Supabase
数据主权 用户邮箱存在自己数据库 存在第三方平台
费用 邮件量不大时几乎免费 按用户量收费
可控性 完全自控 依赖平台稳定性
集成度 完美配合 Prisma 需要适配层

NextAuth + Resend 配置

# .env 中配置
RESEND_API_KEY=re_xxxxxxxxxxxx      # Resend API 密钥
RESEND_FROM=onboarding@resend.dev   # 开发环境发件地址
NEXTAUTH_SECRET=your-secret-here    # JWT 签名密钥(用 openssl rand -base64 32 生成)
NEXTAUTH_URL=http://localhost:3000  # 认证回调地址(按阶段修改)

Resend 开发环境的限制

重要规则:开发环境(onboarding@resend.dev)只能发给自己。

原理说明


3.2 多层防御体系

邮箱验证解决"虚假账号"问题,但无法阻止脚本疯狂调用发信接口消耗你的额度。需要在前面增加更多防线。

第一层:蜜罐技术(Honeypot)—— 对用户无感的陷阱

<!-- 在注册表单中添加一个对人类隐藏的字段 -->
<input
  type="text"
  name="website"           <!-- 字段名像正常字段,吸引机器人填充 -->
  style="display: none"    <!-- CSS 隐藏:人类看不见 -->
  tabindex="-1"            <!-- Tab 跳过:键盘导航也看不见 -->
  autoComplete="off"
/>

原理说明

第二层:速率限制(Rate Limiting)—— 流量的闸机

// 后端校验逻辑伪代码
const canSend = await rateLimit.check({
  ip: request.ip,           // 同一 IP 1 分钟内只能请求 1 次
  email: body.email,        // 同一邮箱 10 分钟内只能触发 1 次
});

if (!canSend) {
  return res.status(429).json({ error: "请求过于频繁,请稍后再试" });
}

原理说明

第三层:人机校验(Cloudflare Turnstile)—— 智能门卫

推荐使用 Cloudflare Turnstile(Google reCAPTCHA 的现代替代品):

# 1. .env 中配置
NEXT_PUBLIC_TURNSTILE_SITE_KEY=1x00000000000000000000xx
TURNSTILE_SECRET_KEY=0x000000000000000000000000000000000000000
// 2. 前端引入 Turnstile 组件
// 大部分情况用户无需点击,Cloudflare 在后台自动验证
// 3. 后端验证 Token 真实性
const result = await fetch('https://challenges.cloudflare.com/turnstile/v0/siteverify', {
  method: 'POST',
  body: `secret=${secret}&response=${token}`,
});

原理说明

防御优先级建议

优先级 1:NextAuth + Resend 邮箱验证     → 确保账号真实性
优先级 2:蜜罐字段                        → 拦截基础脚本
优先级 3:Cloudflare Turnstile            → 上线前务必开启

3.3 从 IP 到域名:网络流量路径

一个项目从"本地运行"走向"公网运营",最核心的转变在于从"直接访问"升级为"代理访问"。

流量穿透的"三级跳"

用户浏览器 → ① 域名解析(DNS) → ② 云平台防火墙(安全组) → ③ Nginx 反向代理 → 内部容器

第一跳:域名解析

用户输入 openrise.com,DNS 将该域名指向你的服务器公网 IP。域名是马甲,IP 才是真实地址。

第二跳:云平台防火墙(安全组)

90% 的部署失败原因在此:腾讯云安全组没有放通 80/443 端口。即便服务器内的应用在运行,外部流量连网卡都碰不到。

第三跳:Nginx 反向代理

Nginx 监听 80 端口,识别请求域名,转发给对应的内部容器。

Nginx 反向代理配置示例

# nginx/nginx.conf
server {
    listen 80;
    server_name openrise.com;    # 监听的域名

    location / {
        proxy_pass http://frontend:3000;  # 转发给 Next.js 容器
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }

    location /api/ {
        proxy_pass http://backend:8000;   # API 转发给后端容器
        proxy_set_header Host $host;
    }

    # 静态资源缓存 7 天
    location /_next/static/ {
        proxy_pass http://frontend:3000;
        expires 7d;
        add_header Cache-Control "public, immutable";
    }
}

原理说明


3.4 NEXTAUTH_URL:被忽略的关键变量

在 OAuth 和邮箱验证流程中,NEXTAUTH_URL 是不可或缺的环境变量。

为什么必须填写?

原理说明

按阶段设置

# 本地开发阶段
NEXTAUTH_URL=http://localhost:3000

# 初期部署(无域名)
NEXTAUTH_URL=http://你的服务器公网IP

# 正式运营(有域名)
NEXTAUTH_URL=https://www.openrise.com

3.5 域名、IP 与服务器备案

物权关系

实体 性质 说明
服务器 购买的空间和算力 提供一个固定公网 IP
域名 单独购买 不自带服务器,只是一个"指向标"
备案(ICP) 中国大陆合规要求 域名通过 80/443 提供服务必须备案

IP 访问 vs 域名

场景 推荐 原因
前期调试 IP 直接访问 无需备案,快速验证
正式运营 域名 (已备案) SSL 证书绑定域名,提供 HTTPS
开发阶段 localhost 稳定,不受代理/VPN 影响

注意http://0.0.0.0:3000 不可用(会 502);http://198.18.0.1:3000 是保留地址,易出现代理转发失败导致的 502。

架构师避坑清单

  1. 安全组放行:部署完发现 IP 打不开?90% 原因是安全组没开 80 端口
  2. Docker 监听地址:容器内 Next.js 必须监听 0.0.0.0,否则 Nginx 在容器外找不到它
  3. 端口映射规范:Nginx 映射宿主机的 80:80;Next.js 只需内网暴露 3000,不映射宿主机

返回知识库 | 下一篇:服务器环境搭建