JWTJSON Web Token是一种常用于分布式、跨域的身份验证机制。与传统的 Session 认证不同JWT 是无状态Stateless的这意味着服务器不需要在数据库或内存中保存用户的登录状态所有的用户信息都加密或签名后存在客户端Token 内。一、发展历程1、版本一1.1、 核心优点1.1.1 开发极其简单开箱即用这是早期 Web 开发最常用的方案。像 Java 的 Tomcat、PHP、Node.js 等主流 Web 容器和框架原生就自带了 Session 管理机制。你不需要引入 Redis也不需要理解复杂的加密算法几行代码就能搞定登录验证。1.1.2 状态控制力极强与 Redis 架构类似因为 Session 完全由服务端掌控服务器可以非常轻松地实现强制某个用户下线直接调用session.invalidate()。实时修改用户的权限和状态。统计当前的在线人数。1.1.3 安全性相对较高敏感数据不出网用户的详细信息、权限列表都老老实实地呆在服务器内存里。返回给浏览器的只是一个随机的、不包含任何业务信息的字符串例如JSESSIONID。黑客拿到这个 ID 也没法解密出用户的隐私数据只能尝试 Session 劫持。1.2、 核心缺点致命痛点1.2.1 无法水平扩展致命伤无法支撑分布式集群这是该架构被现代互联网抛弃的最主要原因。场景如果你的系统做大后部署了 2 台服务器Server 1 和 Server 2前面加了一个负载均衡器Nginx。问题用户 A 登录时请求落在了 Server 1Server 1 的内存里创建了它的 Session。下一次用户 A 发起请求Nginx 把请求转发给了 Server 2。因为 Server 2 的内存里根本没有这个 Session用户 A 就会被莫名其妙地提示“请重新登录”。注虽然可以通过 Nginx 配置ip_hash会话粘滞或者做 Session 复制来解决但前者会导致负载不均衡后者会导致服务器之间同步数据造成巨大的网络风暴。1.2.2 内存开销大容易撑爆服务器加大 server 的开销Session 是存在服务器内存里的。如果你的系统突然遭遇流量暴增比如搞活动或者被刷接口10 万个用户同时涌入服务器内存就会被 10 万个 Session 对象塞满极易导致OOM内存溢出从而引发整台服务器挂机。1.2.3 服务器重启或挂机用户全部掉线因为 Session 存在内存中只要你重启 Web Server比如日常发布新版本、改 Bug 部署当前所有在线用户的登录状态都会瞬间清空所有人都必须重新登录用户体验非常差。2、版本二2.1 右图Session 复制Session Replication2.1.1 它是怎么工作的当系统引入了多台服务器集群部署后为了防止用户请求切换服务器导致掉线中间件如 Tomcat提供了一种自带的集群同步机制用户请求打到上面的“认证模块服务器 1”登录成功在服务器 1 的内存里创建了一个session。核心同步服务器 1 会通过网络组播Multicast或点对点通信把这个session对象实时拷贝/复制到下面的服务器 2 内存中。这样一来当用户的下一次请求被分配到下面的服务器时由于两边的内存数据一模一样用户依然保持登录状态。2.1.2 致命缺点为什么现在基本不用了虽然它解决了集群登录的问题但代价极其惨重网络风暴与带宽消耗想象一下如果有 4 台服务器每台服务器有人登录、修改数据时都要把 Session 同步给另外 3 台。随着服务器数量增加服务器之间为了同步 Session 会瞬间占满局域网带宽触发“网络风暴”。内存极度浪费每台服务器都要存储全量的用户 Session。如果有 100 万用户在线每台服务器的内存里都要存 100 万个 Session 对象集群的内存利用率极低。扩展上限低这种方案一般只能支撑 2~3 台服务器的微型集群服务器再多系统就会直接卡死。2.2 左图解耦/集中的 Session 块2.2.1 它是怎么工作的左图展示了一种优化思路Controller不再直接伸手去捞本地的 Session而是通过Service业务层或者一个独立的模块去统一读写右侧那个被圈出来的session区域。这通常代表了两种演进方向方向 A应用内 Session 抽离。在单体应用内部将状态管理从控制层剥离交给特定的服务或上下文去管理让 Controller 变得更干净只负责路由和参数解析。方向 B迈向“分布式 Session”的过渡期。把 Session 挪到 Web Server 的边缘。如果右边这个红圈的session进一步演进彻底脱离 Web Server 变成一个独立的第三方内存数据库它就变成了第三张图里看到的Redis 架构。3、版本三3.1、 核心优点3.1.1. 支持水平扩展无状态的 Web Server这是该架构最核心的优势。Web ServerController 和 Service 所在的服务自身变成了无状态的。如果用户 A 第一次请求到了服务器 1第二次请求被负载均衡到了服务器 2服务器 2 依然可以通过读取公共的 Redis 拿到用户 A 的登录状态。这解决了传统内存 Session 无法跨服务器共享的问题系统可以通过堆机器轻松实现水平扩展。3.1.2. 支持强控用户状态单点注销、实时封禁相比于纯粹的 JWT 方案这个架构最大的爽点在于服务端拥有绝对的控制权立即下线如果管理员想封禁用户 A或者用户 A 点击了“退出登录”服务器只需要直接从 Redis 中删掉userId对应的 Key。下一次用户请求时Redis 查不到数据用户立即失效。单端登录限制可以轻松实现“同一账号只能在一个设备登录”。当用户在设备 B 登录时去 Redis 里覆盖或删掉设备 A 的 Token/Key 即可。3.1.3. 数据读取性能极高因为引入了 Redis内存级数据库查询用户登录状态和基本信息Key-Value的时间复杂度是 $O(1)$通常在几毫秒内就能完成不会对后端的业务数据库如 MySQL造成并发压力。3.2、 核心缺点与痛点3.2.1 存在单点故障风险Redis 成为全盘关键从图中可以看到所有的验证流量最终都汇聚到了Redis。如果 Redis 挂了或者网络抖动连接不上整个 Web 系统的登录和权限校验功能将全线崩溃所有用户都会被强制下线或提示报错。解决办法在生产环境中Redis 绝对不能是单机必须部署为哨兵模式Sentinel或集群模式Cluster来保证高可用。3.2.2 增加服务器内存与运维成本随着系统在线用户Active Users的激增Redis 内存中存储的user对象会越来越多。如果用户量达到千万级需要消耗大量的内存硬件资源。需要额外维护一套 Redis 集群增加了系统架构的复杂度和运维成本。3.2.3 潜在的数据不一致问题如果用户的基本信息比如昵称、角色权限在数据库中被修改了你必须同时去更新或清除 Redis 中的user对象缓存。如果忘记更新就会导致 Redis 里存的是老数据俗称“缓存脏数据”用户看到的依然是旧信息。4、版本四这是一个非常现代化、标准的分布式微服务架构下的 SSO单点登录与 Token 认证流程。在这个架构中系统被拆分成了多个微服务如认证系统、购物车系统、订单系统。登录时User A 向“认证系统”发起认证成功后获得一个token。访问时前端使用 Pinia 等状态管理将 Token 存下并在后续请求别的微服务如购物车时带上 Token。核心拦截请求不会直接进入具体的业务微服务而是先经过一个网关/中间件Middleware/Interceptor。中间件负责统一拦截请求并远程调用RPC/HTTP“认证系统”的verify()方法来校验 Token 的合法性并进行鉴权。这是一个非常标准且规范的微服务鉴权方案。我们来拆解一下它的优缺点4.1、 核心优点4.1.1 业务逻辑与安全认证解耦高内聚、低耦合这是该架构最大的亮点。通过引入Middleware中间件/拦截器购物车系统、订单系统等业务微服务完全不需要编写任何关于身份验证和权限校验的代码。购物车系统只需要专注于做好“添加购物车”的业务。所有的认证、Token 校验、无 Token 阻断图中的 4.1、4.2 步骤都由中间件统一搞定了。未来如果认证逻辑要修改只需要改中间件或认证系统业务系统完全不用动。4.1.2 完美的单点登录SSO体验由于有一个统一的“认证系统”作为信任源User A 只要在认证系统登录过一次拿到 Token就可以拿着这个 Token 去访问购物车系统、订单系统等所有其他的微服务真正实现了“一次登录全网通行”。4.1.3 集中式权限控制强管控图中的步骤 4 提到中间件会远程调用认证系统进行 token 校验和鉴权。这意味着“认证系统”依然掌握着绝对的控制权。如果用户在后台被管理员取消了“查看购物车”的权限或者被封号认证系统在执行verify()时能立刻发现并拒绝权限变更能够实时生效。4.2、 核心缺点与性能瓶颈4.2.1 致命的性能瓶颈网络开销加倍RPC风暴风险这是该架构最常被诟病的地方。注意看图中的步骤 4“再远程调用认证系统进行 token 的校验和鉴权”。问题这意味着用户每一次请求购物车、每一次请求订单中间件都要额外发起一次跨网络的远程调用去问认证系统“这个 Token 对不对他有没有权限”后果如果有 1 万个高并发请求打向各个业务服务认证系统就要瞬间承受 1 万次verify()的网络请求。认证系统极易成为整个微服务集群的性能瓶颈。4.2.2 链路变长可用性降低单点故障隐患在分布式系统中链路越长出错的概率越高。如果“认证系统”因为高并发挂了或者网络出现抖动导致中间件无法调用verify()那么整个系统所有的微服务购物车、订单等都将无法正常工作因为中间件会把所有请求都卡死。4.2.3 架构复杂度明显增高正如底部批注的AOP切面编程、Dependence Injection依赖注入和Interceptor拦截器要在网关或中间件层面优雅地实现这一整套非阻塞的异步远程调用、异常处理、熔断降级比如认证系统挂了该怎么办需要高水平的架构设计和代码实现。4.3、 现代工业界的通用优化方案为了解决“每次都要远程调用认证系统导致性能差”的缺点现代微服务通常会结合JWT 机制进行如下改良改用 JWT 作为 Token认证系统生成的 Token 采用 JWT 格式并在 Payload 中带上用户权限。中间件网关本地校验中间件Middleware不再远程调用认证系统而是直接在本地使用公钥Public Key对 JWT 进行解密和验签。效果省去了步骤 4 的那次网络远程调用性能直接提升数倍同时认证系统挂了也不影响现有用户的访问。二、认证过程图一图二这两张图非常清晰地展示了基于FastAPIPython框架实现 JWT 认证的完整闭环。第一张图描绘的是“登录并生成 Token”的后端逻辑第二张图描绘的是“客户端携带 Token 请求受保护资源并由后端解密验签”的过程。我们将这两张图连接起来梳理出 JWT 完整的登录与认证生命周期1、阶段一登录授权生成 Token对应第一张图当用户在前端输入账号密码并点击登录时后端进入第一张图的流程[用户登录请求] ── login_for_access_token() ── 查库校验 ── 成功 ── create_access_token() ── 返回 Token1.1 身份验证查库客户端调用login_for_access_token()接口。后端通过MOCK_USERS_DB.get(form_data.username)根据前端传来的用户名去数据库这里模拟为内存字典中查找该用户。分支预测Failure失败若用户不存在或密码错误直接抛出异常通常是 401 Unauthorized。Success成功验证通过进入 Token 生成核心环节。1.2 核心代码实现create_access_token()登录成功后调用核心函数生成 Token图右侧绿色部分详细拆解了它的内部细节data.copy()为了不污染原始数据先拷贝一份浅副本通常包含用户的唯一标识例如{sub: user_dict[username]}sub代表 Subject即 Token 的主体。注意这里有一道面试题是浅拷贝和深拷贝data.update({exp: expire})计算过期时间例如当前时间往后推 15 分钟并将过期时间作为exp键值对注入到数据中。jwt.encode()签名加密这是最关键的一步该函数接收三个核心要素to_encode (claim/数据)包含sub和exp的字典。SECRET_KEY (密钥)只有后端知道的硬编码字符串用于防止篡改。algorithm (算法)指定签名算法如HS256。Token加密算法TokenHS256(base64(header) bases64(payload) secret)1.3 返回响应最终接口向客户端返回一个标准 JSON 对象{ access_token: eyJhbGciOi..., token_type: bearer }2、阶段二客户端存储与后续请求对应第二张图左侧客户端拿到 Token 后开始执行第二张图左侧的前端控制流2.1 页面跳转与持久化前端通过window.location.href 面板跳转到登录后的主页Dashboard。触发fetchUserData()函数在跳转或初始化时将拿到的 Token 存入浏览器的本地存储图中展示为localStorage.getItem(jwt_token)。2.2 发起受保护的 Ajax 请求前端进行条件判断如果localStorage中没有 Token直接重定向回login.html。如果有 Token则通过 Axios 发起异步请求访问受保护的获取当前登录用户信息的接口axios.get(${API_BASE_URL}/me)注意根据底部的难点批注这里使用了 FastAPI 的OAuth2PasswordBearer机制前端在发请求时Axios 会自动或由拦截器手动在请求头中附带Authorization: Bearer token_value。注OAuth2PasswordBearer 机制详细讲解3、阶段三服务端截获并反序列化鉴权对应第二张图右侧请求到达后端后进入第二张图红色的后端验证流[axios.get(/me)] ── read_users_me() ── Depends(get_current_user) ── jwt.decode() ── 返回用户对象3.1 依赖注入与拦截请求打到/me路由对应的函数read_users_me()。该函数定义了依赖项current_user: Annotated[User, Depends(get_current_user)]。这意味着在执行read_users_me内部逻辑之前FastAPI 会强行先去执行get_current_user()函数。3.2 核心验签与解密get_current_user()进入红色虚线框的get_current_user()代码实现细节提取 Token通过OAuth2PasswordBearer组件自动从 HTTP 请求头的Authorization中把 Token 字符串抽离出来。jwt.decode()反序列化后端使用之前相同的SECRET_KEY和ALGORITHM对传过来的 Token 进行逆向解密和验签。校验与异常处理如果 Token 被篡改过或已过期jwt.decode()会抛出异常触发raisecredentials_exception返回 401 错误提示 Could not validate credentials。如果验签通过从解密出来的 Payload 中提取出username即之前存入的sub。3.3 组装数据并返回如果username成功拿到后端将其反序列化并封装为统一的TokenData或直接查询出User对象。get_current_user()将获取到的用户对象返回给read_users_me()。read_users_me()顺理成章地拿到current_user并return给前端。前端 Axios 收到响应将当前登录成功的用户名渲染展示在网页上。4、总结这两张图完美地闭环了 JWT 的核心思想图一颁发执照后端用密钥把用户信息和过期时间塞进 Token高高兴兴发给前端。图二凭证通行前端存好 Token每次进门请求接口时亮出 Token。网关/依赖项在门口用同一个密钥解密。只要解密成功且没过期就证明你是合法用户直接放行并获取你的身份全程无需频繁去查询数据库。