FastAPI安全认证:从密码到令牌的魔法之旅


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:

  • 后端开发
  • FastAPI

tags:

  • FastAPI
  • OAuth2
  • JWT
  • 安全认证
  • 密码哈希
  • 令牌校验
  • 访问控制

扫描二维码

关注或者微信搜一搜:编程智域 前端至全栈交流与成长

探索数千个预构建的 AI 应用,开启你的下一个伟大创意https://tools.cmdragon.cn/

第一章:FastAPI安全认证核心实现

(注:根据写作规范要求,章节编号从第一章开始编排)

一、令牌端点(Token Endpoint)的创建

1.1 OAuth2密码流程原理

OAuth2密码流程(Password Grant)是直接通过用户名密码获取访问令牌的认证方式。类比演唱会验票流程:用户先到售票处(令牌端点)用身份证(凭证)换取门票(令牌),之后凭门票入场(访问资源)。

流程步骤:

  1. 客户端发送用户名密码到/token端点
  2. 服务器验证凭证有效性
  3. 生成包含用户身份和有效期的JWT令牌
  4. 返回访问令牌给客户端

1.2 FastAPI端点实现

python 复制代码
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

代码解析:

  1. CryptContext 使用bcrypt算法进行密码哈希处理
  2. UserCreate模型规范了客户端请求的数据格式
  3. 密码验证使用verify()方法比对哈希值
  4. create_access_token生成带过期时间的JWT令牌

1.3 运行环境配置

bash 复制代码
# 安装依赖库(版本需严格对应)
fastapi==0.68.1
uvicorn==0.15.0
python-jose[cryptography]==3.3.0
passlib==1.7.4

二、访问令牌生成与校验

2.1 JWT令牌结构解析

示例令牌:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsImV4cCI6MTY1OTA3MDQwMH0.3w7hJH4KZ6Q-Mje3Q2T3T6k4Vd6QyQ6Qk7v6Qw7q6Qk

分段说明:

  • Header:{"alg": "HS256", "typ": "JWT"}
  • Payload:{"sub": "admin", "exp": 1659070400}
  • Signature:使用密钥对前两部分的签名

2.2 令牌校验实现

python 复制代码
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}"}

校验流程:

  1. 从请求头提取Bearer令牌
  2. 解码并验证JWT签名
  3. 检查令牌有效期
  4. 验证用户是否存在(示例简化处理)

三、课后Quiz

  1. 为什么在密码存储时要使用哈希而不是明文?

    A. 提高查询速度

    B. 防止数据泄露导致密码暴露

    C. 减少存储空间占用

    D. 方便密码找回

    答案:B。哈希处理后的密码即使泄露也无法逆向获取原始密码

  2. JWT中的签名部分主要作用是什么?

    A. 美化令牌格式

    B. 验证令牌内容未被篡改

    C. 加速令牌解析

    D. 支持多种加密算法

    答案:B。签名确保令牌在传输过程中未被修改

四、常见报错解决方案

问题1:401 Unauthorized

  • 现象{"detail":"Not authenticated"}

  • 原因:请求头缺少Authorization字段或格式错误

  • 解决

    bash 复制代码
    curl -H "Authorization: Bearer your_token" http://localhost:8000/protected

问题2:422 Validation Error

  • 现象:请求体参数校验失败
  • 原因:未按UserCreate模型格式提交数据
  • 解决:检查请求是否包含username和password字段

问题3:403 Forbidden

  • 现象{"detail": "Invalid authentication credentials"}
  • 原因:令牌已过期或签名验证失败
  • 解决:重新获取有效令牌,检查密钥一致性

五、安全增强建议

  1. 生产环境必须

    • 通过HTTPS传输令牌
    • 使用环境变量存储密钥
    • 定期轮换加密密钥
  2. 推荐方案

    python 复制代码
    # 从环境变量读取密钥
    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

往期文章归档: