fastapi:第二十八章:用jwt做用户身份验证

一,安装用到的库

在 FastAPI 中,使用 JWT(JSON Web Token)进行身份验证时,现在官方和社区最推荐、使用率最高的组合是:

  1. PyJWT:负责生成(Encode)和解析(Decode)JWT 字符串。

  2. passlib[bcrypt]:负责对用户密码进行哈希加盐存储和验证。

  3. 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": "如果你能看到我,说明认证成功了!"}

 

三,测试效果:

image

image

posted @ 2026-06-12 10:53  刘宏缔的架构森林  阅读(14)  评论(0)    收藏  举报