首页 / 知识库 / python服务端进阶 / 解析用户登录场景案例

04 - 路线 B:Token + JWT(客户端拿通行证)

第二条路线,思路和 Session 正好反过来

Session 是”服务端记账本,客户端拿个空号码”; Token/JWT 是”服务端不记账本,把’你是谁’直接写进凭证里,再盖个防伪章”。

换个类比:从”手环”到”带防伪的通行证”

Session 那套,工作人员每次都要翻登记本才知道你是谁。现在换一种玩法:

你办完卡(认证通过):
   前台直接发给你一张通行证,上面白纸黑字写着:
   ┌────────────────────────────┐
   │ 姓名:张三                   │
   │ 等级:VIP                    │
   │ 有效期至:18:00              │
   │ 防伪钢印:🔏(前台盖的)      │
   └────────────────────────────┘

你之后进任何区域:
   你:(出示通行证)
   工作人员:看一眼——张三、VIP、没过期,
             再验一下钢印是不是真的 🔏 → 是真的,放行 ✅
             (全程不用翻任何登记本!)

关键区别:

  • 信息直接写在证件上,工作人员一看就知道你是谁,不用查后台账本
  • 防伪靠钢印(签名):这个印章别人伪造不出来,所以证件内容改不了、也造不了假。
  • 前台发完这张证,自己什么都不记——这就是所谓”无状态(stateless)”。

这张”带防伪章、自己就说明了一切”的通行证,就是 JWT(JSON Web Token)

JWT 长什么样:三段式结构

JWT 就是一串用两个点隔开的字符串:xxxxx.yyyyy.zzzzz

eyJhbGciOiJIUzI1NiJ9  .  eyJzdWIiOiLlvKDkuIkiLCJleHAiOjE3MH0  .  SflKxwRJSMeKKF2QT4
─────────┬─────────       ─────────────┬─────────────           ────────┬────────
      Header                        Payload                          Signature
     (头部)                      (载荷/内容)                       (签名/防伪章)
   用了什么算法                  真正的数据:你是谁、                 用密钥算出来的防伪章,
                                什么时候过期                        改了内容就对不上
部分装什么类比
Header用了哪种签名算法证件用的什么防伪工艺
Payload你是谁(用户 ID)、角色、过期时间 exp证件上印的姓名、等级、有效期
Signature密钥对前两段算出来的签名那个别人伪造不出来的防伪钢印

极其重要的一个认知:Header 和 Payload 只是做了 Base64 编码,不是加密!任何人都能解开看到里面的内容。

Payload 里写着"张三、VIP" → 谁都能看到(所以别往里塞密码、身份证号等敏感信息)
但谁都改不了 → 因为一改内容,签名就对不上,服务端一验就发现是假的

一句话:JWT 防篡改,但不防偷看。它保证”内容没被改过”,但不保证”内容保密”。

服务端怎么验这张证:只验签名,不查库

这是 JWT 最大的特点。服务端收到 token 后:

收到 token

   ① 把它拆成 Header . Payload . Signature 三段

   ② 用自己的密钥,对 Header.Payload 重新算一遍签名

   ③ 算出来的签名 == token 里带的 Signature 吗?
   │     ├─ 不相等 → 内容被篡改过 / 是伪造的 → 拒绝 ❌
   │     └─ 相等  → 内容可信,继续

   ④ 看 Payload 里的 exp 过期了没?
   │     ├─ 过期 → 拒绝 ❌
   │     └─ 没过期 → 放行,直接从 Payload 拿到"这是张三" ✅

   全程没有查任何数据库/Redis!

注意第 ④ 步后面那句话:服务端确认签名有效后,直接从 token 内容里读出”你是张三”,完全不需要查库。这就是无状态——服务端不保存任何登录状态。

完整流程图

① 登录
   客户端 ──POST /login (张三/密码)──▶ 服务端
                                        │ 认证通过
                                        │ 把 {user: 张三, exp: 18:00} 用密钥签名
                                        │ 生成 JWT: eyJ...(自己不记任何东西)
   客户端 ◀──── 返回 { "token": "eyJ..." } ──── 服务端

        └─ 客户端自己保存这个 token(比如存 localStorage)

② 之后每次请求(前端要手动带上)
   客户端 ──GET /my/orders  Header: Authorization: Bearer eyJ...──▶ 服务端
                                                                   │ 验签名 ✅
                                                                   │ 看过期 ✅
                                                                   │ 从内容读出:是张三
   客户端 ◀────────────────── 张三的订单 ──────────────────────

对比第 03 章 Session 的图,两个关键差异:

  1. 登录响应:Session 是 Set-Cookie(浏览器自动管);JWT 是把 token 放在响应体里返回,前端自己保存、自己每次手动加到请求头
  2. 验证阶段:Session 要去 Redis 查账本;JWT 只在本地验签名,不查任何存储

用几行代码点透

登录时签发 token(服务端不存任何东西):

@app.post("/login")
def login(form: LoginForm):
    user = authenticate(form.username, form.password)   # 认证
    if not user:
        raise HTTPException(401, "用户名或密码错误")

    token = jwt.encode(                                  # 把信息+过期时间签成一张证
        {"sub": user.id, "exp": now + timedelta(hours=2)},
        SECRET_KEY, algorithm="HS256",
    )
    return {"token": token}                              # 发证,服务端什么都不记

访问时验证 token(不查库):

def get_current_user(token: str = Header(...)):
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])  # 验签+验过期
    except JWTError:
        raise HTTPException(401, "token 无效或已过期")
    return payload["sub"]                                # 直接从证里读出你是谁

对比第 03 章:Session 版的 get_current_userredis.get(...) 查账本;JWT 版只是 jwt.decode(...) 验个签名。少了一次存储查询,这就是无状态带来的性能与扩展红利。

优点很诱人,但请记住这个伏笔

JWT 的优点直接来自”服务端不记账本”:

  • 天生适合多服务器 / 微服务:任何一台服务器只要有密钥就能独立验证,不需要共享的 Session 存储。
  • 少一次查询:验签在本地完成,省掉一次 Redis/DB 访问。
  • 跨域、跨端友好:token 放请求头里,App、小程序、第三方服务都好用,不依赖 Cookie。

但是——成也无状态,败也无状态。请牢牢记住这个伏笔:

既然服务端”不记账本”,那它也就没有一个开关能主动作废一张已经发出去的 token。证已经发到用户手上了,在它自己过期之前,服务端拦不住它。

这就直接导致了:JWT 的”退出登录”是个大难题。 详见第 07 章。

小结

  • JWT 方案 = 把”你是谁”直接写进凭证 + 用密钥签名防伪 + 服务端不记账本
  • JWT 三段:Header(算法)、Payload(你是谁、过期时间)、Signature(防伪签名)。
  • Payload 只编码不加密:防篡改,但不防偷看,别往里放敏感信息
  • 服务端验证只需”验签名 + 验过期”,不查任何存储,这带来极好的扩展性。
  • 代价埋在无状态里:服务端无法主动作废已签发的 token,退出登录很棘手(第 07 章)

两条路线都讲完了。下一章我们把它们摆在一起做个正面对比,帮你在实际项目里做出选择。


上一章 ← 03 - 路线 A:Session + Cookie | 下一章 → 05 - 两条路线怎么选 | 回到 README 目录