摘要:大白话讲清 Session 和 JWT 分布式痛点,以及 Axios 请求拦截器如何自动携带 Token,理清 localStorage 和 Zustand 分工,整理开发高频踩坑点。
前言
做后台系统鉴权,一定会碰到两个知识点:Session‑Cookie、JWT,同时前端通过 Axios 拦截器统一处理 token 携带。很多同学搞不懂:为什么 JWT 更适合分布式?拦截器到底干了什么?localStorage 和 Zustand 存 token 有什么不一样?本文结合课堂笔记,用大白话讲透底层逻辑。
一、Session 与 JWT 的分布式差异
笔记原文:
javascript
sessionId -> 内存中 session会话对象 不太适合分布式
jwt 没有这个问题,任何一台服务器签发的token 都可以在任何一台服务器上 解码出来,JSON 对象
1. Session‑Cookie 模式
用户登录之后:
- 服务器 A 在本机内存保存 session 会话对象,里面记录当前是谁登录。
- 返回浏览器一个
sessionId,浏览器后续请求自动带上这个 id。 - 服务器拿到
sessionId,去本机内存查找会话,识别用户身份。
分布式场景下的致命问题 现在线上部署多台服务器 A、B 做负载均衡。
- 登录请求落到 A 机器,session 保存在 A 的内存。
- 下一次请求被转发到 B 机器。
- B 机器内存里没有这份 session 数据,直接判定用户未登录,登录状态丢失。
所以笔记写:不太适合分布式。 如果非要用 Session 做分布式,需要把 session 迁移到 Redis 共享存储,增加额外开发工作量。
2. JWT 如何解决分布式问题
JWT不在服务器存任何会话数据,用户身份直接打包在 token 字符串内部。
前提:集群中所有服务器必须使用同一套 secret 密钥。
- A 服务器登录签发 JWT 令牌。
- 用户携带 token 发起请求,流量转发到 B 服务器。
- B 服务器使用相同密钥直接解码 token,读出内部 JSON 用户对象。
不需要服务器之间共享会话存储,集群任意机器都可以识别凭证,天然适配分布式。
对比表格
表格
| 方案 | 登录信息存哪里 | 多服务器 (分布式) 问题 |
|---|---|---|
| SessionId | 服务器本机内存 | A 存的数据 B 看不到,需要 Redis 共享 session |
| JWT | token 字符串内部 | 所有机器共用密钥,任意机器都可以解码读取用户 JSON |
⚠️重要前提 "任意服务器都可以解码" 不是无条件的。全部后端实例 secret 密钥必须完全一样。 A 密钥
aaa,B 密钥bbb,B 完全解不开 A 生成的 token,签名校验直接报错。
补充:JWT 解码得到的 JSON 对象,就是 payload 载荷,存放{id:1,username:"admin"}这类用户身份信息。
一句话总结: session 会话存在单台机器内存,多服务器部署容易登录丢失;JWT 把用户 JSON 封装在 token 本身,密钥一致,集群任意服务器都能解析身份。
二、Axios 请求拦截器:自动携带 Token
什么是请求拦截器
笔记片段代码:
ini
//拦截每个请求 request, 使用一个配置
instance.interceptors.request.use(config => {
const token = localStorage.getItem('token');
})
instance是我们自己创建的 axios 实例。
instance.interceptors.request.use()就是请求拦截器。
每一次发送网络请求,请求还没有发给后端,先进入这个拦截器函数 ,我们在这里修改请求配置
config,处理完成之后才真正发送 http 请求。
好处:不用每一个接口都手动写带上 token 的逻辑,所有请求统一处理,消除重复代码。
config:请求配置对象,包含 url、headers 请求头、请求参数 method 等,我们主要修改config.headers往里面塞入 token 凭证。
localStorage.getItem('token')
ini
const token = localStorage.getItem('token');
localStorage属于浏览器本地持久存储。登录成功后后端返回的 JWT 字符串就存在这里。.getItem('token'):读取 key 为token的存储值,拿到 JWT 字符串。- 如果本地没有存储 token,拿到的值是
null。
⚠️上面只是笔记片段,缺少赋值 token、return config,无法直接运行。
完整可运行代码
javascript
import axios from "axios";
// 创建axios实例
const instance = axios.create({
baseURL: "/api",
});
// 请求拦截器
instance.interceptors.request.use((config) => {
// 1.从浏览器本地取出token
const token = localStorage.getItem("token");
// 2.token存在,挂载到请求头Authorization
if (token) {
// Bearer后面必须保留空格
config.headers.Authorization = `Bearer ${token}`;
}
// 3.必须return config,返回修改后的配置,请求才会真正发出
return config;
});
export default instance;
完整业务流程串联
- 登录接口调用成功,后端返回 JWT token。前端执行
localStorage.setItem('token', res.data.token),把 token 存入浏览器本地。 - 页面发起任意接口请求,先走请求拦截器
- 拦截器内部读取 localStorage 拿到 token,添加到请求头
Authorization: Bearer eyJxxxx... - return config,发送 http 请求到后端。
- 后端读取请求头,调用
jwt.verify()校验签名、过期时间,解码拿到用户身份。
localStorage 和 Zustand 的区别
很多新手会混淆两者作用:
localStorage:浏览器持久存储,刷新页面数据不会丢失,用来存 token 字符串。zustand:React 内存状态仓库,页面刷新直接清空,用来全局共享用户信息、登录状态。
标准业务流程:
登录成功 → localStorage 存 token,Zustand 存用户信息; 页面初始化的时候读取 localStorage 里面的 token,回填到 Zustand 全局状态,恢复登录态。
请求拦截器高频踩坑
- ❌忘记写
return config,请求直接卡住,不会发送出去。 - ❌不做 token 存在判断,token 为 null,请求头变成
Bearer null,后端鉴权报错。 - ❌
Bearer后面漏掉空格,请求头格式错误,JWT 校验失败。
✅一句话总结请求拦截器:统一拦截所有请求,从浏览器本地取出 token,自动挂载请求头,免去每个接口重复写 token 逻辑。
拓展:配套响应拦截器(处理 401 token 过期)
光有请求拦截不够,token 过期后端返回 401 未登录,需要响应拦截器统一捕获,清除凭证跳转登录页。
javascript
instance.interceptors.response.use(
(response) => {
// 简化返回,业务层不需要每次写 .data
return response.data;
},
(error) => {
// token过期或者未登录状态码401
if (error.response?.status === 401) {
localStorage.removeItem("token");
// 这里写路由跳转到登录页逻辑
}
return Promise.reject(error);
}
);
Axios 完整生命周期
arduino
页面调用axios发起请求
↓
【request请求拦截器】读取localStorage,挂载Authorization请求头 return config
↓
http请求发送到后端
↓
后端jwt.verify校验token,执行业务逻辑返回响应
↓
【response响应拦截器】处理返回数据,捕获401过期错误
↓
数据交给页面业务代码
总结
- Session 会话保存在服务器内存,多服务器分布式部署会登录失效,需要 Redis 解决;JWT 凭证放在 token 字符串,密钥一致集群任意机器都可以解析身份。
- Axios 请求拦截器,在请求发出之前统一修改请求头,自动带上 token,消除重复代码。
- localStorage 负责持久保存 token,Zustand 负责 React 组件全局状态共享,两者分工不同。
- 不要忘记 return config,Bearer 后面空格,做好 token 非空判断,是拦截器开发的三个关键点。