安全可靠性模式:认证与授权(OAuth2、JWT)、防SQL注入、防XSS/CSRF、安全头、密钥管理
聊到安全可靠性,我脑子里第一个蹦出来的词就是「攻防」。你想想看,一个系统如果连门都看不住,里面代码写得再漂亮又有什么用?今天咱们就把安全这块的几个硬骨头啃一啃——认证授权、注入攻击、跨站脚本、请求伪造,还有密钥管理。这些不是理论,是实战中每天都会碰到的真问题。
一、认证与授权:OAuth2 与 JWT
先说认证和授权的区别。认证是「你是谁」,授权是「你能干什么」。很多新手容易搞混,我见过一个项目,把用户角色直接塞进 JWT 的 payload 里,结果权限校验全在前端做——这跟把钥匙挂在门外有啥区别?
1. OAuth2 授权框架
OAuth2 说白了就是一个「代客泊车」的协议。用户不想把车钥匙(密码)直接给你,而是交给一个可信的停车场管理员(授权服务器),管理员给你一个临时停车牌(access token),你拿着这个牌去开车门(访问资源)。
常见的四种授权模式:
- 授权码模式:最安全,适合有后端的 Web 应用。用户先拿到授权码,后端再用授权码换 token。
- 隐式模式:适合纯前端应用,但现在已经不推荐了。我建议直接用授权码模式 + PKCE。
- 密码模式:用户直接把密码给客户端。除非是你自己的一体化应用,否则别用。
- 客户端凭证模式:机器对机器通信,没有用户参与。
2. JWT 令牌
JWT 的结构很简单:Header.Payload.Signature。但简单不代表你可以乱用。我见过有人把密码明文放 payload 里——嗯,这跟把密码写在便利贴上贴脑门上差不多。
// 一个标准的 JWT 生成示例(Node.js)
const jwt = require('jsonwebtoken');
const payload = {
userId: 123,
role: 'admin',
// 千万别放敏感信息!payload 只是 base64 编码,不是加密
};
const token = jwt.sign(payload, 'your-secret-key', {
expiresIn: '2h',
algorithm: 'RS256' // 生产环境用非对称加密
});
这里有个坑:JWT 一旦签发,在过期之前是无法撤销的。我曾经在一个项目中遇到员工离职,但他的 token 还有效——怎么办?解决方案是维护一个黑名单,或者把 token 有效期设短,配合 refresh token 使用。
二、防 SQL 注入
SQL 注入是个老话题了,但每年依然有大量系统栽在这上面。说白了,就是用户输入的数据被当成了 SQL 代码执行。
最有效的防御手段就一条:永远不要拼接 SQL 字符串。用参数化查询或者 ORM 的预编译机制。
// ❌ 错误示范:字符串拼接
const sql = `SELECT * FROM users WHERE username = '${username}' AND password = '${password}'`;
// ✅ 正确做法:参数化查询
const sql = 'SELECT * FROM users WHERE username = ? AND password = ?';
db.query(sql, [username, password]);
你可能会说:「我用 ORM 了,应该安全了吧?」不一定。ORM 的 raw query 功能、order by 后面的字段、like 查询的模糊匹配,这些地方依然可能出问题。我见过一个项目用 Sequelize 的 sequelize.literal() 拼接排序字段,结果被注入了。
三、防 XSS 与 CSRF
1. XSS(跨站脚本攻击)
XSS 就是攻击者往你的页面里塞了一段恶意脚本。常见的有三种:存储型(数据存到数据库里了)、反射型(通过 URL 参数注入)、DOM 型(前端代码自己执行了恶意内容)。
防御手段其实不复杂:
- 输出编码:所有用户输入的内容,在输出到 HTML 时都要做转义。比如
<转成< - Content Security Policy:通过 HTTP 头限制脚本的来源。我习惯设置
script-src 'self',只允许加载同域脚本。 - HttpOnly Cookie:把 session cookie 设置成 HttpOnly,这样 JavaScript 就读取不到。
v-html 或 dangerouslySetInnerHTML,一定要确保内容已经过消毒处理。我一般用 DOMPurify 库做二次清洗。
2. CSRF(跨站请求伪造)
CSRF 的原理是:用户登录了银行网站,然后不小心点开了攻击者的钓鱼页面,这个页面偷偷向银行发了一个转账请求——因为浏览器会自动带上 cookie,所以银行以为是用户本人操作的。
防御方案:
- CSRF Token:每个表单都带一个随机 token,服务端校验。这是最经典的做法。
- SameSite Cookie:设置
SameSite=Strict或Lax,浏览器就不会在跨站请求中自动带 cookie。 - 验证 Referer 头:检查请求来源是否合法。但 Referer 可能被篡改,所以只能作为辅助手段。
四、安全头配置
HTTP 响应头是浏览器安全的第一道防线。很多攻击其实可以通过几个简单的头配置就挡住。我每次搭建新项目,第一件事就是配安全头。
| 安全头 | 作用 | 推荐配置 |
|---|---|---|
| Content-Security-Policy | 限制资源加载来源 | default-src 'self'; script-src 'self' |
| X-Content-Type-Options | 禁止 MIME 类型嗅探 | nosniff |
| X-Frame-Options | 防止点击劫持 | DENY 或 SAMEORIGIN |
| Strict-Transport-Security | 强制 HTTPS | max-age=31536000; includeSubDomains |
| Referrer-Policy | 控制 Referer 信息泄露 | strict-origin-when-cross-origin |
配置这些头其实不费什么事,但很多人就是忽略了。我记得有一次帮一个客户做安全审计,发现他们的 API 连 X-Content-Type-Options 都没配,浏览器直接把一个 JSON 响应当 HTML 解析了——这要是被利用,后果不堪设想。
五、密钥管理
密钥管理是安全体系里最容易被忽视的一环。你想想看,JWT 签名密钥、数据库密码、API 密钥、加密密钥——这些东西一旦泄露,整个系统的安全就形同虚设。
我总结了几条铁律:
- 密钥不要硬编码:别把密钥写在代码里,也别提交到 Git 仓库。用环境变量或者专门的密钥管理服务。
- 定期轮换:密钥要有生命周期。我习惯每 90 天轮换一次 JWT 签名密钥。
- 最小权限:每个服务只拿它需要的密钥。比如支付服务只需要支付密钥,不需要数据库的 root 密码。
- 审计日志:谁在什么时候访问了哪个密钥,都要有记录。
六、知识体系总览
下面这张图把今天讲的内容串起来了。你可以看到,安全不是单点防御,而是一个从认证授权到数据存储、再到传输和展示的完整链路。
安全这件事,说白了就是「信任最小化」。不要信任任何输入,不要信任任何外部请求,甚至不要信任自己的代码——因为你永远不知道哪个角落会出问题。我做了十几年架构,最大的体会就是:安全不是加几个功能,而是一种设计思维。从架构选型到代码实现,每一步都要问自己:「如果这里被攻击了,我扛得住吗?」
公众号:蓝海资料掘金营,微信deep3321