Python FastAPI 数据库集成(SQLAlchemy)+ 接口权限校验

Python FastAPI 教程:数据库集成(SQLAlchemy)+ 接口权限校验

前面我们掌握了参数传递与响应校验,这一节将聚焦生产级 API 核心需求 ------ 数据库持久化(基于 SQLAlchemy ORM)和接口权限控制(基于 OAuth2 + JWT),让 API 具备数据存储、身份认证和权限隔离能力。

一、前置依赖安装

需额外安装数据库相关和权限相关依赖:

css 复制代码
pip install sqlalchemy pymysql python-jose[cryptography] passlib[bcrypt] python-multipart
  • 依赖说明:
    • sqlalchemy:Python 主流 ORM 框架,实现数据库操作抽象。
    • pymysql:MySQL 数据库驱动(若用 PostgreSQL 可替换为 psycopg2-binary)。
    • python-jose:生成和验证 JWT 令牌。
    • passlib:密码哈希处理(避免明文存储)。

二、数据库集成(SQLAlchemy)

以 MySQL 为例,实现数据库连接、模型定义、CRUD 操作封装。

2.1 项目结构调整

为便于维护,调整项目结构如下:

bash 复制代码
fastwebprojects/
├── main.py          # 主程序入口(路由、APP 初始化)
├── database.py      # 数据库连接配置
├── models.py        # SQLAlchemy 数据模型(对应数据库表)
├── schemas.py       # Pydantic 模型(请求/响应校验)
└── crud.py          # 数据库 CRUD 操作封装

2.2 数据库连接配置(database.py

ini 复制代码
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
# MySQL 连接地址:mysql+pymysql://用户名:密码@主机:端口/数据库名
SQLALCHEMY_DATABASE_URL = "mysql+pymysql://root:123456@localhost:3306/fastapi_db"
# 创建数据库引擎
engine = create_engine(
    SQLALCHEMY_DATABASE_URL,
    pool_pre_ping=True  # 连接前校验,避免失效连接
)
# 创建会话本地类(每次请求对应一个会话,独立隔离)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
# 基础模型类(所有数据模型继承此类)
Base = declarative_base()

2.3 定义数据模型(models.py

数据模型对应数据库表,SQLAlchemy 会自动生成表结构(需手动执行迁移)。

ini 复制代码
from sqlalchemy import Column, Integer, String, Boolean
from database import Base
# 用户表模型
class User(Base):
    __tablename__ = "users"  # 数据库表名
    id = Column(Integer, primary_key=True, index=True)  # 主键 ID,索引
    username = Column(String(50), unique=True, index=True, nullable=False)  # 用户名,唯一
    email = Column(String(100), unique=True, index=True, nullable=False)  # 邮箱,唯一
    hashed_password = Column(String(100), nullable=False)  # 哈希后的密码
    is_active = Column(Boolean, default=True)  # 是否激活
    is_admin = Column(Boolean, default=False)  # 是否为管理员(用于权限控制)

2.4 初始化数据库表

main.py 中添加表创建逻辑,启动时自动创建不存在的表:

python 复制代码
from fastapi import FastAPI
from database import engine, SessionLocal
import models
# 创建所有数据模型对应的数据库表
models.Base.metadata.create_all(bind=engine)
app = FastAPI()
# 依赖函数:获取数据库会话(每个请求独立会话)
def get_db():
    db = SessionLocal()
    try:
        yield db  # 提供会话给路由函数
    finally:
        db.close()  # 请求结束后关闭会话

2.5 封装 CRUD 操作(crud.py

分离数据库操作与路由逻辑,降低耦合:

python 复制代码
from sqlalchemy.orm import Session
import models
import schemas
from passlib.context import CryptContext
# 密码哈希上下文(使用 bcrypt 算法)
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
# 密码哈希处理
def get_password_hash(password: str):
    return pwd_context.hash(password)
# 验证密码
def verify_password(plain_password: str, hashed_password: str):
    return pwd_context.verify(plain_password, hashed_password)
# 创建用户
def create_user(db: Session, user: schemas.UserCreate):
    hashed_password = get_password_hash(user.password)
    db_user = models.User(
        username=user.username,
        email=user.email,
        hashed_password=hashed_password
    )
    db.add(db_user)  # 添加到会话
    db.commit()      # 提交事务
    db.refresh(db_user)  # 刷新数据(获取自动生成的 ID 等)
    return db_user
# 根据用户名查询用户
def get_user_by_username(db: Session, username: str):
    return db.query(models.User).filter(models.User.username == username).first()

2.6 定义 Pydantic 模型(schemas.py

区分请求体(创建用户时传入密码)和响应体(返回时隐藏密码):

python 复制代码
from pydantic import BaseModel, EmailStr, Field
from typing import Optional
# 创建用户的请求体模型(需传入密码)
class UserCreate(BaseModel):
    username: str = Field(..., min_length=3, max_length=50, description="用户名")
    email: EmailStr = Field(..., description="邮箱")  # EmailStr 自动校验邮箱格式
    password: str = Field(..., min_length=6, description="密码,至少 6 位")
# 返回用户的响应体模型(隐藏密码)
class UserResponse(BaseModel):
    id: int
    username: str
    email: EmailStr
    is_active: bool
    is_admin: bool
    class Config:
        orm_mode = True  # 支持从 SQLAlchemy 模型直接转换

三、接口权限校验(OAuth2 + JWT)

实现「用户名密码登录获取令牌」→「携带令牌访问受保护接口」的完整流程。

3.1 配置 JWT 密钥与过期时间

main.py 中添加配置(实际项目建议用环境变量存储密钥):

python 复制代码
from datetime import datetime, timedelta
from jose import JWTError, jwt
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from sqlalchemy.orm import Session
# JWT 配置
SECRET_KEY = "your-secret-key-32bytes-long-keep-it-safe"  # 密钥(生产环境需随机生成)
ALGORITHM = "HS256"  # 加密算法
ACCESS_TOKEN_EXPIRE_MINUTES = 30  # 令牌过期时间(30 分钟)
# OAuth2 依赖:从请求头的 Authorization 字段获取令牌(格式:Bearer <token>)
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

3.2 实现令牌生成与验证逻辑

main.py 中添加核心函数:

ini 复制代码
# 生成访问令牌
def create_access_token(data: dict):
    to_encode = data.copy()
    # 设置过期时间
    expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    to_encode.update({"exp": expire})
    # 生成 JWT 令牌
    encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
    return encoded_jwt
# 依赖函数:验证令牌并返回当前用户
def get_current_user(
    db: Session = Depends(get_db),
    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")  # 令牌中存储的用户名(sub 为标准字段)
        if username is None:
            raise credentials_exception
    except JWTError:
        raise credentials_exception
    # 查询用户
    user = crud.get_user_by_username(db, username=username)
    if user is None:
        raise credentials_exception
    return user
# 依赖函数:验证当前用户是否为管理员(权限细化)
def get_current_admin_user(current_user: models.User = Depends(get_current_user)):
    if not current_user.is_admin:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="无权限访问,仅管理员可操作"
        )
    return current_user

3.3 实现登录接口(获取令牌)

ini 复制代码
@app.post("/token", response_model=dict)
async def login_for_access_token(
    db: Session = Depends(get_db),
    form_data: OAuth2PasswordRequestForm = Depends()  # 接收表单格式的用户名密码
):
    # 验证用户名和密码
    user = crud.get_user_by_username(db, username=form_data.username)
    if not user or not crud.verify_password(form_data.password, user.hashed_password):
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="用户名或密码错误",
            headers={"WWW-Authenticate": "Bearer"},
        )
    # 生成令牌(sub 字段存储用户名)
    access_token = create_access_token(data={"sub": user.username})
    return {"access_token": access_token, "token_type": "bearer"}

3.4 实现受保护接口

  • 普通接口:需登录(携带令牌)即可访问。
  • 管理员接口:需登录且为管理员身份才能访问。
python 复制代码
# 普通受保护接口:获取当前登录用户信息
@app.get("/users/me/", response_model=schemas.UserResponse)
async def read_users_me(current_user: models.User = Depends(get_current_user)):
    return current_user
# 管理员接口:创建用户(仅管理员可操作)
@app.post("/users/", response_model=schemas.UserResponse)
async def create_new_user(
    user: schemas.UserCreate,
    db: Session = Depends(get_db),
    current_user: models.User = Depends(get_current_admin_user)  # 管理员权限依赖
):
    # 检查用户名是否已存在
    db_user = crud.get_user_by_username(db, username=user.username)
    if db_user:
        raise HTTPException(status_code=400, detail="用户名已存在")
    return crud.create_user(db=db, user=user)

四、测试流程与验证

1. 初始化数据库

  1. 确保 MySQL 已启动,创建 fastapi_db 数据库(CREATE DATABASE fastapi_db;)。
  1. 启动 FastAPI 服务(uvicorn main:app --reload),自动创建 users 表。

2. 测试接口(通过 /docs 页面)

  • 第二步:输入用户名(如 admin)、密码(如 123456),先调用 /users/ 接口(需管理员身份,可手动在数据库将 is_admin 设为 1)创建管理员用户。
  • 第三步:用管理员用户名密码调用 /token 接口,获取 access_token。
  • 第四步:调用 /users/me/ 接口,系统会自动携带令牌,返回当前用户信息。
  • 第五步:非管理员用户调用 /users/ 接口,会返回 403 权限错误。

五、生产级优化建议

  1. 密钥与数据库配置:用 python-dotenv 读取环境变量,避免硬编码。
  1. 数据库迁移:使用 alembic 管理表结构变更(避免直接删除重建表)。
  1. 令牌安全:延长过期时间时,可结合刷新令牌机制。
  1. 日志与监控:添加接口访问日志,监控令牌过期、数据库异常等情况。
相关推荐
Hacker_Future2 小时前
Python FastAPI 参数传递与响应校验
后端
NiShiKiFuNa2 小时前
AutoHotkey 功能配置与使用指南
后端
黎燃2 小时前
基于生产负载回放的数据库迁移验证实践:从模拟测试到真实预演【金仓数据库】
后端
文心快码BaiduComate3 小时前
双十一将至,用Rules玩转电商场景提效
前端·人工智能·后端
该用户已不存在3 小时前
免费的 Vibe Coding 助手?你想要的Gemini CLI 都有
人工智能·后端·ai编程
bcbnb3 小时前
uni-app iOS性能监控全攻略,跨端架构下的性能采集、分析与多工具协同优化实战
后端
qq_12498707533 小时前
基于springboot+vue的物流管理系统的设计与实现(源码+论文+部署+安装)
java·spring boot·后端·毕业设计
CryptoRzz3 小时前
DeepSeek印度股票数据源 Java 对接文档
前端·后端
刘一说5 小时前
深入理解 Spring Boot Actuator:构建可观测性与运维友好的应用
运维·spring boot·后端