bigywater.tech
Flask · 109

JWT 登录认证

JWT 里到底有什么?验证要注意哪些参数?alg=none 攻击在现在的库里还有效吗?令牌发出去之后怎么收回权限?这篇用 PyJWT 把每一步都跑一遍。

明文
JWT 的 payload 不需要密钥就能读出
InvalidAlgorithmError
alg=none 的伪造令牌在 algorithms 白名单下的结果
role = user
数据库里角色改成 banned 之后,旧令牌仍然报告的角色

JWT 的结构

实测 · 本机执行输出
  令牌: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwicm9sZSI… (长度 160)
  header   : {"alg":"HS256","typ":"JWT"}
  payload  : {"sub":"1","role":"user","iat":1790917619,"exp":1790918519}
  signature: 43 个字符的 base64url(HMAC-SHA256 的 32 字节)
  不验证签名也能读出 payload: user

上一篇:API Token 认证;下一篇:Refresh Token。

验证与 claims

实测 · 本机执行输出
  algorithms=["HS256"]               → 通过 sub=1
  不传 algorithms                      → DecodeError: It is required that you pass in a value for the "algorithms" argument 
  错误的密钥                              → InvalidSignatureError: Signature verification failed
  exp 已过去 10 秒                       → ExpiredSignatureError: Signature has expired
  exp 过去 10 秒 + leeway=30            → 通过 sub=1
  nbf 在 60 秒之后                       → ImmatureSignatureError: The token is not yet valid (nbf)
  没有 exp(默认不要求)                      → 通过 sub=1
  options={"require": ["exp"]} 时没有 exp → MissingRequiredClaimError: Token is missing the "exp" claim
  要求 aud 但令牌没有 aud                   → MissingRequiredClaimError: Token is missing the "aud" claim

经典攻击

实测 · 本机执行输出
  alg=none 的伪造令牌                     → InvalidAlgorithmError: The specified alg value is not allowed
  改 role 为 admin、沿用旧签名               → InvalidSignatureError: Signature verification failed
  (但如果有人关掉了 verify_signature,伪造的令牌就会被接受——永远不要这样用)

HS256 与 RS256

实测 · 本机执行输出
  RS256 令牌长度 418(HS256 是 160)
  公钥验证 RS256 令牌                      → 通过 sub=1
  算法混淆:把公钥当 HS256 密钥来签               → InvalidKeyError: The specified key is an asymmetric key or x509 certificate and should 
  验证方同时允许 RS256 与 HS256(用公钥)         → InvalidKeyError: The specified key is an asymmetric key or x509 certificate and should 

RS256 的实验需要 cryptography 包(我先装上了 50.0.2,没装之前这一节无法运行)。

放进 Flask

实测 · 本机执行输出
  错误密码 → 401
  登录 → 200 令牌长度 160
  用令牌访问 /me → 200 {'role': 'user', 'sub': '1'}
  数据库里把角色改成 banned 之后,旧令牌仍然报告 role = user (JWT 是自包含的)

分步演示

JWT 登录认证
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Python 3.14.7 / Flask 3.1.3 / Werkzeug 3.1.9 / Jinja2 3.1.6 / SQLAlchemy 2.1.1 / gunicorn 26.2.0(都装在自己的虚拟环境里);大多数实验用 Flask 的 test_client(直接调用 WSGI 应用,不经过网络),需要真实 HTTP 时另起 gunicorn 或开发服务器(端口 9600 以上,本机 127.0.0.1);计时用 time.perf_counter,数字每次运行会有小幅抖动。PyJWT 2.15.1、cryptography 50.0.2
    • 没有验证:ES256 与 EdDSA、JWK 与 kid 的密钥轮换、jwks_uri 的远程密钥获取、JWE(加密的令牌)、浏览器端存放 JWT 的风险、Flask-JWT-Extended 的封装行为
    京ICP备2026067502号-1京公网安备11010502063487号