21 KiB
21 KiB
认证系统
**本文档引用的文件** - [backend/app/models/user.py](file://backend/app/models/user.py) - [backend/app/schemas/auth.py](file://backend/app/schemas/auth.py) - [backend/app/services/auth.py](file://backend/app/services/auth.py) - [backend/app/api/auth.py](file://backend/app/api/auth.py) - [backend/app/api/deps.py](file://backend/app/api/deps.py) - [backend/app/config.py](file://backend/app/config.py) - [backend/app/database.py](file://backend/app/database.py) - [backend/app/main.py](file://backend/app/main.py) - [backend/alembic/versions/488d0bd5ab01_initial_migration.py](file://backend/alembic/versions/488d0bd5ab01_initial_migration.py) - [frontend/lib/auth.ts](file://frontend/lib/auth.ts) - [frontend/lib/api.ts](file://frontend/lib/api.ts) - [frontend/app/(auth)/login/page.tsx](file://frontend/app/(auth)/login/page.tsx) - [frontend/app/(auth)/register/page.tsx](file://frontend/app/(auth)/register/page.tsx) - [frontend/app/api/auth/[...nextauth]/route.ts](file://frontend/app/api/auth/[...nextauth]/route.ts) - [tests/test_auth.py](file://tests/test_auth.py)目录
简介
本文件为 GEO 平台认证系统的实现文档,覆盖用户模型设计、JWT 令牌生成与验证、用户注册与登录流程、依赖注入在认证中的应用(当前用户获取与权限检查)、安全最佳实践、令牌存储策略与会话管理,以及认证错误处理与调试指南。系统采用后端 FastAPI + SQLAlchemy 异步 ORM + PostgreSQL + Redis(配置项存在);前端使用 NextAuth.js 作为会话与令牌桥接层,通过自定义凭据提供者对接后端认证接口。
项目结构
后端认证相关模块组织如下:
- 模型层:用户模型定义与数据库表结构
- 模式层:Pydantic 数据校验模型(注册、登录、响应)
- 服务层:密码哈希/校验、JWT 编码/解码、注册与认证业务逻辑
- API 层:认证路由(注册、登录、当前用户)、依赖注入解析当前用户
- 配置与数据库:设置加载、异步数据库引擎与会话工厂
- 前端:NextAuth 配置、API 客户端、登录/注册页面
graph TB
subgraph "后端"
M["models/user.py<br/>用户模型"]
S["schemas/auth.py<br/>Pydantic 模型"]
SV["services/auth.py<br/>密码/JWT/业务"]
A["api/auth.py<br/>认证路由"]
D["api/deps.py<br/>依赖注入解析当前用户"]
CFG["config.py<br/>配置"]
DB["database.py<br/>异步数据库"]
MAIN["main.py<br/>应用入口"]
MIG["alembic 迁移<br/>initial_migration.py"]
end
subgraph "前端"
FA["lib/api.ts<br/>API 客户端"]
FN["lib/auth.ts<br/>NextAuth 配置"]
LR["app/(auth)/login/page.tsx<br/>登录页"]
RR["app/(auth)/register/page.tsx<br/>注册页"]
NAR["app/api/auth/[...nextauth]/route.ts<br/>NextAuth 路由"]
end
LR --> FA
RR --> FA
FA --> A
A --> SV
SV --> DB
D --> SV
D --> DB
MAIN --> A
MAIN --> D
CFG --> SV
CFG --> DB
MIG --> DB
FN --> NAR
FA --> FN
图表来源
- backend/app/models/user.py:11-41
- backend/app/schemas/auth.py:7-34
- backend/app/services/auth.py:16-69
- backend/app/api/auth.py:13-43
- backend/app/api/deps.py:16-43
- backend/app/config.py:4-17
- backend/app/database.py:6-29
- backend/app/main.py:24-48
- backend/alembic/versions/488d0bd5ab01_initial_migration.py:21-128
- frontend/lib/api.ts:23-36
- frontend/lib/auth.ts:5-56
- frontend/app/(auth)/login/page.tsx:19-93
- frontend/app/(auth)/register/page.tsx:20-128
- frontend/app/api/auth/[...nextauth]/route.ts:1-6
章节来源
- backend/app/main.py:24-48
- backend/app/api/auth.py:13-43
- backend/app/api/deps.py:16-43
- backend/app/services/auth.py:16-69
- backend/app/schemas/auth.py:7-34
- backend/app/models/user.py:11-41
- backend/app/config.py:4-17
- backend/app/database.py:6-29
- backend/alembic/versions/488d0bd5ab01_initial_migration.py:21-128
- frontend/lib/api.ts:23-36
- frontend/lib/auth.ts:5-56
- frontend/app/(auth)/login/page.tsx:19-93
- frontend/app/(auth)/register/page.tsx:20-128
- frontend/app/api/auth/[...nextauth]/route.ts:1-6
核心组件
- 用户模型与数据库表
- 字段与约束:UUID 主键、唯一邮箱、密码哈希、可选姓名、计划与配额、激活状态、时间戳等
- 关系:与查询与订阅的一对多级联删除
- Pydantic 模式
- 注册:邮箱、密码最小长度、姓名长度限制
- 登录:邮箱与密码
- 响应:用户信息与创建时间
- 服务层
- 密码:bcrypt 上下文进行哈希与校验
- JWT:HS256 签名,基于配置的密钥与过期时长
- 注册:去重检查、哈希密码、持久化
- 认证:邮箱查找、密码校验
- API 层
- 注册:接收注册体,调用服务,异常转 HTTP
- 登录:认证失败返回未授权,成功签发访问令牌
- 当前用户:通过依赖注入解析当前用户
- 依赖注入
- OAuth2 密码流解析 Bearer 令牌,解码 JWT,按 ID 查询用户
- 配置与数据库
- 设置:数据库 URL、Redis URL、JWT 密钥、过期小时数等
- 引擎:异步 PostgreSQL 引擎与会话工厂
- 前端集成
- NextAuth 使用凭据提供者对接后端登录接口,以 JWT 策略存储会话
章节来源
- backend/app/models/user.py:11-41
- backend/alembic/versions/488d0bd5ab01_initial_migration.py:21-37
- backend/app/schemas/auth.py:7-34
- backend/app/services/auth.py:16-69
- backend/app/api/auth.py:13-43
- backend/app/api/deps.py:16-43
- backend/app/config.py:4-17
- backend/app/database.py:6-29
- frontend/lib/auth.ts:5-56
- frontend/lib/api.ts:23-36
架构总览
后端认证流程概览:
- 前端登录/注册页面通过 API 客户端调用后端 /api/v1/auth/* 接口
- 后端服务层完成密码哈希/校验与 JWT 签发
- 依赖注入层解析 Bearer 令牌并解析当前用户
- 前端 NextAuth 将后端返回的访问令牌存入本地会话(JWT 策略)
sequenceDiagram
participant FE as "前端页面"
participant API as "API 客户端"
participant AUTH as "后端认证路由"
participant SVC as "认证服务"
participant DB as "数据库"
participant DEP as "依赖注入"
FE->>API : "提交登录/注册表单"
API->>AUTH : "POST /api/v1/auth/login 或 /register"
AUTH->>SVC : "authenticate_user/register_user"
SVC->>DB : "查询/插入用户"
DB-->>SVC : "返回用户/结果"
SVC-->>AUTH : "返回用户或令牌"
AUTH-->>API : "返回 {access_token, user}"
API-->>FE : "保存令牌并跳转"
FE->>DEP : "后续请求携带 Authorization : Bearer"
DEP->>SVC : "verify_token 解码"
SVC-->>DEP : "payload(sub)"
DEP->>DB : "按ID查询用户"
DB-->>DEP : "返回用户"
DEP-->>FE : "注入当前用户"
图表来源
- frontend/lib/api.ts:23-36
- backend/app/api/auth.py:13-43
- backend/app/services/auth.py:37-69
- backend/app/api/deps.py:16-43
- frontend/lib/auth.ts:13-32
详细组件分析
用户模型与数据库表
- 字段与类型
- id: UUID 主键,默认生成
- email: 字符串,唯一且非空
- password_hash: 字符串,非空
- name: 字符串,可空
- plan: 字符串,默认 "free"
- max_queries: 整数,默认 5
- is_active: 布尔,默认 true
- created_at/updated_at: 时间戳,默认当前时间,更新时同步
- 约束与索引
- 唯一约束:email
- 外键:查询与订阅表关联 users(id),级联删除
- 关系
- 一对多:用户 -> 查询、订阅
erDiagram
USERS {
uuid id PK
string email UK
string password_hash
string name
string plan
int max_queries
boolean is_active
timestamp created_at
timestamp updated_at
}
QUERIES {
uuid id PK
uuid user_id FK
string keyword
string target_brand
jsonb brand_aliases
jsonb platforms
string frequency
string status
timestamp last_queried_at
timestamp next_query_at
timestamp created_at
timestamp updated_at
}
SUBSCRIPTIONS {
uuid id PK
uuid user_id FK
string plan
string status
date start_date
date end_date
numeric amount
string payment_method
string payment_id
timestamp created_at
}
USERS ||--o{ QUERIES : "拥有"
USERS ||--o{ SUBSCRIPTIONS : "拥有"
图表来源
章节来源
JWT 令牌生成与验证机制
- 签名算法:HS256
- 密钥来源:配置项 JWT_SECRET
- 过期时间:配置项 JWT_EXPIRE_HOURS(小时)
- 生成流程
- 以用户 ID 作为 sub,加入过期时间,使用 HS256 签名编码
- 验证流程
- 使用相同密钥与算法解码,提取 sub(用户 ID),再按 ID 查询用户
- 刷新策略
- 代码中未实现刷新令牌机制;当前为一次性访问令牌,过期后需重新登录
flowchart TD
Start(["开始"]) --> Encode["构造载荷{sub, exp}<br/>HS256 签名"]
Encode --> Token["生成 access_token"]
Token --> Verify["解码 access_token<br/>校验算法与密钥"]
Verify --> ParseSub["提取 sub(用户ID)"]
ParseSub --> Lookup["按ID查询用户"]
Lookup --> Done(["结束"])
图表来源
章节来源
用户注册流程
- 输入校验:邮箱格式、密码最小长度、姓名长度
- 去重检查:按邮箱查询,若已存在则抛出错误
- 密码处理:bcrypt 哈希
- 持久化:创建用户记录并提交事务
- 返回:用户信息(不含敏感字段)
flowchart TD
RStart(["注册入口"]) --> Validate["Pydantic 校验"]
Validate --> CheckDup["按邮箱查重"]
CheckDup --> Dup{"已存在?"}
Dup -- 是 --> Err["抛出错误"]
Dup -- 否 --> Hash["bcrypt 哈希密码"]
Hash --> Persist["创建用户并提交"]
Persist --> RDone(["返回用户信息"])
Err --> RDone
图表来源
章节来源
登录认证过程
- 输入校验:邮箱与密码
- 用户查找:按邮箱查询
- 凭据验证:bcrypt 校验密码哈希
- 会话管理:签发访问令牌(Bearer)
- 权限分配:当前用户解析后,后续路由可依赖注入获取用户
sequenceDiagram
participant C as "客户端"
participant A as "认证路由"
participant S as "认证服务"
participant DB as "数据库"
C->>A : "POST /login {email,password}"
A->>S : "authenticate_user(email,password)"
S->>DB : "select * from users where email=?"
DB-->>S : "用户或空"
S->>S : "bcrypt 校验"
S-->>A : "用户或None"
A-->>C : "{access_token,bearer,user}"
图表来源
章节来源
依赖注入与当前用户获取
- OAuth2 密码流:tokenUrl 指向 /api/v1/auth/login
- 依赖函数 get_current_user:
- 从 Authorization 头解析 Bearer 令牌
- 调用 verify_token 解码
- 提取 sub(UUID),查询用户
- 不存在或解码失败返回未授权
- 在路由中使用:Depends(get_current_user) 获取当前用户
flowchart TD
DS(["依赖注入入口"]) --> Parse["OAuth2 解析 Bearer"]
Parse --> Decode["verify_token 解码"]
Decode --> Sub{"sub 是否存在?"}
Sub -- 否 --> Unauth["401 未授权"]
Sub -- 是 --> Query["按ID查询用户"]
Query --> Found{"找到用户?"}
Found -- 否 --> Unauth
Found -- 是 --> Inject["注入当前用户"]
Inject --> Done(["下游路由可用"])
Unauth --> Done
图表来源
章节来源
前端集成与会话管理
- NextAuth 配置
- 凭据提供者:向后端发送邮箱/密码
- authorize:调用后端登录接口,成功则将 access_token 与用户信息写入 JWT
- 会话策略:jwt
- 回调:将 access_token 与用户 ID 写入 token/session
- 页面交互
- 登录页:收集邮箱/密码,调用 signIn("credentials"),错误提示“邮箱或密码错误”
- 注册页:校验两次密码一致,调用后端注册,随后自动登录
- API 客户端
- 自动在请求头添加 Authorization: Bearer token
- 统一错误处理与响应解析
sequenceDiagram
participant L as "登录页"
participant NA as "NextAuth"
participant API as "后端登录"
participant CL as "API 客户端"
L->>NA : "signIn('credentials',{email,password})"
NA->>API : "POST /api/v1/auth/login"
API-->>NA : "{access_token,user}"
NA->>NA : "回调写入 token.accessToken/token.id"
NA-->>L : "登录成功,跳转仪表盘"
L->>CL : "后续请求携带 Authorization : Bearer"
图表来源
- frontend/app/(auth)/login/page.tsx:26-42
- frontend/app/(auth)/register/page.tsx:29-55
- frontend/lib/auth.ts:13-32
- frontend/lib/api.ts:12-21
- frontend/app/api/auth/[...nextauth]/route.ts:1-6
章节来源
- frontend/lib/auth.ts:5-56
- frontend/app/(auth)/login/page.tsx:19-93
- frontend/app/(auth)/register/page.tsx:20-128
- frontend/lib/api.ts:23-36
- frontend/app/api/auth/[...nextauth]/route.ts:1-6
依赖分析
- 组件耦合
- API 层依赖服务层与数据库依赖注入
- 服务层依赖配置与密码/JWT工具
- 依赖注入层依赖服务层的令牌验证与数据库查询
- 外部依赖
- 数据库:PostgreSQL(异步驱动)
- 缓存:Redis(配置项存在,当前认证未直接使用)
- 加密:bcrypt(passlib)
- JWT:jose(python-jose)
- 可能的循环依赖
- 当前模块间无明显循环导入
graph LR
API["api/auth.py"] --> SVC["services/auth.py"]
API --> DB["database.py"]
DEP["api/deps.py"] --> SVC
DEP --> DB
SVC --> CFG["config.py"]
SVC --> DB
MAIN["main.py"] --> API
MAIN --> DEP
图表来源
- backend/app/api/auth.py:1-43
- backend/app/api/deps.py:1-43
- backend/app/services/auth.py:1-69
- backend/app/database.py:1-29
- backend/app/config.py:1-17
- backend/app/main.py:1-48
章节来源
- backend/app/api/auth.py:1-43
- backend/app/api/deps.py:1-43
- backend/app/services/auth.py:1-69
- backend/app/database.py:1-29
- backend/app/config.py:1-17
- backend/app/main.py:1-48
性能考虑
- 数据库连接
- 使用异步引擎与会话工厂,减少阻塞
- 会话配置:关闭自动提交/刷写,expire_on_commit 控制回话生命周期
- 密码哈希
- bcrypt 默认成本适中,建议在生产环境评估成本参数
- JWT
- HS256 为对称算法,计算开销低;注意密钥安全与轮换
- 网络与缓存
- Redis 配置存在但未在认证中使用,可考虑用于令牌黑名单或会话缓存(需额外实现)
故障排查指南
- 常见错误与定位
- 注册重复邮箱:后端抛出值错误,测试断言 400 与错误详情
- 登录凭据错误:后端返回 401 未授权,测试断言错误详情
- 未携带或无效令牌:依赖注入解析失败,返回 401
- 前端登录失败
- 检查 NextAuth authorize 是否收到 access_token
- 确认后端 /api/v1/auth/login 返回结构
- API 请求失败
- 检查 Authorization 头是否正确附加 Bearer 令牌
- 统一错误处理会将后端错误消息透传
章节来源
- tests/test_auth.py:25-84
- backend/app/api/auth.py:17-30
- backend/app/api/deps.py:20-41
- frontend/lib/api.ts:16-21
结论
本认证系统采用清晰的分层设计:模式层负责输入校验,服务层封装密码与令牌逻辑,API 层提供 REST 接口,依赖注入统一解析当前用户。前端通过 NextAuth 与后端无缝衔接,形成完整的登录/注册与会话管理闭环。当前实现具备良好的扩展性,建议在生产环境中强化令牌刷新、黑名单与密钥轮换策略,并结合 Redis 实现更完善的会话与令牌治理。
附录
- 安全最佳实践
- 密钥管理:确保 JWT_SECRET 不泄露,定期轮换
- 传输安全:启用 HTTPS,避免明文传输
- 输入校验:前端与后端双重校验,防止越界与注入
- 令牌策略:考虑引入刷新令牌与黑名单机制
- 会话策略:前端 JWT 策略简单可靠,建议配合 HttpOnly Cookie 与 SameSite 策略进一步加固
- 令牌存储策略
- 前端:NextAuth JWT 存储在浏览器会话中
- 后端:当前未实现服务端会话存储,建议结合 Redis 实现
- 权限检查
- 当前仅实现“当前用户”解析;如需细粒度权限,可在服务层增加角色/资源权限映射并在路由中校验