四、认证与授权

本项目是一个基于FastAPI构建的SaaS用户管理系统,采用模块化设计,将数据库操作、安全认证、角色权限、第三方登录等功能分别封装在独立模块中。用户管理功能通过User模型实现,支持基本和高级两种角色。安全认证采用JWT令牌机制,同时支持多因素认证(MFA)和API密钥认证。系统还集成了GitHub第三方登录功能,提供多种认证方式。
架构
数据存储层
数据访问层
服务层
API层
客户端
API层
服务层
数据访问层
数据库
main.py
路由分发
security.py
认证服务
rbac.py
权限服务
mfa.py
MFA服务
api_key.py
API密钥服务
operations.py
数据操作
db_connection.py
连接管理
SQLite数据库
模型
用户系统的核心是User数据模型,定义了用户的基本属性和安全相关字段。模型采用SQLAlchemy的声明式语法,支持字段索引和唯一性约束。
"拥有角色"
User
+id : int
+username : str
+email : str
+hashed_password : str
+role : Role
+totp_secret : str
Role
+basic : str
+premium : str
基本模型
py
# models.py
from enum import Enum
from sqlalchemy.orm import (
DeclarativeBase,
Mapped,
mapped_column,
)
# DeclarativeBase 是 SQLAlchemy ORM 中的声明式基类
class Base(DeclarativeBase):
pass
# 定义可分配的角色
# 继承 str + Enum 使枚举值可直接作为字符串使用
# user.role == "basic" => True
class Role(str, Enum):
basic = "basic"
premium = "premium"
class User(Base):
# 指定数据库表明为 users
__tablename__ = "users"
id: Mapped[int] = mapped_column(
primary_key=True, index=True
)
username: Mapped[str] = mapped_column(
unique=True, index=True
)
email: Mapped[str] = mapped_column(
unique=True, index=True
)
hashed_password: Mapped[str]
role: Mapped[Role] = mapped_column(
default=Role.basic
)
totp_secret: Mapped[str] = mapped_column(
nullable=True
)
ORM 就是 Object-Relational Mapping(对象关系映射),将 Python 类映射到数据库表,将 Python 对象映射到数据库行。
Python 代码层面 数据库层面
─────────────────────────────────────
class User(Base): ←→ CREATE TABLE users (
id: int ←→ id INT PRIMARY KEY,
username: str ←→ username VARCHAR UNIQUE,
email: str ←→ email VARCHAR UNIQUE,
hashed_password: str ←→ hashed_password VARCHAR,
role: Role ←→ role VARCHAR DEFAULT 'basic',
totp_secret: str ←→ totp_secret VARCHAR NULL
←→ );
响应模型
py
# responses.py(1)
# 保存不同端点的响应类
from typing import Annotated
from pydantic import BaseModel, EmailStr, Field
class UserCreateResponse(BaseModel):
username: str
email: EmailStr
class ResponseCreateUser(BaseModel):
message: Annotated[
str, Field(default="user created")
]
user: UserCreateResponse
数据库
建立数据库连接
py
# db_connection.py
from functools import lru_cache
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
# 数据库url
SQLALCHEMY_DATABASE_URL = "sqlite:///database.db"
# 内置装饰器 用于缓存函数返回值
@lru_cache
def get_engine():
# 第一次调用 get_engine() 时,会执行 create_engine
# 后续所有调用直接返回缓存的同一个引擎对象
return create_engine(
SQLALCHEMY_DATABASE_URL,
)
# 生成器函数
def get_session():
Session = sessionmaker(
autocommit=False, # 不自动提交事务
autoflush=False, # 不自动flush
# flush 将内存中的变更写入数据库
bind=get_engine(), # 绑定到上面的引擎
)
try:
session = Session()
# yield之前 资源获取
yield session
# yield之后 资源释放
finally:
# 最后一定要关闭数据库连接
session.close()
!NOTE
在
main.py中使用会使用到get_session()
py@app.post("/register/user", ...) def register( user: UserCreateBody, session: Session = Depends(get_session), # ← 依赖注入 ) -> dict[str, UserCreateResponse]: user = add_user(session=session, **user.model_dump()) # ...实际执行流程
plaintext用户发送注册请求 ↓ [第1步] FastAPI 看到 Depends(get_session) ↓ [第2步] FastAPI 调用 get_session() 函数 ↓ [第3步] 执行到 yield session 前的代码 ├─ 创建 Session 工厂 ├─ session = Session() 创建连接对象 └─ 获得一个可用的数据库连接 ✅ ↓ [第4步] yield session 暂停执行 └─ 把 session 对象传给 register 函数 ↓ [第5步] register 函数开始执行 ├─ 调用 add_user(session=session, ...) │ ├─ session.add(db_user) │ ├─ session.commit() ← 真正操作数据库 │ └─ session.refresh(db_user) ├─ 返回响应 └─ 请求处理完成 ↓ [第6步] 继续执行 yield 后的代码(finally 块) └─ session.close() ✅ 关闭连接 ↓ 向用户返回响应不适用
yield的后果
- 直接返回
session会导致session创建后不会自动关闭,造成连接堆积。最终因为连接耗尽二导致应用崩溃。- 异常可能导致
session没有正常关闭当前设计的好处
- 无论何时
session都会给关闭(正常执行、发生异常、框架出现错误)- 与 FastAPI 依赖注入配合
- 资源的获取和释放都在
get_session()函数中
数据库初始化
py
# main.py(1)
from contextlib import asynccontextmanager
from fastapi import (
Depends,
FastAPI,
HTTPException,
status,
)
from sqlalchemy.orm import Session
import api_key
import github_login
import mfa
import premium_access
import rbac
import security
import user_session
from db_connection import get_engine, get_session
from models import Base
from operations import add_user
from responses import (
ResponseCreateUser,
UserCreateBody,
UserCreateResponse,
)
from third_party_login import resolve_github_token
# 设置服务器 并初始化数据库连接
@asynccontextmanager
#
async def lifespan(app: FastAPI):
# # 这一行能工作,就是因为 Base 继承了 DeclarativeBase
Base.metadata.create_all(bind=get_engine())
yield
# 使用lifespan参数指示服务器在启动时将我们的数据库类User与数据库同步
app = FastAPI(
title="Saas application", lifespan=lifespan
)
app.include_router(security.router)
app.include_router(premium_access.router)
app.include_router(rbac.router)
app.include_router(github_login.router)
app.include_router(mfa.router)
app.include_router(user_session.router)
app.include_router(api_key.router)
数据库操作
导入相关包
py
# operations.py(1)
from email_validator import (
EmailNotValidError,
validate_email,
)
from passlib.context import CryptContext
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import Session
from models import Role, User
passlib 库
py
# operations.py(2)
# 哈希处理
# 创建了一个密码哈希上下文对象
pwd_context = CryptContext(
# schemes=["bcrypt"] 使用bcrypt哈希算法
# deprecated="auto" 持平滑算法迁移 后续如果修改schemes 会自动实现如下操作:
# 1.用旧算法(bcrypt)验证密码
# 2.自动用新算法(argon2)重新哈希
# 3.更新数据库中的哈希值 自动实现渐进式安全升级
schemes=["bcrypt"], deprecated="auto"
)
# CryptContext对象方法
# .hash(明文密码):将用户输入的密码不可逆地转换为唯一哈希字符串
# .verify(明文密码, 哈希字符串):验证用户输入的密码是否与存储的哈希匹配
身份验证
┌─────────────────────────────────────────────────────────────┐
│ 第1步:用户注册 │
├─────────────────────────────────────────────────────────────┤
│ POST /register/user │
│ {username, email, password} │
│ ↓ │
│ 密码哈希 → 存入数据库 │
│ HTTP 201 Created │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 第2步:用户登录 - 获取 JWT Token │
├─────────────────────────────────────────────────────────────┤
│ POST /token │
│ {username, password} │
│ ↓ │
│ 认证用户 → 验证密码 │
│ 生成 JWT Token(30分钟有效) │
│ HTTP 200 OK {access_token, token_type} │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 第3步:使用 Token 访问受保护资源 │
├─────────────────────────────────────────────────────────────┤
│ GET /users/me │
│ Authorization: Bearer xxx │
│ ↓ │
│ 验证 Token 签名 → 检查过期 → 查询用户 │
│ HTTP 200 OK {description: "john_doe authorized"} │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 第4步:基于角色访问不同资源 │
├─────────────────────────────────────────────────────────────┤
│ GET /welcome/all-users (basic 用户可访问) │
│ GET /welcome/premium-user (premium 用户可访问) │
│ ↓ │
│ 获取当前用户 → 检查角色 → 返回对应资源 │
│ HTTP 200 OK │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 第5步:可选 - 启用 MFA 多因素认证 │
├─────────────────────────────────────────────────────────────┤
│ POST /user/enable-mfa (需要 Token) │
│ ↓ │
│ 生成 TOTP Secret → 返回二维码 │
│ POST /verify-totp → 验证 TOTP 代码 │
└─────────────────────────────────────────────────────────────┘
用户注册
流程图
成功
失败
验证失败
验证成功
唯一约束冲突
插入成功
User 为 None
User 不为 None
客户端请求
POST /register/user
{username, email, password}
FastAPI 路由匹配
依赖注入启动
Depends(get_session)
建立数据库连接
获得 session 对象
HTTP 500
Internal Server Error
Pydantic 数据验证
UserCreateBody
HTTP 422
Unprocessable Entity
进入 register 函数
调用 add_user 函数
密码哈希处理
bcrypt(password)
创建 User 对象
内存中
session.add
标记新记录
session.commit
执行 INSERT 语句
session.rollback
撤销事务
session.refresh
获取生成的 id
返回 None
返回 User 对象
检查结果
抛出 HTTPException
status 409 Conflict
创建响应对象
UserCreateResponse
构建错误响应
username or email already exists
构建成功响应
message + user 信息
Pydantic 响应验证
ResponseCreateUser
序列化为 JSON
关闭数据库连接
session.close
HTTP 201 Created
- 响应体
关闭数据库连接
关闭数据库连接
返回响应给客户端
返回错误给客户端
返回错误给客户端
相关代码
json
//客户端发送请求模拟
POST /register/user HTTP/1.1
Content-Type: application/json
{
"username": "john_doe",
"email": "john@example.com",
"password": "SecurePassword123!"
}
main.py 路由端点
python
# main.py(2)
# 用户注册端点
@app.post(
"/register/user",
status_code=status.HTTP_201_CREATED,
response_model=ResponseCreateUser,
responses={
status.HTTP_409_CONFLICT: {
"description": "The user already exists"
}
},
)
def register(
user: UserCreateBody,
session: Session = Depends(get_session),
) -> dict[str, UserCreateResponse]:
user = add_user(
# **user.model_dump() 将 UserCreateBody 对象转换成字典 然后进行解包 作为 add_user() 方法的参数
session=session, **user.model_dump()
)
if not user:
raise HTTPException(
status.HTTP_409_CONFLICT,
"username or email already exists",
)
user_response = UserCreateResponse(
username=user.username, email=user.email
)
return {
"message": "user created",
"user": user_response,
}
依赖注入
py
session: Session = Depends(get_session)
FastAPI 看到 Depends(get_session),开始依赖注入。从 db_connection.py 中的 get_session() 获取一个 session。
数据验证
py
def register(
# 此处使用pydantic验证请求体
user: UserCreateBody,
session: Session = Depends(get_session),
) -> dict[str, UserCreateResponse]:
FastAPI 使用 Pydantic 验证请求体,转换为 UserCreateBody 对象。
原始 JSON 数据
↓
检查 username 是否为字符串?
检查 email 是否为有效邮箱格式?
检查 password 是否为字符串?
↓
创建 UserCreateBody 对象
↓
传给 register 函数
py
# responses.py(2)
class UserCreateBody(BaseModel):
username: str
email: EmailStr
password: str
数据库操作
py
# operations.py(2)
# 使用哈希后的密码将新用户插入数据库
def add_user(
session: Session,
username: str,
password: str,
email: str,
role: Role = Role.basic, # 默认是basic角色
) -> User | None:
# 对密码进行哈希
hashed_password = pwd_context.hash(password)
# 创建user对象
db_user = User(
username=username,
email=email,
hashed_password=hashed_password,
role=role,
)
# 添加到 session 并进行提交
session.add(db_user)
try:
session.commit()
session.refresh(db_user)
except IntegrityError:
session.rollback()
return
return db_user
JWT 令牌认证
流程图
不存在
存在
错误
正确
失败
成功
用户登录请求
POST /token
发送表单数据
username & password
FastAPI 依赖注入
解析 OAuth2PasswordRequestForm
获取数据库 session
调用 authenticate_user
查询用户
get_user
用户是否存在?
返回 None
验证密码
pwd_context.verify
密码是否正确?
返回 User 对象
认证结果
HTTP 401
Incorrect username or password
生成 JWT Token
create_access_token
设置过期时间
30 分钟
返回 Token
access_token + token_type
客户端保存 Token
localStorage/Cookie
认证失败
认证成功
相关代码
bash
# 客户端发起登陆请求
curl -X POST "http://localhost:8000/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "username=john_doe&password=SecurePassword123!"
py
# operations.py(3)
# 从数据库中检索用户 支持使用用户名或者邮箱检索
def get_user(
session: Session, username_or_email: str
) -> User | None:
# 智能判断输入类型
try:
validate_email(username_or_email)
query_filter = User.email
except EmailNotValidError:
query_filter = User.username
user = (
session.query(User)
.filter(query_filter == username_or_email)
.first()
)
return user
py
# security.py
# 集成 OAuth2 与 JWT(JSON Web Token)以实现应用程序中的安全用户身份验证。
from datetime import datetime, timedelta
from fastapi import (
APIRouter,
Depends,
HTTPException,
status,
)
from fastapi.security import (
OAuth2PasswordBearer,
OAuth2PasswordRequestForm,
)
from jose import JWTError, jwt
from pydantic import BaseModel
from sqlalchemy.orm import Session
from db_connection import get_session
from models import User
from operations import get_user, pwd_context
# 接上文security.py
# 用户认证函数
def authenticate_user(
session: Session,
username_or_email: str,
password: str,
) -> User | None:
# 从数据库查询用户
user = get_user(session, username_or_email) # operations.py get_user()
# 检查用户是否存在且密码正确
if not user or not pwd_context.verify( # operations.py
password, user.hashed_password
):
return
return user
# 指定密钥
SECRET_KEY = "a_very_secret_key"
# 指定算法
ALGORITHM = "HS256"
# 指定过期时间
ACCESS_TOKEN_EXPIRE_MINUTES = 30
# 创建令牌
def create_access_token(data: dict) -> str:
to_encode = data.copy()
expire = datetime.utcnow() + timedelta(
minutes=ACCESS_TOKEN_EXPIRE_MINUTES
)
# 添加标准过期声明
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(
to_encode, SECRET_KEY, algorithm=ALGORITHM
)
return encoded_jwt
# 解码令牌(令牌验证)
def decode_access_token(
token: str, db_session: Session
) -> User | None:
try:
payload = jwt.decode(
token, SECRET_KEY, algorithms=[ALGORITHM]
)
# 获取用户标识
username: str = payload.get("sub")
except JWTError:
return
if not username:
return
user = get_user(db_session, username)
return user
py
# main.py(3)
router = APIRouter()
class Token(BaseModel):
access_token: str # JWT 字符串
token_type: str
# 获取令牌路由端点
@router.post(
"/token",
response_model=Token,
responses={
status.HTTP_401_UNAUTHORIZED: {
"description": "Incorrect username or password"
}
},
)
def get_user_access_token(
form_data: OAuth2PasswordRequestForm = Depends(),
session: Session = Depends(get_session),
):
user = authenticate_user(
session, form_data.username, form_data.password
)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
)
access_token = create_access_token(
data={"sub": user.username}
)
return {
"access_token": access_token,
"token_type": "bearer",
}
验证 token 有效性
流程图
否
是
是
否
否
是
user 为 None
user 存在
客户端请求
GET /users/me
Authorization: Bearer xxx
FastAPI 路由匹配
依赖注入启动
oauth2_scheme
提取 Token
get_session
获取数据库连接
调用 read_user_me
token + session
调用 decode_access_token
token, session
jwt.decode
验证签名
签名正确?
抛 JWTError
检查 Token 是否过期
Token 过期?
提取 username
从 payload
从数据库查询用户
get_user
用户存在?
返回 None
返回 User 对象
返回 None
判断结果
raise HTTPException
401 Unauthorized
返回成功响应
description: username authorized
HTTP 401
detail: User not authorized
HTTP 200
description: john_doe authorized
返回给客户端
相关代码
py
# main.py(4)
# 创建OAuth2PasswordBearer对象获取访问令牌
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
# 根据令牌返回用户凭证
@router.get(
"/users/me",
responses={
status.HTTP_401_UNAUTHORIZED: {
"description": "User not authorized"
},
status.HTTP_200_OK: {
"description": "username authorized"
},
},
)
def read_user_me(
token: str = Depends(oauth2_scheme),
session: Session = Depends(get_session),
):
user = decode_access_token(token, session)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="User not authorized",
)
return {
"description": f"{user.username} authorized",
}
基于角色的访问控制
高级用户注册
py
# premium_access.py
# 高级用户注册
from fastapi import (
APIRouter,
Depends,
HTTPException,
status,
)
from sqlalchemy.orm import Session
from db_connection import get_session
from models import Role
from operations import add_user
from responses import (
ResponseCreateUser,
UserCreateBody,
UserCreateResponse,
)
router = APIRouter()
@router.post(
"/register/premium-user",
status_code=status.HTTP_201_CREATED,
response_model=ResponseCreateUser,
responses={
status.HTTP_409_CONFLICT: {
"description": "The user already exists"
},
status.HTTP_201_CREATED: {
"description": "User created"
},
},
)
def register_premium_user(
user: UserCreateBody,
session: Session = Depends(get_session),
):
user = add_user(
session=session,
**user.model_dump(),
role=Role.premium,
)
if not user:
raise HTTPException(
status.HTTP_409_CONFLICT,
"username or email already exists",
)
user_response = UserCreateResponse(
username=user.username,
email=user.email,
)
return {
"message": "user created",
"user": user_response,
}
访问控制实现
py
# rbac.py
# 基于角色的访问控制
from typing import Annotated
from fastapi import (
APIRouter,
Depends,
HTTPException,
status,
)
from pydantic import BaseModel, EmailStr
from sqlalchemy.orm import Session
from db_connection import get_session
from models import Role
from security import decode_access_token, oauth2_scheme
class UserCreateRequestWithRole(BaseModel):
username: str
email: EmailStr
role: Role
def get_current_user(
token: str = Depends(oauth2_scheme),
session: Session = Depends(get_session),
) -> UserCreateRequestWithRole:
user = decode_access_token(token, session)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="User not authorized",
)
return UserCreateRequestWithRole(
username=user.username,
email=user.email,
role=user.role,
)
def get_premium_user(
current_user: Annotated[
get_current_user, Depends()
],
):
if current_user.role != Role.premium:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="User not authorized",
)
return current_user
router = APIRouter()
@router.get(
"/welcome/all-users",
responses={
status.HTTP_401_UNAUTHORIZED: {
"description": "User not authorized"
}
},
)
def all_user_can_access(
user: Annotated[get_current_user, Depends()],
):
return {
f"Hello {user.username}, welcome to your space"
}
@router.get(
"/welcome/premium-user",
responses={
status.HTTP_401_UNAUTHORIZED: {
"description": "User not authorized"
}
},
)
def only_premium_user_can_access(
user: Annotated[get_premium_user, Depends()],
):
return {
f"Hello {user.username}, "
"welcome to your premium space"
}
第三方认证
py
# github_login.py
# 第三方认证
import httpx
from fastapi import APIRouter, HTTPException, status
from security import Token
from third_party_login import (
GITHUB_AUTHORIZATION_URL,
GITHUB_CLIENT_ID,
GITHUB_CLIENT_SECRET,
GITHUB_REDIRECT_URI,
)
router = APIRouter()
@router.get("/auth/url")
def github_login():
return {
"auth_url": GITHUB_AUTHORIZATION_URL
+ f"?client_id={GITHUB_CLIENT_ID}"
}
@router.get(
"/github/auth/token",
response_model=Token,
responses={
status.HTTP_401_UNAUTHORIZED: {
"description": "User not registered"
}
},
)
async def github_callback(code: str):
token_response = httpx.post(
"https://github.com/login/oauth/access_token",
data={
"client_id": GITHUB_CLIENT_ID,
"client_secret": GITHUB_CLIENT_SECRET,
"code": code,
"redirect_uri": GITHUB_REDIRECT_URI,
},
headers={"Accept": "application/json"},
).json()
access_token = token_response.get("access_token")
if not access_token:
raise HTTPException(
status_code=401,
detail="User not registered",
)
token_type = token_response.get(
"token_type", "bearer"
)
return {
"access_token": access_token,
"token_type": token_type,
}
py
# main.py(5)
@app.get(
"/home",
responses={
status.HTTP_403_FORBIDDEN: {
"description": "token not valid"
}
},
)
def homepage(
user: UserCreateResponse = Depends(
resolve_github_token
),
):
return {"message": f"logged in {user.username} !"}
多因素认证
MFA = Multi-Factor Authentication(多因素认证)
正确
错误
用户账户
username + email + password
启用 MFA
POST /user/enable-mfa
生成 TOTP Secret
pyotp.random_base32
保存到数据库
user.totp_secret
返回二维码 URI
totp_uri
前端扫描二维码
Google Authenticator
认证器应用配置成功
用户需要登录时
输入用户名和密码
获取认证器中的 6 位码
验证 TOTP 码
POST /verify-totp
TOTP 码
是否正确?
登录成功
用户已验证双因素
登录失败
需要重新输入
py
# mfa.py
# 多因素认证
import pyotp
from fastapi import (
APIRouter,
Depends,
HTTPException,
status,
)
from sqlalchemy.orm import Session
from db_connection import get_session
from operations import get_user
from rbac import get_current_user
from responses import UserCreateResponse
def generate_totp_secret():
return pyotp.random_base32()
def generate_totp_uri(secret, user_email):
return pyotp.totp.TOTP(secret).provisioning_uri(
name=user_email, issuer_name="YourAppName"
)
router = APIRouter()
# 用于要完成登陆之后才能启用mfa
@router.post("/user/enable-mfa")
def enable_mfa(
user: UserCreateResponse = Depends(
get_current_user
),
db_session: Session = Depends(get_session),
):
# 生成 TOTP Secret
secret = generate_totp_secret()
# 从数据库查询用户
db_user = get_user(db_session, user.username)
# 保存密码到用户记录
db_user.totp_secret = secret
db_session.add(db_user)
db_session.commit()
# 生成二维码 URI
totp_uri = generate_totp_uri(secret, user.email)
# 返回给客户端
return {
"totp_uri": totp_uri,
"secret_numbers": pyotp.TOTP(secret).now(),
}
@router.post("/verify-totp")
def verify_totp(
code: str,
username: str,
session: Session = Depends(get_session),
):
user = get_user(session, username)
if not user.totp_secret:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="MFA not activated",
)
totp = pyotp.TOTP(user.totp_secret)
if not totp.verify(code):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid TOTP token",
)
# Proceed with granting access
# or performing the sensitive operation
return {
"message": "TOTP token verified successfully"
}
API密钥认证
这里只是一个简化的操作,与其余的功能并没有什么关联
py
# api_key.py
# API密钥验证
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException
VALID_API_KEYS = [
"verysecureapikey",
"anothersecureapi",
"onemoresecureapi",
]
# 依赖函数
async def get_api_key(api_key: Optional[str]):
if api_key not in VALID_API_KEYS:
raise HTTPException(
status_code=403, detail="Invalid API Key"
)
return api_key
router = APIRouter()
@router.get("/secure-data")
async def get_secure_data(
# 依赖注入
api_key: str = Depends(get_api_key),
):
return {"message": "Access to secure data granted"}
否
是
否
是
客户端请求 /secure-data
是否包含API密钥?
返回403错误
提取API密钥
密钥是否有效?
执行数据获取
返回成功响应
请求结束
会话Cookie与注销
py
# user_session.py
# 处理会话cookie与注销
from fastapi import APIRouter, Depends, Response
from sqlalchemy.orm import Session
from db_connection import get_session
from operations import get_user
from rbac import get_current_user
from responses import UserCreateResponse
router = APIRouter()
@router.post("/login")
async def login(
response: Response,
user: UserCreateResponse = Depends(
get_current_user
),
session: Session = Depends(get_session),
):
user = get_user(session, user.username)
response.set_cookie(
key="fakesession", value=f"{user.id}"
)
return {"message": "User logged in successfully"}
@router.post("/logout")
async def logout(
response: Response,
user: UserCreateResponse = Depends(
get_current_user
),
):
response.delete_cookie(
"fakesession"
) # Clear session data
return {"message": "User logged out successfully"}