Skip to main content

登录

主流的登录实现主要有以下几种:

  • cookie + session
  • Token + 本地存储
  • SSO 单点登录
  • OAuth 第三方登录

这个应该是比较老牌的登录实现了

  1. 前端用一个 post 请求发送账号密码到后端
  2. 后端存储账号和密码(密码加密存储),然后生成一个 session 对象,保存 sessionId 和其他信息
  3. 将 sessionId 写入 cookie
  4. 前端再次请求后端接口时会携带 cookie,后端验证 cookie 中的 sessionId

缺点:

  • 服务器需要保存 session
  • sessionId 存放在 Cookie 中,容易遭受 CSRF 攻击
  • 多个域名难以共享 sessionId

jwt + 本地存储

JSON Web Token(jwt) 可以解决上一个登录方案的缺点,交由客户端保存状态

  1. 前端用一个 post 请求发送账号密码到后端
  2. 后端生成 token(携带用户的部分信息)并返回前端
  3. 前端保存 token 到本地存储(localStorage)
  4. 前端请求时手动将 token 带上,一般是将 token 挂在请求头上

缺点:

  • 服务器需要额外解析 token

SSO 单点登录

单点登录(Single Sign On),简称为 SSO,是比较流行的企业业务整合的解决方案之一。 SSO 的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统(来自百度百科)

单点登录的核心在于共享。需要有一个通用的服务(CAS)来承载这个登录功能,比如 blog.zhouweibin.top 和 code.zhouweibin.top 的登录都由 passport.zhouweibin.top 来实现

可以看到,只要在服务器 A 登录后,CAS 的 cookie 会保持时效,从而实现不同域名间的状态共享

OAuth 第三方登录

以 github 授权为例:

  1. 前端点击第三方登录,跳转到 github 的认证网页,询问授权
  2. 授权完成后,会重定向到页面 A,并且携带 code
  3. 前端再通过 code 和先前在 github 上注册的 appsecret 去请求 github,获取到 token 值
  4. 通过 token 值,再去请求 github 服务器获取部分用户信息

其实授权模式主要分为四种,上述 github 这种授权模式属于授权码模式,是最常用且安全的模式,其他的还有隐藏模式、密码模式、凭证模式等,可以参考 阮一峰 - OAuth 2.0 的四种方式

无感刷新(双 token)

面试题:token 过期了如何做到用户无感知地续期?

为兼顾安全与体验,常用双 token

  • accessToken:短有效期(如 15min),随请求发送,用于鉴权。
  • refreshToken:长有效期(如 7 天),仅用于换取新的 accessToken,安全要求更高(建议存 httpOnly cookie)。

流程:请求返回 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 鉴权与安全