身份认证与鉴权
区分两个概念:认证(Authentication)——你是谁;授权(Authorization)——你能做什么。
Cookie / Session / Token
面试题:Cookie、Session、Token 的区别?
- Cookie:浏览器存储的小数据,每次请求自动带上。
HttpOnly(JS 读不到,防 XSS)、Secure(仅 HTTPS)、SameSite(防 CSRF)。 - Session:服务端存储用户状态,给客户端一个
sessionId(通常存在 Cookie 里)。有状态,服务端要存储,集群下需共享(存 Redis)。 - Token(JWT):服务端签发、客户端保存(localStorage / Cookie),请求放在
Authorization: Bearer xxx头里。无状态,服务端不存储,天然适合分布式和跨域。
| 维度 | Session | JWT |
|---|---|---|
| 存储 | 服务端存(占内存) | 服务端不存(自包含) |
| 扩展性 | 集群需共享存储 | 天然支持分布式 |
| 跨域 | Cookie 跨域麻烦 | 放 header,跨域友好 |
| 失效控制 | 服务端删即可,实时 | 签发后难主动失效(需黑名单) |
| 体积 | 小(仅 id) | 较大(每次请求都带) |
JWT 详解
面试题:JWT 的结构?如何防篡改?
三段用 . 分隔:Header.Payload.Signature(base64Url 编码)。
- Header:算法(如 HS256)和类型。
- Payload:声明(用户 id、过期时间
exp等)。注意:只是 base64 编码,不是加密,不能放敏感信息(如密码)。 - Signature:
HMACSHA256(base64(header) + "." + base64(payload), secret)。服务端用密钥重新计算签名比对,篡改 payload 会导致签名不匹配。
const jwt = require("jsonwebtoken");
// 签发
const token = jwt.sign({ userId: 1 }, process.env.JWT_SECRET, {
expiresIn: "2h",
});
// 校验(中间件里)
try {
const payload = jwt.verify(token, process.env.JWT_SECRET);
ctx.state.user = payload;
} catch (err) {
ctx.throw(401, "token 无效或过期");
}
面试题:JWT 怎么实现「主动注销」/「续期」?
- 主动注销:JWT 本身无状态无法注销,需配合服务端黑名单(注销的 token 存 Redis 直到过期)。
- 续期:用 双 Token——短期
accessToken(如 2h)+ 长期refreshToken(如 7d)。accessToken 过期时用 refreshToken 换新的,refreshToken 存服务端可控制失效。
OAuth 2.0 与单点登录
- OAuth 2.0:第三方授权(「用微信登录」),主流是授权码模式(Authorization Code):客户端拿 code → 后端用 code + secret 换 access_token → 用 token 拉用户信息。
- SSO 单点登录:一处登录,多系统通用。同域用共享 Cookie;跨域用 CAS / OIDC(基于 OAuth2 的认证层)。
- RBAC 权限模型:用户 - 角色 - 权限三层,鉴权时校验角色是否有对应权限。
后端安全
前端视角的安全见 前端安全,这里聚焦服务端防护。
SQL 注入
拼接 SQL 导致恶意输入改变语义。防御:参数化查询 / 预编译(不要字符串拼接),ORM 默认参数化。
// 危险:'1; DROP TABLE user;--' 会被注入
db.query(`SELECT * FROM user WHERE id = ${id}`);
// 安全:参数化
db.query("SELECT * FROM user WHERE id = ?", [id]);
XSS / CSRF(服务端层面)
- XSS(跨站脚本):对用户输入做转义/过滤,输出时编码;敏感 Cookie 设
HttpOnly;设置Content-Security-Policy响应头。 - CSRF(跨站请求伪造):① CSRF Token(表单/请求带随机 token,服务端校验);② Cookie 设
SameSite=Lax/Strict;③ 校验Origin/Referer。
密码存储
面试题:密码应该怎么存?为什么不能用 MD5?
- 绝不能明文存;MD5/SHA 是「快速哈希」,可被彩虹表 + GPU 暴力破解。
- 应使用加盐 + 慢哈希算法:
bcrypt、scrypt、argon2。盐值让相同密码哈希结果不同,慢哈希增加暴力破解成本。
const bcrypt = require("bcrypt");
const hash = await bcrypt.hash(password, 10); // 10 是 cost factor
const ok = await bcrypt.compare(inputPassword, hash);
其他防护要点
- 限流:防暴力破解和刷接口(令牌桶/漏桶,见性能篇)。
- Helmet:一行设置一系列安全响应头(
X-Frame-Options防点击劫持、HSTS 等)。 - HTTPS:传输加密,防中间人。
- 越权校验:每个接口都要校验「当前用户是否有权操作该资源」(防水平/垂直越权),不要只靠前端隐藏按钮。
- 敏感信息:密钥、数据库密码走环境变量 / 密钥管理,不要进代码仓库。
- 依赖安全:
npm audit检查依赖漏洞。