JWT 简介
JWT(JSON Web Token)是一种开放的行业标准,定义于 RFC 7519,用于在各方之间安全地传输信息。该信息以 JSON 对象的形式存在,并通过数字签名确保其完整性和可信性。签名可使用 HMAC 算法配合密钥,或基于 RSA、ECDSA 的公私钥对实现。
从工程实现角度看,JWT 本质上是一个标准化、带防伪签名且具备有效期的声明字符串,适用于分布式环境下的身份认证与信息交换。
Header.Payload.Signature
设计初衷:解决什么问题?
理解 JWT 需要先认识 HTTP 协议的“无状态”特性所带来的挑战。传统认证方式依赖 Session-Cookie 模型:
- 用户登录后,服务器创建 Session 并将 Session ID 存入 Cookie 返回客户端;
- 后续请求中,客户端携带 Cookie,服务器根据 Session ID 查找对应数据以识别身份。
这种模式存在以下工程痛点:
- 服务端状态存储压力大:每个活跃用户的 Session 都需占用内存或数据库资源,随着用户量增长,服务器负载显著上升。
- 扩展性受限:在微服务或多节点架构下,若请求被分发到未保存该用户 Session 的服务器,则认证失败。虽可通过 Session 共享或粘连解决,但会增加系统复杂度。
- 跨域与多终端支持弱:Cookie 在跨域场景及非浏览器客户端(如移动 App、IoT 设备)中使用受限,难以满足现代应用的多样化需求。
为此,JWT 提出一种无状态、可移植的身份验证机制,将身份信息和有效期等状态从服务端转移到客户端,由客户端在每次请求中自行携带,服务端仅做校验即可完成认证。
xxxxx.yyyyy.zzzzz
技术结构解析:JWT 的三段式构成
一个 JWT 字符串由三部分组成,依次为 Header、Payload 和 Signature,各部分通过点号(.)连接:
<Header>.<Payload>.<Signature>
第一部分:Header(头部)
Header 是一个 JSON 对象,包含关于令牌本身的元数据。
常见字段包括:
typ (Type)
- typ:表示令牌类型,固定值为 "JWT"。
- alg:指定签名所用的加密算法,如 HS256(HMAC-SHA256)、RS256(RSA-SHA256),此字段决定接收方如何验证签名。
alg (Algorithm)
生成过程:将 Header JSON 对象进行 Base64Url 编码,形成 JWT 的第一段。Base64Url 是 URL 安全版本的 Base64 编码,避免出现 +、/、= 等特殊字符影响传输。
示例原始内容:
{
"alg": "HS256",
"typ": "JWT"
}
编码后结果:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
第二部分:Payload(载荷)
Payload 包含一组称为“声明(Claims)”的键值对,描述了用户信息及相关元数据。
声明分为三种类型:
- Registered Claims(注册声明):预定义的标准字段,推荐使用以提升互操作性。
iss (Issuer) - iss:签发者(Issuer)
sub (Subject)
aud (Audience)
exp (Expiration Time)
nbf (Not Before)
iat (Issued At)
生成过程:将 Payload JSON 对象进行 Base64Url 编码,得到 JWT 的第二段。
示例原始内容:
{
"sub": "1234567890",
"name": "John Doe",
"role": "admin",
"iat": 1516239022,
"exp": 1516242622
}
编码后结果:eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwicm9sZSI6ImFkbWluIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTYyNDI2MjJ9
第三部分:Signature(签名)
Signature 的核心作用是防止数据篡改,确保 JWT 的完整性与真实性。
生成方式如下:
- 将已编码的 Header 和 Payload 用点号拼接成字符串:
encodedHeader + '.' + encodedPayload; - 使用 Header 中指定的算法(如 HS256)对该字符串进行签名,结合密钥(HMAC)或私钥(RSA/ECDSA)生成签名值;
- 将签名结果也进行 Base64Url 编码,作为第三段附加到 JWT 后。
最终,接收方可以通过相同方式重新计算签名并与原 Signature 比对,验证 JWT 是否被修改。
验证令牌的真实性(Authenticity)和完整性(Integrity),确保其在传输过程中未被篡改,且确实由可信的签发方生成。
签名生成流程
- 对 Header 进行 Base64Url 编码。
- 对 Payload 进行 Base64Url 编码。
- 使用点号(.)连接上述两部分,形成字符串:
base64UrlEncode(header) + "." + base64UrlEncode(payload)。 - 依据 Header 中声明的 alg 算法,结合服务器私有的密钥(Secret),对该字符串进行签名运算。
以 HS256 算法为例:
Signature = HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), your-256-bit-secret )
将计算出的签名再次进行 Base64Url 编码,即构成 JWT 的第三部分——Signature。
核心工作流
认证阶段(Authentication)
- 客户端向服务器的
/login接口提交用户名与密码。 - 服务器校验凭证有效性。
- 验证通过后,服务器创建一个包含用户标识(如 user_id)和过期时间(exp)的 Payload,并使用私钥对数据签名,生成完整的 JWT。
- 该 JWT 被返回给客户端。
授权阶段(Authorization)
- 客户端通常将接收到的 JWT 存储于 localStorage 或 sessionStorage 中。
- 当请求受保护的 API 时,客户端在 HTTP 请求头的 Authorization 字段中携带 JWT,格式如下:
Bearer <token>
Authorization: Bearer eyJhbGciOiJIUzI1Ni...
- 服务器接收到请求后,首先提取以下请求头中的令牌信息:
Authorization
- 服务器不会直接信任 Payload 内容,而是执行签名验证:
- 读取 JWT 的 Header 和 Payload 部分。
- 使用与签发时相同的算法和密钥,重新计算签名。
- 将新生成的签名与 JWT 第三部分(原始 Signature)进行严格比对。
- 若两者一致,则证明令牌完整且来源可信。
- 服务器必须检查 Payload 中的 exp 字段,确认令牌尚未过期。
- 所有验证均通过后,才可信任其中的信息(如 sub 字段中的 user_id),并继续处理业务逻辑。
工程实践中的权衡分析
优势
无状态性与高可扩展性:
? 服务器无需维护会话状态,显著降低内存开销,天然适配分布式系统与微服务架构。
自包含性:
? 用户相关信息可嵌入载荷中,减少频繁查询数据库的需求。
解耦与跨平台支持:
? 认证逻辑独立,适用于多种客户端类型(Web、移动端、IoT设备)及跨域通信场景。
潜在风险与安全建议
不可撤销性问题:
? JWT 一旦签发,在有效期内始终有效。若发生泄露,攻击者可在过期前持续使用。
解决方案包括:设置较短有效期、引入刷新令牌(Refresh Token)机制、或建立黑名单机制(但此方式违背了无状态设计原则)。
数据明文暴露:
? Payload 仅为 Base64Url 编码,不具备加密功能。
任何敏感信息都严禁存放在 Payload 中。
令牌体积膨胀:
? 若在 Payload 中写入过多数据,会导致 JWT 字符串过长,增加每次 HTTP 请求的传输负担。
密钥安全管理:
? 签名所用的密钥是整个安全体系的核心,
必须严格保护,绝不能暴露于客户端或日志中。
同时需避免使用不安全的算法,例如 alg: "none"。
FastAPI 实现方案
1. 核心依赖库说明
明确开发所需的技术组件:- FastAPI:现代 Python Web 框架,支持异步、自动文档生成。
- python-jose:实现 JOSE(JavaScript Object Signing and Encryption)标准的库,被 FastAPI 官方推荐用于 JWT 处理,相比 PyJWT 支持更广泛的加密算法。
- passlib:专注于密码哈希与验证的安全库。
工程红线:数据库中禁止存储明文密码。
=> 推荐使用 bcrypt 算法进行密码哈希。
2. 系统架构设计思路
将认证功能划分为三个职责清晰、协同工作的模块:- 密码安全模块 (security.py):
专注实现密码的哈希生成与比对,保持功能单一。 - 认证核心模块 (auth.py):
承担 JWT 的生成、解析与验证任务,是认证体系的核心中枢。 - API 路由模块 (main.py):
提供公开的登录接口以及受 JWT 保护的业务端点。
采用分层结构提升代码的内聚性,降低模块间耦合度,便于单元测试与后续功能拓展。
3. 工程化实施步骤(示例)
步骤一:配置管理初始化
应避免硬编码敏感信息(如密钥、算法等),需集中管理配置项。
config.py
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
# JWT 配置项
SECRET_KEY: str = "a_very_secret_and_long_string_for_jwt" # 在生产环境中应通过环境变量设置
ALGORITHM: str = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES: int = 30 # 设置访问令牌的过期时间为30分钟
class Config:
env_file = ".env" # 支持从 .env 文件中加载配置,实现灵活覆盖
settings = Settings()
第二步:密码处理模块设计
该模块职责单一,专注于密码的相关操作,包括加密、校验等核心功能。
第三步:JWT 令牌的生成与验证逻辑实现
以下是 auth.py 文件中的完整实现代码:
from datetime import datetime, timedelta, timezone
from typing import Optional
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from jose import JWTError, jwt
from pydantic import BaseModel
from .config import settings
# 定义用于解析令牌数据的 Pydantic 模型
class TokenData(BaseModel):
username: Optional[str] = None
# 初始化 OAuth2 密码流模式,指定获取令牌的接口路径
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/auth/token")
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str:
"""
生成 JWT 访问令牌
:param data: 要编码进令牌的数据(即 payload 内容)
:param expires_delta: 自定义令牌有效期,若未提供则使用默认值
:return: 编码后的 JWT 字符串
"""
to_encode = data.copy()
# 确定过期时间点
if expires_delta:
expire = datetime.now(timezone.utc) + expires_delta
else:
expire = datetime.now(timezone.utc) + timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
# 添加标准声明 'exp' 表示过期时间
to_encode.update({"exp": expire})
# 使用指定密钥和算法对数据进行 JWT 编码
encoded_jwt = jwt.encode(
claims=to_encode,
key=settings.SECRET_KEY,
algorithm=settings.ALGORITHM
)
return encoded_jwt
def get_current_user(token: str = Depends(oauth2_scheme)): # -> User:
"""
解析并验证传入的 JWT 令牌,提取用户信息
此函数作为 FastAPI 的依赖项,可用于保护任意需要认证的路由
"""
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
# 尝试解码令牌
payload = jwt.decode(
token,
settings.SECRET_KEY,
algorithms=[settings.ALGORITHM]
)
# 提取主题字段 'sub',通常为用户名
username: str = payload.get("sub")
if username is None:
raise credentials_exception
# 使用模型校验结构有效性
token_data = TokenData(username=username)
# 步骤四:整合模块并构建 API 接口
将认证、密码处理与路由逻辑结合,实现完整的接口服务。
main.py
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import OAuth2PasswordRequestForm
from . import auth, password_utils
app = FastAPI()
# 公共访问接口:用户登录并获取访问令牌
@app.post("/api/auth/token")
def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
# 实际项目中应通过数据库查询用户信息
# user = get_user_from_db(username=form_data.username)
# if not user:
# raise HTTPException(...)
# 演示环境下的模拟验证逻辑
# 假定仅存在一个用户名为 'admin' 的用户,密码明文为 'password'
stored_hashed_password = password_utils.get_password_hash("password")
is_valid = password_utils.verify_password(form_data.password, stored_hashed_password)
if not (form_data.username == "admin" and is_valid):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"}
)
# 构造 JWT 载荷数据,'sub' 为标准注册声明,通常存储用户唯一标识
token_payload = {"sub": form_data.username}
# 调用工具函数生成 JWT 字符串
access_token = auth.create_access_token(data=token_payload)
return {"access_token": access_token, "token_type": "bearer"}
# 受权限控制的接口示例一:获取当前用户信息
@app.get("/api/users/me")
def read_users_me(current_user: auth.TokenData = Depends(auth.get_current_user)):
"""
返回已认证用户的详细信息。
FastAPI 自动完成以下流程:
1. 解析请求头中的 Authorization 字段
2. 执行 get_current_user 依赖项进行身份校验
3. 若校验失败,自动抛出 401 错误,后续代码不执行
4. 若成功,则将解析出的用户数据注入到 current_user 参数
"""
return {"username": current_user.username}
# 受权限控制的接口示例二:管理员创建文章
@app.post("/api/admin/posts")
def create_new_post(current_user: auth.TokenData = Depends(auth.get_current_user)):
# 只有成功通过 auth.get_current_user 验证的请求才能进入此逻辑
# 后续可在此添加操作 Markdown 文件等业务逻辑
return {"message": f"Hello {current_user.username}, you are authorized to create a post."}
Header.Payload.Signature
# 总结与关键点回顾
**JWT 令牌的生成方式**
使用 `create_access_token` 函数来创建安全令牌。
输入参数为一个字典,表示 JWT 中的“声明”(claims)。其中最重要的自定义声明是 `sub`,用于存放用户的唯一标识(如用户名或 ID)。
该函数在内部会自动补充标准字段,例如过期时间(exp)、签发时间(iat)等,并使用预设密钥和算法对数据进行签名,最终返回编码后的 JWT 字符串。
注册声明
exp 字段用于设定令牌的有效期限,控制其生命周期。
随后,调用 jose.jwt.encode 方法,传入以下参数:
- 载荷(payload)
- 密钥(key)
- 签名算法
由此生成最终的 JWT 字符串。
实现思路
分层解耦
将密码处理、身份认证与路由控制等逻辑进行分离,提升代码模块化程度,使整体结构更加清晰易维护。
依赖注入
借助 FastAPI 提供的 Depends 机制,构建了一个可复用的 get_current_user 依赖项。在需要用户认证的接口中,仅需在函数参数中声明该依赖,即可自动完成令牌解析与用户验证流程。
配置驱动
将密钥、过期时间、加密算法等敏感或易变的配置项移至外部配置文件中管理,既提高了安全性,也增强了系统在不同环境下的适应能力。
安全第一
强制采用密码哈希存储机制,防止明文密码泄露;同时对令牌验证过程中的各类异常情况(如签名错误、已过期等)进行严格处理,保障系统的安全可靠性。


雷达卡


京公网安备 11010802022788号







