site logo

Marico's space

面向 AI 工程师的 FastAPI(第 6 期):JWT 认证

编程技术 2026-06-16 11:27:35 14

上期聊了认证(Authentication)和授权(Authorization)的概念区别,踩了几个坑才搞清楚这两货到底有啥不同。光看概念不过瘾,今天来点实操——手把手在 FastAPI 里搭一套完整的 JWT 认证系统。

用微信、支付宝、抖音的时候,你输入账号密码,系统验证身份,然后就能访问各种功能了。但这背后到底怎么实现的?

这篇文章来解决这个问题。

为什么需要认证系统?

假如你在做一个 AI 辅助学习平台。没有认证的话:

  • 谁都能查看任意用户的个人信息
  • 谁都能看到其他同学的学习进度
  • 谁都能修改别人的数据

这显然是个安全隐患。应用需要一种机制:

  1. 验证用户身份
  2. 保护敏感资源
  3. 让用户保持登录状态

JWT 认证就是干这个的。

JWT 是什么?

JWT 是 JSON Web Token(JSON 网络令牌)的缩写。

简单说,JWT 是一个安全令牌,里面包含了用户信息。用户不需要每次请求都传用户名密码,而是传一个 token 就够了。

典型流程:

注册用户 ↓ 登录 ↓
验证凭证 ↓
生成 JWT 令牌 ↓
访问受保护接口

安装依赖包

pip install python-jose passlib[bcrypt]

用到的两个库:

  • passlib:处理密码哈希
  • python-jose:生成和验证 JWT 令牌

第一步:密码哈希

明文存储密码是极其危险的。千万别这么干:

users = { "张伟": "password123"
}

万一数据库被攻破,所有用户的密码就暴露了。正确做法是存哈希值。

创建密码哈希器

from passlib.context import CryptContext pwd_context = CryptContext( schemes=["bcrypt"], deprecated="auto"
)

什么是 CryptContext?

是密码哈希算法的管理器。这里用 指定使用 bcrypt 哈希算法。

哈希密码

hashed_password = pwd_context.hash("password123") print(hashed_password)

输出:

$2b$12$.....

可以看到原始密码已经看不到了。

验证密码

用户登录时验证:

pwd_context.verify( "password123", hashed_password
)

返回:

True

这样就能验证密码正确性,而不需要存储明文。

第二步:用户注册

写一个简单的注册接口:

from fastapi import FastAPI app = FastAPI() users = {} @app.post("/register")
def register(username: str, password: str): hashed_password = pwd_context.hash(password) users[username] = hashed_password return {"message": "用户注册成功"}

流程是这样的:用户提交用户名和密码 → 密码被哈希 → 哈希值存入数据库而非原始密码。

第三步:用户登录

验证凭证:

@app.post("/login")
def login(username: str, password: str): stored_password = users.get(username) if not stored_password: return {"message": "用户不存在"} if not pwd_context.verify(password, stored_password): return {"message": "凭证无效"} return {"message": "登录成功"}

此时用户可以正常登录了。但每次请求还是得传用户名密码,JWT 要解决的就是这个问题。

第四步:生成 JWT 令牌

from jose import jwt
from datetime import datetime, timedelta SECRET_KEY = "mysecretkey" ALGORITHM = "HS256"

为什么需要密钥?

密钥用来给令牌签名。如果有人篡改了令牌内容,签名就会失效。

生成令牌函数

def create_access_token(data: dict): to_encode = data.copy() expire = datetime.utcnow() + timedelta(minutes=30) to_encode.update({"exp": expire}) encoded_jwt = jwt.encode( to_encode, SECRET_KEY, algorithm=ALGORITHM ) return encoded_jwt

函数干了四件事:复制用户数据 → 添加过期时间 → 创建带签名的 JWT → 返回令牌。

第五步:登录时生成令牌

@app.post("/login")
def login(username: str, password: str): stored_password = users.get(username) if not stored_password: return {"message": "用户不存在"} if not pwd_context.verify(password, stored_password): return {"message": "凭证无效"} token = create_access_token( {"sub": username} ) return { "access_token": token, "token_type": "bearer" }

登录成功后返回:

{ "access_token": "eyJhbGciOiJIUzI1NiIs...", "token_type": "bearer"
}

第六步:受保护的接口

@app.get("/profile")
def get_profile(): return { "message": "受保护的个人资料数据" }

现在这个接口任何人都能访问。生产环境中,FastAPI 需要在用户访问前验证 JWT 令牌。完整的接口保护逻辑下期再讲。

目前先搞清楚这几个核心流程:注册、密码哈希、密码验证、JWT 生成。这些是所有认证系统的基础。

认证流程回顾

注册用户 ↓
哈希密码 ↓
存储哈希值 ↓ 登录 ↓
验证密码 ↓
生成 JWT ↓
访问受保护接口

写在最后

今天搭了一套 JWT 认证的核心组件:用户注册、密码哈希、密码验证、JWT 令牌生成。用户现在可以注册、登录、获取签名令牌。

但生成令牌只是故事的一半。下一步是学会验证令牌、用 FastAPI 依赖注入保护接口。

下期来实现基于 JWT 的接口保护,然后开始聊基于角色的访问控制(RBAC)。