我如何构建一个安全优先的 SaaS Boilerplate,拥有 100% 测试覆盖率
Source: Dev.to
我们以后再加安全。
如果你曾经这么说过,你并不孤单。我也有过同样的经历。但在目睹了无数数据泄露、凌晨 2 点的紧急安全补丁,以及每次泄露平均 $4.45 million 的成本后,我决定采取不同的做法。
这就是我如何构建 ShipSecure 的故事——一个安全不是事后考虑,而是基石的 Next.js 样板项目。
问题:安全作为待办事项
大多数开发者处理安全的方式如下:
- ✅ 构建着陆页
- ✅ 添加身份验证
- ✅ 集成支付功能
- ⬜ 安全(我们稍后再处理)
听起来熟悉吗?问题在于稍后往往意味着第一次事故之后。到那时,你已经:
- 失去客户信任
- 面临潜在的监管罚款(GDPR、CCPA、SOC 2)
- 花费数周时间将安全性改装到原本未为其设计的架构中
解决方案:安全优先的开发
与其把安全当作一个功能,我把它当作 架构。每一行代码都考虑了安全,每个安全特性都有相应的测试。
下面带你了解关键概念。
1. 安全响应头 – 你的第一道防线
大多数开发者都知道 Content‑Security‑Policy,但有多少人真正正确地实现它?这不仅仅是添加一个响应头,而是需要 完整的安全响应头策略。
| 标头 | 防止的攻击 |
|---|---|
Content‑Security‑Policy | XSS 攻击、代码注入 |
Strict‑Transport‑Security | 协议降级攻击 |
X‑Content‑Type‑Options | MIME‑嗅探漏洞 |
X‑Frame‑Options | 点击劫持攻击 |
Referrer‑Policy | 信息泄露 |
Permissions‑Policy | 未授权的浏览器功能访问 |
难点在于?让这些响应头协同工作而不破坏应用。单独的 CSP 就有数十个指令需要精细配置——一个错误的设置就会导致 OAuth 流程中断、分析停止或样式不加载。
我的做法: 一个集中式的 getSecurityHeaders() 函数,返回配置好的响应头对象,并通过中间件在所有路由上统一使用。
// lib/securityHeaders.ts
export function getSecurityHeaders() {
return {
"Content-Security-Policy":
"default-src 'self'; script-src 'self' https://trusted.cdn.com",
"Strict-Transport-Security":
"max-age=63072000; includeSubDomains; preload",
"X-Content-Type-Options": "nosniff",
"X-Frame-Options": "DENY",
"Referrer-Policy": "no-referrer",
"Permissions-Policy": "geolocation=(), microphone=()",
};
}
2. 限流 – “慢一点”的艺术
如果没有限流,你的 API 就会成为以下攻击的敞开邀请:
- 🔓 登录暴力破解
- 💥 DDoS 攻击
- 🎭 凭证填充
生产环境 vs. 开发环境
生产环境需要分布式限流(例如 Upstash Redis),这样限制才能跨无服务器实例生效。开发环境则不应强制依赖 Redis。
我的解决方案: 采用混合架构并自动回退。
// lib/rateLimiter.ts
import { Redis } from "@upstash/redis";
let store: RateLimitStore;
if (process.env.UPSTASH_REDIS_URL) {
const redis = new Redis({
url: process.env.UPSTASH_REDIS_URL,
token: process.env.UPSTASH_REDIS_TOKEN,
});
store = new RedisStore(redis);
} else {
store = new MemoryStore(); // 简单的内存映射
}
// 导出使用滑动窗口算法的限流中间件
export const rateLimiter = createRateLimiter({
store,
window: 60, // 秒
limit: 100, // 每窗口请求数
algorithm: "sliding",
});
为什么选滑动窗口?
固定窗口会让攻击者在边界“突发”(例如在 :59 发 10 条请求,在 :01 再发 10 条)。滑动窗口消除了这种边缘情况。
3. 测试驱动的安全 – 改变游戏规则的关键
每个安全特性都有对应的测试。
重要性
- 测试证明声明。 任何人都可以说“我们使用 CSP”。测试才能验证它。
- 测试防止回归。 实习生不小心删掉响应头也能被捕获。
- 测试记录行为。 安全需求变成可执行的规范。
三层测试策略
层级 1:单元测试 (Vitest)
• 各个安全函数
• 验证逻辑
• 边界情况
层级 2:集成测试
• 中间件行为
• 认证流程
• API 保护
层级 3:端到端测试 (Playwright)
• 真正的浏览器行为
• 响应头校验
• 用户旅程安全
结果?75+ 条测试 覆盖了所有安全机制。CI/CD 在每次 PR 时运行它们——没有安全特性会在没有覆盖的情况下上线。
4. I
Source: …
输入验证 – 大多数漏洞的起点
SQL 注入、XSS、命令注入——全部源于 未验证的输入。
典型(易受攻击)的代码
// ❌ This is asking for trouble
const { email, name } = await req.json();
await db.users.create({ email, name });
没有验证,没有类型检查,没有防护。
安全优先的做法
- 为每个输入 定义 schema。
- 在边界处 进行验证(API 路由、表单提交)。
- 快速失败 并返回明确的错误信息。
- 永不信任——即使是已认证的用户也可能发送恶意数据。
我使用 Zod 来获得编译时的类型安全和运行时的验证,但工具本身不如 随时随地验证一切 的纪律重要。
// lib/schemas.ts
import { z } from "zod";
export const createUserSchema = z.object({
email: z.string().email(),
name: z.string().min(1).max(100),
});
// pages/api/users.ts
import { createUserSchema } from "@/lib/schemas";
export async function POST(req: Request) {
const data = await req.json();
const parsed = createUserSchema.safeParse(data);
if (!parsed.success) {
return new Response(JSON.stringify(parsed.error), { status: 400 });
}
await db.users.create(parsed.data);
return new Response(null, { status: 201 });
}
5. 正确的身份验证
身份验证是大多数安全漏洞的聚集地:密码存储、会话管理、CSRF 防护、OAuth 流程等。
我的做法: 使用 Auth.js v5(前称 NextAuth),因为它提供:
- ✅ 默认 HttpOnly、Secure Cookie
- ✅ 内置 CSRF 防护
- ✅ 经受考验的 OAuth 实现
- ✅ 可靠的会话管理
关键洞察:身份验证不仅仅是登录/登出——它还涉及:
- 会话的存储与验证方式
- Cookie 配置(SameSite、Secure、HttpOnly)
- 令牌的处理与轮换
- 真正使会话失效的正确登出流程
Auth.js 开箱即用地处理上述所有内容,降低关键失误的风险。
来之不易的教训
在构建完之后,我希望自己早些知道以下内容:
1. 在设计时加入安全性可省下 10× 成本
在已有代码库中后期加入安全性非常痛苦。每一个未考虑安全的决定,日后都要付出高昂的修复代价。
(…其余教训将在原文中继续。)
2. 测试是你最好的安全文档
当有人问“你如何防止 X ?”时,指向测试文件。那是事实的证明,而不是空口承诺。
3. 开发者体验很重要
如果安全措施令人烦恼,开发者会寻找变通办法。
- 速率限制的内存回退?这关乎开发者体验。
- 集中的 Header 函数?这也是为了开发者体验。
安全性应对使用你代码库的开发者保持透明。
4. 一切自动化
在 CI/CD 中加入安全测试,意味着没有人能意外(或故意)发布不安全的代码。这不是信任的问题,而是系统的问题。
关键结论
事实如下:
- 60 % 的初创公司在安全漏洞后 6 个月内关闭
- 73 % 的企业客户要求安全认证
- SOC 2 合规可将销售周期缩短 40 %
安全不是一个功能——它是竞争优势。
想要完整实现吗?
我已将所学全部打包成 ShipSecure —— 一个从第一天起就安全的 Next.js 15 样板项目:
- 🛡️ 所有 7 项安全头已预配置并可用
- ⚡ 使用 Redis + 内存回退的速率限制
- 🔐 Auth.js v5,使用安全默认配置
- ✅ 超过 75 条测试,覆盖 100 % 安全
- 📦 包含 Stripe 集成
- 📚 完整文档
- 🔄 终身更新
一次性购买。省去安全研究,直接开始构建。
你负责开发,我们负责安全。
对 Next.js 的安全有疑问吗?留下评论——我会阅读每一条。