完整的 FastAPI 后端开发流程

这是一份 完整的 FastAPI 后端开发流程(以用户认证为例) ,涵盖从项目初始化到部署上线的 核心步骤 + 关键细节 + 安全最佳实践


🧱 一、项目初始化

✅ 1. 创建虚拟环境

复制代码

bash

编辑

复制代码
python -m venv .venv
source .venv/bin/activate   # Linux/macOS
# 或 .venv\Scripts\activate  # Windows

✅ 2. 安装核心依赖

复制代码

txt

编辑

复制代码
# requirements.txt
fastapi==0.115.0
uvicorn[standard]==0.32.0
sqlalchemy==2.0.36
passlib[bcrypt]==1.7.4
python-jose[cryptography]==3.3.0
python-dotenv==1.0.1
alembic==1.13.2
pydantic-settings==2.6.1

⚠️ 关键细节

  • passlib[bcrypt] 必须带 [bcrypt],且 bcrypt 版本 < 4.0 (避免 __about__ 错误)
  • 使用 python-jose(不是 PyJWT),FastAPI 官方推荐

✅ 3. 项目结构(推荐)

复制代码

text

编辑

复制代码
/backend
├── app/
│   ├── __init__.py
│   ├── main.py                 # FastAPI 实例
│   ├── config.py               # 配置(含 SECRET_KEY)
│   ├── database.py             # DB 会话
│   ├── models/                 # SQLAlchemy 模型
│   │   └── user.py
│   ├── schemas/                # Pydantic 模型
│   │   └── token.py, user.py
│   ├── routers/                # 路由
│   │   └── auth.py
│   └── core/
│       └── security.py         # 密码哈希、JWT 工具
├── alembic/                    # 数据库迁移
├── .env                        # 环境变量(不提交 Git!)
├── .gitignore
└── requirements.txt

🔐 二、安全配置(重中之重)

✅ 1. 生成强密钥

复制代码

bash

编辑

复制代码
python -c "import secrets; print(secrets.token_urlsafe(32))"
# 输出示例:x7GpL2#mQv9@nRfT8sKzWbYhNcXeUjAqP5oVlDgEiJkM

✅ 2. .env 文件(绝不提交到 Git

复制代码

env

编辑

复制代码
SECRET_KEY=x7GpL2#mQv9@nRfT8sKzWbYhNcXeUjAqP5oVlDgEiJkM
ALGORITHM=HS256
ACCESS_TOKEN_EXPIRE_MINUTES=30
DATABASE_URL=sqlite:///./test.db

✅ 3. config.py 加载配置

复制代码

python

编辑

复制代码
# app/config.py
from pydantic_settings import BaseSettings

class Settings(BaseSettings):
    SECRET_KEY: str
    ALGORITHM: str = "HS256"
    ACCESS_TOKEN_EXPIRE_MINUTES: int = 30
    DATABASE_URL: str

    class Config:
        env_file = ".env"

settings = Settings()

安全细节

  • SECRET_KEY 必须保密、足够长(≥32 字符)
  • 生产环境用 postgresql://mysql://,不要用 SQLite

🗃️ 三、数据库与模型

✅ 1. database.py

复制代码

python

编辑

复制代码
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

engine = create_engine(settings.DATABASE_URL, connect_args={"check_same_thread": False})
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

✅ 2. models/user.py

复制代码

python

编辑

复制代码
from sqlalchemy import Column, Integer, String
from app.database import Base

class User(Base):
    __tablename__ = "users"
    id = Column(Integer, primary_key=True, index=True)
    username = Column(String, unique=True, index=True)
    password = Column(String)  # 存 bcrypt 哈希

⚠️ 注意

  • 初始密码可为明文(用于自动升级),但绝不能在生产环境预设明文
  • 用户名/邮箱必须加 unique=True

🔑 四、安全工具(core/security.py

复制代码

python

编辑

复制代码
from datetime import datetime, timedelta
from jose import jwt
from passlib.context import CryptContext
from app.config import settings

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

def verify_password(plain_password: str, hashed_password: str) -> bool:
    return pwd_context.verify(plain_password, hashed_password)

def hash_password(password: str) -> str:
    if len(password.encode('utf-8')) > 72:
        raise ValueError("Password too long (max 72 bytes)")
    return pwd_context.hash(password)

def create_access_token(data: dict, expires_delta: timedelta = None):
    to_encode = data.copy()
    expire = datetime.utcnow() + (expires_delta or timedelta(minutes=15))
    to_encode.update({"exp": expire})
    return jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM)

关键防御

  • hash_password 中显式检查长度 ≤72 字节(防 bcrypt 报错)
  • 不允许对已哈希密码再次哈希(靠 pwd_context.verify 自动处理)

🚪 五、认证路由(routers/auth.py

复制代码

python

编辑

复制代码
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.orm import Session
from app import models, schemas, security
from app.database import get_db
from app.config import settings

router = APIRouter(tags=["auth"])

@router.post("/login", response_model=schemas.Token)
def login(
    data: schemas.Data,
    db: Session = Depends(get_db)
):
    user = db.query(models.User).filter(models.User.username == data.username).first()
    if not user or not security.verify_password(data.password, user.password):
        raise HTTPException(status_code=401, detail="用户名或密码错误")

    # 自动升级明文密码(仅当是明文时)
    if not pwd_context.identify(user.password):
        user.password = security.hash_password(data.password)
        db.commit()

    access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
    access_token = security.create_access_token(
        data={"sub": str(user.id)},
        expires_delta=access_token_expires
    )
    return {"access_token": access_token, "token_type": "bearer"}

安全细节

  • 统一返回"用户名或密码错误"(防止枚举用户名)
  • 明文密码自动升级为 bcrypt(平滑迁移)
  • 使用 Depends(get_db) 管理数据库会话

🛡️ 六、受保护路由(如何验证 Token)

复制代码

python

编辑

复制代码
from fastapi.security import OAuth2PasswordBearer

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/login")

def get_current_user(
    token: str = Depends(oauth2_scheme),
    db: Session = Depends(get_db)
):
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Could not validate credentials",
        headers={"WWW-Authenticate": "Bearer"},
    )
    try:
        payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
        user_id: str = payload.get("sub")
        if user_id is None:
            raise credentials_exception
    except JWTError:
        raise credentials_exception

    user = db.query(User).filter(User.id == user_id).first()
    if user is None:
        raise credentials_exception
    return user

# 使用示例
@router.get("/me")
def read_users_me(current_user: User = Depends(get_current_user)):
    return {"username": current_user.username}

关键点

  • 所有受保护接口依赖 get_current_user
  • 返回标准 401 + WWW-Authenticate: Bearer

📦 七、数据库迁移(Alembic)

复制代码

bash

编辑

复制代码
alembic init alembic
# 修改 alembic.ini: sqlalchemy.url = %(DATABASE_URL)s
# 修改 env.py: from app.config import settings; target_metadata = Base.metadata
alembic revision --autogenerate -m "create users table"
alembic upgrade head

✅ 避免手动改表,用迁移保证一致性


🧪 八、测试与文档

✅ 1. 自动文档

访问 http://localhost:8000/docs(Swagger UI)

→ 前端可直接测试接口、查看请求格式

✅ 2. 单元测试(pytest)

复制代码

python

编辑

复制代码
def test_login_success(client, test_user):
    response = client.post("/login", json={"username": "test", "password": "test123"})
    assert response.status_code == 200
    assert "access_token" in response.json()

🚀 九、部署注意事项

事项 建议
SECRET_KEY 生产环境从环境变量读取,绝不写死代码
数据库 用 PostgreSQL/MySQL,关闭 SQLite
HTTPS 必须启用(否则 token 可能被窃听)
CORS 限制前端域名(fastapi.middleware.cors
日志 记录登录失败次数(防暴力破解)
速率限制 /login 接口限流(如 slowapi

❌ 十、绝对不要做的(安全红线)

  1. 在日志/响应中打印密码或 token
  2. 使用可逆加密存储密码
  3. .env 提交到 Git
  4. 前端通过 URL 传 token(如 ?token=xxx
  5. 忽略 verify_password 直接比对明文

✅ 总结:后端开发 Checklist

  • 使用虚拟环境 + 锁定依赖版本
  • .env 管理敏感配置(SECRET_KEY, DB_URL)
  • 密码用 bcrypt 哈希(非加密)
  • JWT 无状态设计,退出登录由前端处理
  • 统一认证错误信息(防用户枚举)
  • 用 Alembic 管理数据库变更
  • 启用 HTTPS + CORS 限制
  • 不记录任何密码/token 到日志
相关推荐
懒人村杂货铺4 小时前
FastAPI 中全局异常处理
fastapi
Good kid.6 小时前
【原创】基于 RoBERTa 的智能垃圾分类系统(规则 + AI 混合,FastAPI 接口 + Web Demo)
人工智能·分类·fastapi
博客胡21 小时前
Python-fastAPI的学习与使用
学习·fastapi·ai编程
wang6021252181 天前
FastAPI中的异步任务执行-celery
fastapi·celery
宁雨桥1 天前
多引擎中英翻译API搭建与使用教程
python·fastapi·翻译
Luke Ewin1 天前
基于FunASR开发的可私有化部署的语音转文字接口 | FunASR接口开发 | 语音识别接口私有化部署
人工智能·python·语音识别·fastapi·asr·funasr
钱彬 (Qian Bin)2 天前
项目实践11—全球证件智能识别系统(切换为PostgreSQL数据库)
人工智能·qt·fastapi
wang6021252182 天前
FastAPI的异步开发-Asyncio
python·fastapi·asyncio
Hi_kenyon3 天前
FastAPI+VUE3创建一个项目的步骤模板(二)
python·fastapi