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已过期)

已登录:

必须登录的接口

浙公网安备 33010602011771号