这是一份 完整的 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) |
❌ 十、绝对不要做的(安全红线)
- 在日志/响应中打印密码或 token
- 使用可逆加密存储密码
- 把
.env提交到 Git - 前端通过 URL 传 token(如
?token=xxx) - 忽略
verify_password直接比对明文
✅ 总结:后端开发 Checklist
- 使用虚拟环境 + 锁定依赖版本
.env管理敏感配置(SECRET_KEY, DB_URL)- 密码用
bcrypt哈希(非加密) - JWT 无状态设计,退出登录由前端处理
- 统一认证错误信息(防用户枚举)
- 用 Alembic 管理数据库变更
- 启用 HTTPS + CORS 限制
- 不记录任何密码/token 到日志