← 返回生产场景
🔑问题1:JWT vs Session 怎么选高频
Session 是服务端存状态(有状态),JWT 是令牌自包含(无状态)。各有适用场景。
// Session(有状态)
服务端存 session → sessionId 存 Cookie → 每次请求带 Cookie
// 优点:可主动注销(删session就行)
// 缺点:多台机器要共享session(Redis session / sticky session)
// JWT(无状态)
登录后签发 JWT → 客户端存(localStorage/cookie)→ 每次请求 Header 带上
服务端不存,只验签 + 过期时间
// 优点:无状态,天然支持分布式,不用共享session
// 缺点:无法主动注销(签了就只能等过期),payload别放敏感信息(base64不是加密)
选型:微服务/前后端分离用 JWT;传统单体/需要主动注销用 Session+Redis。JWT 的 payload 是明文 base64,别放密码!
🔄问题2:Token 刷新竞态(多个请求同时401)线上事故
页面同时发5个请求,token过期,5个都401。每个都触发refresh,refresh token被多次使用失效。
// 双Token机制:accessToken(短期) + refreshToken(长期)
// accessToken 过期 → 用 refreshToken 换新的
// 竞态问题:5个请求同时401 → 触发5次refresh → 后端认为异常 → 踢下线
// 解法:只让第一个触发refresh,其他排队等
let isRefreshing = false;
let queue = [];
axios.interceptors.response.use(null, async err => {
if (err.status === 401 && !err.config._retry) {
if (isRefreshing) {
// 已经在刷新了,排队等
return new Promise(resolve => {
queue.push(token => { err.config.headers.Auth = 'Bearer '+token; resolve(axios(err.config)); });
});
}
isRefreshing = true;
const newToken = await refreshToken();
queue.forEach(cb => cb(newToken)); queue = [];
isRefreshing = false;
return axios(err.config);
}
});
🔐问题3:密码怎么存(别明文!别MD5!)安全
// ❌ 明文存 → 泄露=完蛋
// ❌ MD5 → 彩虹表秒破
// ❌ MD5+salt → 虽然好点但MD5太快,GPU暴力破解
// ✅ BCrypt(推荐) — 自带salt + 可调慢速因子
String hash = BCrypt.hashpw(password, BCrypt.gensalt(12));
// 12 = 2^12次迭代,故意慢,让暴力破解不可行
// 每次hash结果不同(随机salt),但 verify 能验
boolean ok = BCrypt.checkpw(input, hash);
原则:① 永远不明文存密码 ② 用 BCrypt/Argon2(故意慢) ③ 密码不明文传输(HTTPS)④ 登录失败限流(防暴力破解)。
📱问题4:第三方登录(OAuth 2.0)
// OAuth 2.0 授权码模式(最安全)
// 1. 前端跳转到微信授权页(带 redirect_uri)
// 2. 用户点授权 → 微信回调 redirect_uri?code=xxx
// 3. 后端用 code 换 accessToken(后端到后端,安全)
// 4. 用 accessToken 拉用户信息(openid, nickname, avatar)
// 5. 本地绑定/创建用户,签发自己的 JWT
// 关键:code 换 token 必须在后端做
// 前端拿 code → 发给后端 → 后端请求微信 → 返回 JWT
用户系统核心:
① JWT 无状态适合分布式,Session 适合需要主动注销
② Token 刷新用双Token + Promise队列防竞态
③ 密码用 BCrypt(故意慢),永远不明文
④ 第三方登录用 OAuth 2.0 授权码模式(code换token在后端做)
生产场景 · 用户系统 · 全栈资料库