前端转全栈 09:安全实战——XSS / CSRF / 注入 / 越权 / 密钥
前端全栈tutorial安全xsscsrf注入越权密钥
系列目录:本文是「前端转全栈:Next.js + Supabase 实战」系列的第 9 篇。功能做出来只是 1%,安全守不住就是 0 分。这一篇是前端转全栈的「保命指南」。
前端同学常以为「安全是后端的事」,但当你自己写全栈,安全就是你的事。下面五个坑,每一个都曾让真实公司上新闻。
一、XSS:把用户输入当 HTML 渲染
原理:用户提交 <script>偷cookie</script>,你却直接插进页面,脚本就在别的用户浏览器里执行。
// ❌ 危险:直接插入用户内容
<div dangerouslySetInnerHTML={{ __html: userContent }} />
防御:
- React 默认对所有插值做转义(
{userContent}是安全的),别用dangerouslySetInnerHTML。 - 必须渲染富文本时,用
DOMPurify等库过滤白名单标签。 - 设
Content-Security-Policy响应头,限制脚本来源。
二、CSRF:借你的登录态干坏事
原理:你登录了银行,攻击者诱导你点一个链接,悄悄发起「转账」请求——浏览器自动带上你的 cookie,服务端以为是本人操作。
防御(本站场景):
- Supabase Auth 用的是 SameSite cookie + 短时效 token,天然抗 CSRF。SameSite 限制第三方站点带 cookie。
- 若自己写 session,设置
SameSite=Lax/Strict,关键操作加 CSRF token。 - 对「状态改变」的请求(POST/PUT/DELETE)严格校验来源
Origin/Referer头。
三、SQL 注入:拼接出来的灾难
原理:把用户输入直接拼进 SQL 字符串。
// ❌ 致命:用户输入拼进查询
const sql = `SELECT * FROM users WHERE name = '${req.body.name}'`
如果用户传 name = ' OR '1'='1,整张表就泄露了。
防御(用参数化 / ORM):
// ✅ Supabase 查询自动参数化,不会注入
supabase.from("users").select("*").eq("name", req.body.name)
只要永远用参数化查询或 ORM(Prisma/Drizzle),绝不字符串拼接 SQL,注入就基本无解。本站全程 Supabase 查询,天然安全。
四、越权访问(Broken Access Control)
原理:用户 A 把自己的请求 id=123 改成 id=456,就能看/删用户 B 的数据。
防御:
- 对象级鉴权:每个写操作都验证「这条记录属于当前用户」。
const { data } = await supabase.from("comments").delete()
.eq("id", id).eq("user_id", user.id) // 双重条件锁死归属
- 配合 RLS(第 7 篇):
USING (auth.uid() = user_id),即使绕过接口也删不了别人的。 - 最小权限原则:admin 客户端的
service_rolekey 只用于真正需要的运维操作,不在普通业务流里用。
五、密钥泄露:最蠢也最常见
原理:把 SUPABASE_SERVICE_ROLE_KEY、数据库密码写进前端代码或提交到 GitHub 公开仓库,等于把家门钥匙挂门口。
防御清单:
- 所有密钥进环境变量(
.env.local),永不进前端、永不提交。 - 前端只能有
NEXT_PUBLIC_开头的公开 key(anon key 本就设计为公开)。 .gitignore屏蔽.env*;用git-secrets/ GitHub 推送保护防止误提交。- 泄露即轮换(rotate),别抱侥幸心理。
六、全栈安全自查清单
部署前逐条过:
- [ ] 所有输出做了 HTML 转义(没用
dangerouslySetInnerHTML或已净化) - [ ] 所有 DB 查询参数化 / 走 ORM
- [ ] 每个写操作验证资源归属(对象级鉴权)
- [ ] RLS / 行级权限已开启
- [ ] 密钥全在环境变量,无硬编码、无提交
- [ ] 错误响应不泄露堆栈 / 内部路径
- [ ] 设置了 CSP、SameSite cookie 等基础防护头
总结
- XSS:别直接渲染用户输入,React 默认转义是朋友。
- CSRF:SameSite cookie + 校验来源。
- SQL 注入:参数化查询 / ORM,杜绝字符串拼接。
- 越权:对象级鉴权 + RLS 双保险。
- 密钥:环境变量 + 不提交 + 泄露即轮换。
安全不是功能,是底线。下一篇我们补上「通用 Node 后端」——当你需要 Next.js 之外的独立服务时怎么办。
练习:打开你项目里所有用到
dangerouslySetInnerHTML或字符串拼接 SQL 的地方,逐一改成安全写法;再检查.env.local是否已被.gitignore屏蔽。
Comments
Sign in to leave a comment.