title: FastAPI安全认证:从密码到令牌的魔法之旅
date: 2025/06/02 13:24:43
updated: 2025/06/02 13:24:43
author: cmdragon
excerpt:
在FastAPI中实现OAuth2密码流程的认证机制。通过创建令牌端点,用户可以使用用户名和密码获取JWT访问令牌。代码示例展示了如何使用CryptContext
进行密码哈希处理,生成和验证JWT令牌,并实现安全路由保护。此外,还提供了JWT令牌的结构解析、常见报错解决方案以及安全增强建议,如使用HTTPS传输令牌和从环境变量读取密钥。最后,通过课后Quiz巩固了关键概念。
categories:
tags:
扫描二维码
关注或者微信搜一搜:编程智域 前端至全栈交流与成长
探索数千个预构建的 AI 应用,开启你的下一个伟大创意:https://tools.cmdragon.cn/
(注:根据写作规范要求,章节编号从第一章开始编排)
OAuth2密码流程(Password Grant)是直接通过用户名密码获取访问令牌的认证方式。类比演唱会验票流程:用户先到售票处(令牌端点)用身份证(凭证)换取门票(令牌),之后凭门票入场(访问资源)。
流程步骤:
/token
端点from fastapi import APIRouter, Depends, HTTPException, status
from pydantic import BaseModel
from datetime import datetime, timedelta
from jose import JWTError, jwt
from passlib.context import CryptContext
router = APIRouter(tags=["Authentication"])
# 密码哈希配置(使用bcrypt算法)
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
# JWT配置(实际项目应从环境变量读取)
SECRET_KEY = "your-secret-key-keep-it-secret!"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
# 用户模型
class UserCreate(BaseModel):
username: str
password: str
# 令牌响应模型
class Token(BaseModel):
access_token: str
token_type: str
@router.post("/token", response_model=Token)
async def login_for_access_token(form_data: UserCreate):
# 用户验证(示例用静态数据,实际应查数据库)
if form_data.username != "admin" or not pwd_context.verify(
"secret", # 数据库中存储的哈希密码
form_data.password
):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="用户名或密码错误",
headers={"WWW-Authenticate": "Bearer"},
)
# 生成JWT令牌
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
data={"sub": form_data.username},
expires_delta=access_token_expires
)
return {"access_token": access_token, "token_type": "bearer"}
def create_access_token(data: dict, expires_delta: timedelta):
to_encode = data.copy()
expire = datetime.utcnow() + expires_delta
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
CryptContext
使用bcrypt算法进行密码哈希处理UserCreate
模型规范了客户端请求的数据格式verify()
方法比对哈希值create_access_token
生成带过期时间的JWT令牌# 安装依赖库(版本需严格对应)
fastapi==0.68.1
uvicorn==0.15.0
python-jose[cryptography]==3.3.0
passlib==1.7.4
示例令牌:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsImV4cCI6MTY1OTA3MDQwMH0.3w7hJH4KZ6Q-Mje3Q2T3T6k4Vd6QyQ6Qk7v6Qw7q6Qk
分段说明:
{"alg": "HS256", "typ": "JWT"}
{"sub": "admin", "exp": 1659070400}
from fastapi.security import OAuth2PasswordBearer
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/token")
async def get_current_user(token: str = Depends(oauth2_scheme)):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="无法验证凭证",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub")
if username is None:
raise credentials_exception
except JWTError:
raise credentials_exception
# 此处应查询数据库验证用户存在性
if username != "admin":
raise credentials_exception
return username
# 安全路由示例
@router.get("/protected")
async def protected_route(current_user: str = Depends(get_current_user)):
return {"message": f"欢迎您, {current_user}"}
为什么在密码存储时要使用哈希而不是明文?
A. 提高查询速度
B. 防止数据泄露导致密码暴露
C. 减少存储空间占用
D. 方便密码找回
答案:B。哈希处理后的密码即使泄露也无法逆向获取原始密码
JWT中的签名部分主要作用是什么?
A. 美化令牌格式
B. 验证令牌内容未被篡改
C. 加速令牌解析
D. 支持多种加密算法
答案:B。签名确保令牌在传输过程中未被修改
{"detail":"Not authenticated"}
curl -H "Authorization: Bearer your_token" http://localhost:8000/protected
{"detail": "Invalid authentication credentials"}
# 从环境变量读取密钥
import os
SECRET_KEY = os.getenv("JWT_SECRET_KEY")
if not SECRET_KEY:
raise ValueError("Missing JWT_SECRET_KEY environment variable")
通过本章学习,开发者可以掌握FastAPI的OAuth2密码流程核心实现,建议结合数据库实现完整的用户管理系统。下一章将讲解权限控制与角色管理的高级应用。
余下文章内容请点击跳转至 个人博客页面 或者 扫码关注或者微信搜一搜:编程智域 前端至全栈交流与成长
,阅读完整的文章:FastAPI安全认证:从密码到令牌的魔法之旅 | cmdragon's Blog
参与评论
手机查看
返回顶部