FastAPI实战笔记(四) 认证与授权

四、认证与授权

本项目是一个基于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"}
相关推荐
bst@微胖子1 天前
CrewAI+FastAPI实现多Agent协作项目
java·前端·fastapi
花酒锄作田1 天前
FastAPI异步方法中调用同步方法
python·fastapi
写文章的大米1 天前
别再堆if-else验参数了!FastAPI自带的参数验证器,至少省一半调试时间
python·fastapi
特立独行的猫a1 天前
python的FastAPI 框架入门教程:从零构建完整 API 项目(含 Jinja2 模板引擎使用)
开发语言·python·fastapi
工藤学编程1 天前
【完整可运行】图书馆管理系统(SpringBoot+Vue+FastApi+LangChain)
vue.js·spring boot·fastapi
曲幽1 天前
从本地到云端:深入理解WSGI,让你的Python Web应用稳健部署
python·nginx·flask·fastapi·web·gunicorn·uvicorn·diango·waitress
曲幽2 天前
从安装到上线:一份 Nginx 实战指南,让你的 Web 应用稳建安全
python·nginx·flask·fastapi·web·gunicorn·uvicorn
laufing2 天前
sqlmodel -- fastapi 连接关系型数据库
数据库·fastapi·sqlmodel
PieroPc2 天前
用FastAPI 后端 和 HTML/CSS/JavaScript 前端写一个博客系统 例
前端·html·fastapi