fastapi: 如何指定用户登录的强依赖和弱依赖?

一,代码

jwt功能模块和用户登录的两个依赖项:

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
# from passlib.context import CryptContext
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",    # 密码:123456
    }
}

# 规定 Token 的获取地址(FastAPI 会自动在 Swagger UI 中生成登录按钮)
# oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
oauth2_scheme_optional = OAuth2PasswordBearer(tokenUrl="token", auto_error=False)
oauth2_scheme_strict = OAuth2PasswordBearer(tokenUrl="token", auto_error=True)

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

# ----------------- 依赖项 1:必须登录 (Strict) -----------------
async def get_current_user(token: str = Depends(oauth2_scheme_optional)):
    """依赖项:从请求头中解析并验证 Token,返回当前用户"""
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="无法验证凭据",
        # headers={"WWW-Authenticate": "Bearer"},
        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

# ----------------- 依赖项 2:可选登录 (Optional) -----------------
async def get_optional_user(token: Optional[str] = Depends(oauth2_scheme_optional)):
    '''
    user = verify_token(token)
    # 注意:如果传了 Token 但是假的,生产环境通常也会视作未登录(返回 None)
    # 或者也可以选择抛出异常,取决于你的业务设计
    return user
    '''
    if not token:
        return None  # 前端没传 Token,直接返回 None,不报错
    try:
        # 解密 Token
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        username: str = payload.get("sub")
        print("username:", username)
        if username is None:
            return None
    except jwt.PyJWTError:
        return None

    user = USER_DB.get(username)
    if user is None:  # 简写,实际应为 user is None
        return None
    return user

应用依赖:

@router.get("/info")
async def read_users_me(current_user: dict = Depends(get_current_user)):
    """受保护的路由,只有携带有效 Token 才能访问"""
    return {"username": current_user["username"], "message": "如果你能看到我,说明认证成功了!"}


@router.get("/info2")
async def read_users_info2(user: Optional[dict] = Depends(get_optional_user)):
    """受保护的路由,只有携带有效 Token 才能访问"""

    if user:
        return {
            "notebook": f"欢迎回来,{user['username']}!",
            "articles": "已登录用户显示全部文章"  # 已登录用户看到全部文章
        }
    else:
        return {
            "notebook": "当前用户未登录",
            "articles": "未登录用户显示免费文章"  # 未登录用户只能看免费文章
        }

二,测试效果:

未登录(token已过期)

image

已登录:

image

必须登录的接口

image

posted @ 2026-06-29 20:09  刘宏缔的架构森林  阅读(2)  评论(0)    收藏  举报