本文记录了一个大二学生在开发校园论坛时,为即将开始的班级试点进行网络安全加固的全过程。如果你也在独立做全栈项目,希望这些经验能帮你少踩几个坑。
前言
我的校园论坛(江农论坛)已经完成了核心功能开发------帖子发布、评论互动、分区浏览、树洞匿名、首页推荐、个人主页、管理员审核、白名单注册。在准备向全班同学推广之前,我意识到一个严重的问题:项目一直在"裸奔"。
后端 cors() 允许所有来源访问,没有频率限制,请求体大小不受控,HTTP 安全头一个都没配。这就像一个房子装修好了,但门窗全开着。
今天的目标很明确:在试点之前,把安全加固做完。
一、CORS 白名单:别让所有人都能调你的 API
问题
项目初期为了开发方便,后端直接用了 app.use(cors()),这行代码的意思是"允许任何网站跨域请求我的 API"。在生产环境里,这意味着任何网站都可以用 JavaScript 调用你的接口,用你用户的身份发帖、评论。
解决方案
改成只允许自己的域名和本地开发地址:
javascript
app.use(cors({
origin: [
'https://www.aoliforum.me',
'https://aoliforum.me',
'http://localhost:5173'
],
credentials: true
}))
踩坑点
如果你的前端和后端不在同一个域名下(我的前端在 Vercel,后端在 Railway),并且前端请求时带了 Authorization 头,那么 credentials: true 是必须的。否则浏览器不会发送 Cookie 和 Authorization 头。
二、请求体大小限制:防止恶意超大请求
问题
Express 默认不限制请求体大小。如果有人恶意发送一个巨大的 JSON 请求,你的服务器可能会内存溢出或响应变慢。
解决方案
javascript
app.use(express.json({ limit: '1mb' }))
这行代码限制所有请求体不超过 1MB。对于论坛场景(纯文本帖子、评论),1MB 已经非常充裕。如果以后接入了图片上传,图片走七牛云直传,不会经过这个限制。
三、接口防刷:别让人暴力破解和批量注册
问题
登录接口如果没有任何限制,攻击者可以用脚本暴力破解密码。注册接口没限制,可以批量注册垃圾账号。
解决方案
用 express-rate-limit 对不同接口设置不同的频率限制:
javascript
const rateLimit = require('express-rate-limit')
// 登录接口:15分钟最多10次
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 10,
message: { error: '登录尝试过多,请15分钟后再试' }
})
app.use('/api/users/login', loginLimiter)
// 注册接口:1小时最多3次
const registerLimiter = rateLimit({
windowMs: 60 * 60 * 1000,
max: 3,
message: { error: '注册请求过于频繁,请稍后再试' }
})
app.use('/api/users/register', registerLimiter)
踩坑点
app.use 的顺序非常重要 。限速中间件必须放在路由注册之前,否则不会生效。我一开始把 rateLimit 的代码放在了路由注册之后,测试时发现完全没限制,排查了半天才发现是顺序问题。
四、Helmet 安全头:给你的 HTTP 响应穿上盔甲
问题
Express 默认不会添加任何安全相关的 HTTP 头。这意味着你的网站容易被点击劫持、MIME 类型嗅探、XSS 等攻击。
解决方案
安装 helmet 并配置:
javascript
const helmet = require('helmet')
app.use(helmet({
crossOriginResourcePolicy: { policy: 'cross-origin' },
contentSecurityPolicy: false
}))
踩坑点
helmet 默认会启用 CSP(内容安全策略),它会限制页面可以加载哪些来源的资源。但我的前端和后端不同域,前端需要跨域请求后端 API,默认的 CSP 策略会拦截这些请求,导致论坛完全不可用。
解决方案是关闭 CSP(contentSecurityPolicy: false),同时把 crossOriginResourcePolicy 设为 cross-origin,允许跨域资源加载。这不是最佳实践,但对于前后端分离的小项目来说,这是最务实的做法。
五、环境变量与依赖安全
环境变量
检查 .gitignore 是否包含了 .env 文件:
bash
backend/.env
.env
然后用 git log 确认历史记录里没有误提交过 .env 文件。如果提交过,需要立即更换数据库密码和 JWT 密钥。
依赖安全
运行 npm audit 检查依赖漏洞。我的项目发现 mongo-express 有 3 个低危漏洞。mongo-express 是 MongoDB 的 Web 管理界面,只是一个开发工具,不暴露在公网,但为了零漏洞,我直接卸载了它,用 MongoDB Compass 和 Atlas 网页界面替代。
总结与感受
这次安全加固让我意识到:功能开发和安全加固是两件完全不同的事。 功能开发是"让东西跑起来",安全加固是"让东西不被搞垮"。前者追求速度,后者追求防御深度。
分享几点体会:
- CORS 白名单是底线 :永远不要在生产环境用
cors()通配符。这是最低成本的防护,也是最容易被忽视的。 - 限速配置要区分场景:登录、注册、发帖、评论,不同接口的风险不同,限速策略也应该不同。一刀切的全局限制要么太松(登录被刷),要么太紧(正常用户被误伤)。
- Helmet 不是"装上就行":它默认的 CSP 策略会让前后端分离的项目挂掉。你需要理解每个安全头的作用,而不是盲目装包。
- 环境变量泄露是不可逆的 :一旦
.env被提交到 Git,你的密码就永远留在历史记录里了。唯一的补救是立即更换密钥。 - 依赖安全要定期检查 :
npm audit只需要几秒钟,但它能在漏洞被利用之前提醒你。
项目状态更新:
- 在线地址:aoliforum.me
- 已完成功能:帖子发布、评论互动、游客模式、分区浏览、树洞匿名、首页推荐、个人主页、管理员审核、白名单注册、联系通道
- 已完成安全加固:CORS 白名单、接口防刷、Helmet 安全头、请求体限制、环境变量与依赖安全
- 待完成:消息通知
如果你也在独立做全栈项目,欢迎评论区交流你的安全实践。