登录
主流的登录实现主要有以下几种:
- cookie + session
- Token + 本地存储
- SSO 单点登录
- OAuth 第三方登录
cookie + session
这个应该是比较老牌的登录实现了
- 前端用一个 post 请求发送账号密码到后端
- 后端存储账号和密码(密码加密存储),然后生成一个 session 对象,保存 sessionId 和其他信息
- 将 sessionId 写入 cookie
- 前端再次请求后端接口时会携带 cookie,后端验证 cookie 中的 sessionId

缺点:
- 服务器需要保存 session
- sessionId 存放在 Cookie 中,容易遭受 CSRF 攻击
- 多个域名难以共享 sessionId
jwt + 本地存储
JSON Web Token(jwt) 可以解决上一个登录方案的缺点,交由客户端保存状态
- 前端用一个 post 请求发送账号密码到后端
- 后端生成 token(携带用户的部分信息)并返回前端
- 前端保存 token 到本地存储(localStorage)
- 前端请求时手动将 token 带上,一般是将 token 挂在请求头上

缺点:
- 服务器需要额外解析 token
SSO 单点登录
单点登录(Single Sign On),简称为 SSO,是比较流行的企业业务整合的解决方案之一。 SSO 的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统(来自百度百科)
单点登录的核心在于共享。需要有一个通用的服务(CAS)来承载这个登录功能,比如 blog.zhouweibin.top 和 code.zhouweibin.top 的登录都由 passport.zhouweibin.top 来实现

可以看到,只要在服务器 A 登录后,CAS 的 cookie 会保持时效,从而实现不同域名间的状态共享
OAuth 第三方登录
以 github 授权为例:
- 前端点击第三方登录,跳转到 github 的认证网页,询问授权
- 授权完成后,会重定向到页面 A,并且携带 code
- 前端再通过 code 和先前在 github 上注册的 appsecret 去请求 github,获取到 token 值
- 通过 token 值,再去请求 github 服务器获取部分用户信息

其实授权模式主要分为四种,上述 github 这种授权模式属于授权码模式,是最常用且安全的模式,其他的还有隐藏模式、密码模式、凭证模式等,可以参考 阮一峰 - OAuth 2.0 的四种方式
无感刷新(双 token)
面试题:token 过期了如何做到用户无感知地续期?
为兼顾安全与体验,常用双 token:
accessToken:短有效期(如 15min),随请求发送,用于鉴权。refreshToken:长有效期(如 7 天),仅用于换取新的accessToken,安全要求更高(建议存httpOnlycookie)。
流程:请求返回 401(accessToken 过期)→ 用 refreshToken 静默换新 token → 重发原请求,用户无感知。
let isRefreshing = false;
let pendingQueue = []; // 刷新期间挂起的请求
axios.interceptors.response.use(null, async (error) => {
const { response, config } = error;
if (response?.status !== 401) return Promise.reject(error);
if (isRefreshing) {
// 正在刷新,把请求挂起,等新 token
return new Promise((resolve) => {
pendingQueue.push((token) => {
config.headers.Authorization = `Bearer ${token}`;
resolve(axios(config));
});
});
}
isRefreshing = true;
try {
const { accessToken } = await refreshToken();
pendingQueue.forEach((cb) => cb(accessToken)); // 重放挂起请求
pendingQueue = [];
config.headers.Authorization = `Bearer ${accessToken}`;
return axios(config);
} catch (e) {
// refreshToken 也失效,跳登录
redirectToLogin();
return Promise.reject(e);
} finally {
isRefreshing = false;
}
});
要点:用 isRefreshing 标志位 + 请求队列,避免并发请求同时触发多次刷新。
多标签页登录态同步
面试题:一个标签页退出登录,其他标签页怎么同步?
storage事件:A 标签页改localStorage(写入/删除 token),其他标签页会收到window.addEventListener('storage'),据此同步登出/刷新。BroadcastChannel:同源标签页间广播消息,比 storage 事件更直接。
const channel = new BroadcastChannel("auth");
// 退出时广播
channel.postMessage({ type: "logout" });
// 其他标签页监听
channel.onmessage = (e) => {
if (e.data.type === "logout") redirectToLogin();
};
后端鉴权细节(JWT 结构、主动失效、RBAC)见 Node 鉴权与安全。