鉴权与安全
鉴权的标准组合拳:OAuth2 密码流拿令牌 + JWT 无状态校验 + 密码哈希存储。面试主线:令牌怎么发、怎么验、怎么保护密码。
整体流程
OAuth2 密码流 + JWT 无状态鉴权
- 登录接口签发 JWT,客户端后续请求带
Authorization: Bearer <token> - 服务端无状态校验:验签名、验过期、读负载,不需要查会话表
- JWT 三部分:header(算法)、payload(声明,含 exp)、signature(签名)
OAuth2PasswordBearer
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/token") # 声明令牌获取端点
@app.post("/token")
def login(form: OAuth2PasswordRequestForm = Depends()):
user = authenticate(form.username, form.password)
if not user:
raise HTTPException(401, "用户名或密码错误")
token = create_jwt({"sub": user.id})
return {"access_token": token, "token_type": "bearer"}OAuth2PasswordBearer:从请求头提取 Bearer 令牌,缺失返回 401OAuth2PasswordRequestForm:解析表单用户名密码- 令牌获取端点返回标准 OAuth2 响应格式,OpenAPI 文档自动生成“Authorize”按钮
JWT 签发与校验
import jwt # PyJWT 或 python-jose
SECRET_KEY = "..." # 从环境变量读, 别写代码里
def create_jwt(user_id: int) -> str:
return jwt.encode(
{"sub": str(user_id), "exp": datetime.now(timezone.utc) + timedelta(minutes=30)},
SECRET_KEY, algorithm="HS256")
def verify_jwt(token: str) -> int:
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
return payload["sub"]
except jwt.ExpiredSignatureError:
raise HTTPException(401, "令牌过期")
except jwt.InvalidTokenError:
raise HTTPException(401, "无效令牌")要点:过期时间放 exp(标准声明);algorithms 显式指定防算法混淆;SECRET_KEY 从环境变量读。生产可加 refresh_token 轮换(access 短、refresh 长),或用 RS256 非对称签名。
密码哈希
from passlib.context import CryptContext
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
def hash_password(pw: str) -> str:
return pwd_context.hash(pw)
def verify_password(pw: str, hashed: str) -> bool:
return pwd_context.verify(pw, hashed)- 绝不存明文、绝不自己写哈希。bcrypt 内置盐(salt),抗彩虹表
- 校验用
verify,比较由库处理(恒定时间比较防时序攻击) - 密码哈希和 JWT 签名密钥是两回事,分开管理
安全依赖组合
def get_current_user(token: str = Depends(oauth2_scheme),
db: Session = Depends(get_db)) -> User:
user_id = verify_jwt(token)
user = db.get(User, user_id)
if not user:
raise HTTPException(401, "用户不存在")
return user
@app.get("/me")
def me(user: User = Depends(get_current_user)): # 需要登录的接口
return user- 受保护接口声明
Depends(get_current_user),自动完成“取令牌、验签、查用户” - 角色权限:再包一层
require_role("admin")依赖 - 复用依赖树,鉴权逻辑写在依赖里,路由保持干净
面试追问
- JWT 怎么校验? 验签名(密钥)、验 exp、读负载。无状态,不需要服务端会话
- 密码怎么存? bcrypt 加盐哈希。passlib 处理,绝不存明文
- OAuth2PasswordBearer 干什么? 从 Authorization 头提取 Bearer 令牌,缺失返回 401。tokenUrl 关联文档
- access token 过期了怎么办? refresh token 换新;或让用户重新登录。无状态方案里撤销只能等过期或黑名单
- JWT 放哪安全? 前端存内存/HttpOnly Cookie(防 XSS 窃取)。localStorage 有 XSS 风险