fastapi:第二十八章:用jwt做用户身份验证
一,安装用到的库
在 FastAPI 中,使用 JWT(JSON Web Token)进行身份验证时,现在官方和社区最推荐、使用率最高的组合是:
-
PyJWT:负责生成(Encode)和解析(Decode)JWT 字符串。 -
passlib[bcrypt]:负责对用户密码进行哈希加盐存储和验证。 -
FastAPI 自带的 Security 模块:负责提取请求头中的 Bearer Token。
安装:
$ pip install PyJWT "passlib[bcrypt]"
二,代码
用到的库
import hashlib
from datetime import datetime, timedelta, timezone
from typing import Optional
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from pydantic import BaseModel
import jwt
import bcrypt
# --- 配置参数 ---
SECRET_KEY = "your-super-secret-key-change-me-1234567890" # 生产环境请使用安全的随机密钥
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
# 模拟数据库
USER_DB = {
"admin": {
"username": "admin",
"hashed_password": "$2b$12$vuryEKmyQS1V7ahHk5dTf.AD91zDS0ieNJLUhNfPMhE9tEmRAceWG",
}
}
# 规定 Token 的获取地址(FastAPI 会自动在 Swagger UI 中生成登录按钮)
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
def get_password_hash(password: str) -> str:
"""Hash a password using bcrypt"""
#
return bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt()).decode("utf-8")
def verify_password(plain_password: str, hashed: str) -> bool:
"""Verify a password against its hash"""
return bcrypt.checkpw(
plain_password.encode("utf-8"),
hashed.encode("utf-8"),
# hashed,
)
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
"""生成 JWT Token"""
to_encode = data.copy()
if expires_delta:
expire = datetime.now(timezone.utc) + expires_delta
else:
expire = datetime.now(timezone.utc) + timedelta(minutes=15)
# 注意:根据 JWT 规范,过期时间 exp 应该使用 UTC 时间戳
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
async def get_current_user(token: str = Depends(oauth2_scheme)):
"""依赖项:从请求头中解析并验证 Token,返回当前用户"""
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"Authorization": "Bearer"},
)
try:
# 解密 Token
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub")
print("username:", username)
if username is None:
raise credentials_exception
except jwt.PyJWTError:
raise credentials_exception
user = USER_DB.get(username)
if user is None: # 简写,实际应为 user is None
raise credentials_exception
return user
代码:
# app/api/account.py
from datetime import timedelta
from fastapi import APIRouter, Form, Depends, HTTPException
from fastapi.security import OAuth2PasswordRequestForm
from pydantic import BaseModel,Field, field_validator
from starlette import status
from app.core.jwt import get_password_hash, USER_DB, verify_password, ACCESS_TOKEN_EXPIRE_MINUTES, create_access_token, \
get_current_user
from app.utils.response import success
router = APIRouter(prefix="/account", tags=["登录验证"])
# --- 路由设计 ---
@router.post("/login")
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
"""登录接口,用以获取 Token"""
# 1. 初始化模拟数据(实际开发中密码是注册时哈希好的)
# USER_DB["admin"]["hashed_password"] = get_password_hash("secret123")
print("username:",form_data.username)
print("password:", form_data.password)
user = USER_DB.get(form_data.username)
print("user:", user)
is_verify = verify_password(form_data.password, user["hashed_password"])
print("is_verify:", is_verify)
if not user or not is_verify:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="用户名或密码错误",
headers={"WWW-Authenticate": "Bearer"},
)
# 2. 验证成功,发放 Token
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
data={"sub": user["username"]}, expires_delta=access_token_expires
)
return {"access_token": access_token, "token_type": "bearer"}
@router.get("/info")
async def read_users_me(current_user: dict = Depends(get_current_user)):
"""受保护的路由,只有携带有效 Token 才能访问"""
return {"username": current_user["username"], "message": "如果你能看到我,说明认证成功了!"}
三,测试效果:


浙公网安备 33010602011771号