Skip to main content

身份认证与鉴权

区分两个概念:认证(Authentication)——你是谁;授权(Authorization)——你能做什么。

面试题:Cookie、Session、Token 的区别?

  • Cookie:浏览器存储的小数据,每次请求自动带上。HttpOnly(JS 读不到,防 XSS)、Secure(仅 HTTPS)、SameSite(防 CSRF)。
  • Session:服务端存储用户状态,给客户端一个 sessionId(通常存在 Cookie 里)。有状态,服务端要存储,集群下需共享(存 Redis)。
  • Token(JWT):服务端签发、客户端保存(localStorage / Cookie),请求放在 Authorization: Bearer xxx 头里。无状态,服务端不存储,天然适合分布式和跨域。
维度SessionJWT
存储服务端存(占内存)服务端不存(自包含)
扩展性集群需共享存储天然支持分布式
跨域Cookie 跨域麻烦放 header,跨域友好
失效控制服务端删即可,实时签发后难主动失效(需黑名单)
体积小(仅 id)较大(每次请求都带)

JWT 详解

面试题:JWT 的结构?如何防篡改?

三段用 . 分隔:Header.Payload.Signature(base64Url 编码)。

  • Header:算法(如 HS256)和类型。
  • Payload:声明(用户 id、过期时间 exp 等)。注意:只是 base64 编码,不是加密,不能放敏感信息(如密码)。
  • SignatureHMACSHA256(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 暴力破解。
  • 应使用加盐 + 慢哈希算法:bcryptscryptargon2。盐值让相同密码哈希结果不同,慢哈希增加暴力破解成本。
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 检查依赖漏洞。