# 认证系统 **本文档引用的文件** - [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) - [backend/alembic/versions/c3d5e7f9ab12_add_user_management_fields.py](file://backend/alembic/versions/c3d5e7f9ab12_add_user_management_fields.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/(auth)/forgot-password/page.tsx](file://frontend/app/(auth)/forgot-password/page.tsx) - [frontend/app/(auth)/reset-password/page.tsx](file://frontend/app/(auth)/reset-password/page.tsx) - [frontend/app/(auth)/verify-email/page.tsx](file://frontend/app/(auth)/verify-email/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) ## 更新摘要 **变更内容** - 新增完整的邮箱验证系统(忘记密码、重置密码、邮箱验证) - 新增密码重置令牌管理功能 - 新增用户资料修改功能(姓名、头像) - 新增密码更改功能(旧密码验证) - 新增数据库字段支持邮箱验证和密码重置 - 前端新增对应的认证页面和API接口 ## 目录 1. [简介](#简介) 2. [项目结构](#项目结构) 3. [核心组件](#核心组件) 4. [架构总览](#架构总览) 5. [详细组件分析](#详细组件分析) 6. [依赖分析](#依赖分析) 7. [性能考虑](#性能考虑) 8. [故障排查指南](#故障排查指南) 9. [结论](#结论) 10. [附录](#附录) ## 简介 本文件为 GEO 平台认证系统的实现文档,覆盖用户模型设计、JWT 令牌生成与验证、用户注册与登录流程、邮箱验证系统、密码重置机制、用户资料修改功能、依赖注入在认证中的应用(当前用户获取与权限检查)、安全最佳实践、令牌存储策略与会话管理,以及认证错误处理与调试指南。系统采用后端 FastAPI + SQLAlchemy 异步 ORM + PostgreSQL + Redis(配置项存在);前端使用 NextAuth.js 作为会话与令牌桥接层,通过自定义凭据提供者对接后端认证接口。 ## 项目结构 后端认证相关模块组织如下: - 模型层:用户模型定义与数据库表结构,包含邮箱验证和密码重置相关字段 - 模式层:Pydantic 数据校验模型(注册、登录、邮箱验证、密码重置、资料修改等) - 服务层:密码哈希/校验、JWT 编码/解码、注册与认证业务逻辑、邮箱验证和密码重置服务 - API 层:认证路由(注册、登录、当前用户、邮箱验证、密码重置、资料修改等)、依赖注入解析当前用户 - 配置与数据库:设置加载、异步数据库引擎与会话工厂 - 前端:NextAuth 配置、API 客户端、登录/注册/忘记密码/重置密码/邮箱验证页面 ```mermaid graph TB subgraph "后端" M["models/user.py
用户模型
新增邮箱验证字段"] S["schemas/auth.py
Pydantic 模型
新增邮箱验证Schema"] SV["services/auth.py
密码/JWT/业务
新增邮箱验证服务"] A["api/auth.py
认证路由
新增邮箱验证接口"] D["api/deps.py
依赖注入解析当前用户"] CFG["config.py
配置"] DB["database.py
异步数据库"] MAIN["main.py
应用入口"] MIG1["alembic 迁移
initial_migration.py"] MIG2["alembic 迁移
add_user_management_fields.py"] end subgraph "前端" FA["lib/api.ts
API 客户端
新增邮箱验证接口"] FN["lib/auth.ts
NextAuth 配置"] LR["app/(auth)/login/page.tsx
登录页"] RR["app/(auth)/register/page.tsx
注册页"] FP["app/(auth)/forgot-password/page.tsx
忘记密码页"] RP["app/(auth)/reset-password/page.tsx
重置密码页"] VE["app/(auth)/verify-email/page.tsx
邮箱验证页"] NAR["app/api/auth/[...nextauth]/route.ts
NextAuth 路由"] end LR --> FA RR --> FA FP --> FA RP --> FA VE --> FA FA --> A A --> SV SV --> DB D --> SV D --> DB MAIN --> A MAIN --> D CFG --> SV CFG --> DB MIG1 --> DB MIG2 --> DB FN --> NAR FA --> FN ``` **图表来源** - [backend/app/models/user.py:11-48](file://backend/app/models/user.py#L11-L48) - [backend/app/schemas/auth.py:8-62](file://backend/app/schemas/auth.py#L8-L62) - [backend/app/services/auth.py:74-175](file://backend/app/services/auth.py#L74-L175) - [backend/app/api/auth.py:65-115](file://backend/app/api/auth.py#L65-L115) - [backend/alembic/versions/c3d5e7f9ab12_add_user_management_fields.py:21-41](file://backend/alembic/versions/c3d5e7f9ab12_add_user_management_fields.py#L21-L41) - [frontend/lib/api.ts:55-84](file://frontend/lib/api.ts#L55-L84) - [frontend/app/(auth)/forgot-password/page.tsx:18-101](file://frontend/app/(auth)/forgot-password/page.tsx#L18-L101) - [frontend/app/(auth)/reset-password/page.tsx:19-148](file://frontend/app/(auth)/reset-password/page.tsx#L19-L148) - [frontend/app/(auth)/verify-email/page.tsx:19-155](file://frontend/app/(auth)/verify-email/page.tsx#L19-L155) **章节来源** - [backend/app/main.py:24-48](file://backend/app/main.py#L24-L48) - [backend/app/api/auth.py:65-115](file://backend/app/api/auth.py#L65-L115) - [backend/app/api/deps.py:16-43](file://backend/app/api/deps.py#L16-L43) - [backend/app/services/auth.py:74-175](file://backend/app/services/auth.py#L74-L175) - [backend/app/schemas/auth.py:8-62](file://backend/app/schemas/auth.py#L8-L62) - [backend/app/models/user.py:11-48](file://backend/app/models/user.py#L11-L48) - [backend/alembic/versions/c3d5e7f9ab12_add_user_management_fields.py:21-41](file://backend/alembic/versions/c3d5e7f9ab12_add_user_management_fields.py#L21-L41) - [frontend/lib/api.ts:55-84](file://frontend/lib/api.ts#L55-L84) - [frontend/app/(auth)/forgot-password/page.tsx:18-101](file://frontend/app/(auth)/forgot-password/page.tsx#L18-L101) - [frontend/app/(auth)/reset-password/page.tsx:19-148](file://frontend/app/(auth)/reset-password/page.tsx#L19-L148) - [frontend/app/(auth)/verify-email/page.tsx:19-155](file://frontend/app/(auth)/verify-email/page.tsx#L19-L155) ## 核心组件 - 用户模型与数据库表 - 字段与约束:UUID 主键、唯一邮箱、密码哈希、可选姓名、计划与配额、激活状态、邮箱验证状态、验证码及过期时间、密码重置令牌及过期时间、头像URL、管理员权限、时间戳等 - 关系:与查询与订阅的一对多级联删除 - Pydantic 模式 - 注册:邮箱、密码最小长度、姓名长度限制 - 登录:邮箱与密码 - 忘记密码:邮箱 - 重置密码:令牌与新密码 - 邮箱验证:邮箱与6位验证码 - 密码更改:旧密码与新密码 - 资料修改:可选姓名与头像URL - 响应:用户信息与创建时间 - 服务层 - 密码:bcrypt 上下文进行哈希与校验 - JWT:HS256 签名,基于配置的密钥与过期时长 - 注册:去重检查、哈希密码、持久化 - 认证:邮箱查找、密码校验 - 邮箱验证:验证码生成与校验、重置令牌生成与校验 - 资料修改:用户名与头像更新 - API 层 - 注册:接收注册体,调用服务,异常转 HTTP - 登录:认证失败返回未授权,成功签发访问令牌 - 当前用户:通过依赖注入解析当前用户 - 邮箱验证:发送验证码、验证验证码、重新发送验证码 - 密码重置:发送重置链接、重置密码、验证令牌 - 资料修改:更新用户资料 - 依赖注入 - OAuth2 密码流解析 Bearer 令牌,解码 JWT,按 ID 查询用户 - 配置与数据库 - 设置:数据库 URL、Redis URL、JWT 密钥、过期小时数等 - 引擎:异步 PostgreSQL 引擎与会话工厂 - 前端集成 - NextAuth 使用凭据提供者对接后端登录接口,以 JWT 策略存储会话 - 新增忘记密码、重置密码、邮箱验证页面 **章节来源** - [backend/app/models/user.py:11-48](file://backend/app/models/user.py#L11-L48) - [backend/alembic/versions/c3d5e7f9ab12_add_user_management_fields.py:21-41](file://backend/alembic/versions/c3d5e7f9ab12_add_user_management_fields.py#L21-L41) - [backend/app/schemas/auth.py:8-62](file://backend/app/schemas/auth.py#L8-L62) - [backend/app/services/auth.py:74-175](file://backend/app/services/auth.py#L74-L175) - [backend/app/api/auth.py:65-115](file://backend/app/api/auth.py#L65-L115) - [backend/app/api/deps.py:16-43](file://backend/app/api/deps.py#L16-L43) - [backend/app/config.py:4-17](file://backend/app/config.py#L4-L17) - [backend/app/database.py:6-29](file://backend/app/database.py#L6-L29) - [frontend/lib/auth.ts:5-56](file://frontend/lib/auth.ts#L5-L56) - [frontend/lib/api.ts:55-84](file://frontend/lib/api.ts#L55-L84) ## 架构总览 后端认证流程概览: - 前端登录/注册/忘记密码/重置密码/邮箱验证页面通过 API 客户端调用后端 /api/v1/auth/* 接口 - 后端服务层完成密码哈希/校验、JWT 签发、邮箱验证与密码重置服务 - 依赖注入层解析 Bearer 令牌并解析当前用户 - 前端 NextAuth 将后端返回的访问令牌存入本地会话(JWT 策略) ```mermaid 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/forgot-password/reset-password/verify-email" AUTH->>SVC : "authenticate_user/register_user/send_reset_link/verify_email/change_password/update_profile" 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:55-84](file://frontend/lib/api.ts#L55-L84) - [backend/app/api/auth.py:65-115](file://backend/app/api/auth.py#L65-L115) - [backend/app/services/auth.py:74-175](file://backend/app/services/auth.py#L74-L175) - [backend/app/api/deps.py:16-43](file://backend/app/api/deps.py#L16-L43) - [frontend/lib/auth.ts:13-32](file://frontend/lib/auth.ts#L13-L32) ## 详细组件分析 ### 用户模型与数据库表 - 字段与类型 - id: UUID 主键,默认生成 - email: 字符串,唯一且非空 - password_hash: 字符串,非空 - name: 字符串,可空 - plan: 字符串,默认 "free" - max_queries: 整数,默认 5 - is_active: 布尔,默认 true - email_verified: 布尔,默认 false(新增) - verification_code: 字符串,长度6,可空(新增) - verification_code_expires: 时间戳,可空(新增) - reset_token: 字符串,可空(新增) - reset_token_expires: 时间戳,可空(新增) - avatar_url: 字符串,可空(新增) - is_admin: 布尔,默认 false(新增) - created_at/updated_at: 时间戳,默认当前时间,更新时同步 - 约束与索引 - 唯一约束:email - 外键:查询与订阅表关联 users(id),级联删除 - 关系 - 一对多:用户 -> 查询、订阅 ```mermaid erDiagram USERS { uuid id PK string email UK string password_hash string name string plan int max_queries boolean is_active boolean email_verified string verification_code timestamp verification_code_expires string reset_token timestamp reset_token_expires string avatar_url boolean is_admin 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 : "拥有" ``` **图表来源** - [backend/app/models/user.py:11-48](file://backend/app/models/user.py#L11-L48) - [backend/alembic/versions/c3d5e7f9ab12_add_user_management_fields.py:21-41](file://backend/alembic/versions/c3d5e7f9ab12_add_user_management_fields.py#L21-L41) **章节来源** - [backend/app/models/user.py:11-48](file://backend/app/models/user.py#L11-L48) - [backend/alembic/versions/c3d5e7f9ab12_add_user_management_fields.py:21-41](file://backend/alembic/versions/c3d5e7f9ab12_add_user_management_fields.py#L21-L41) ### JWT 令牌生成与验证机制 - 签名算法:HS256 - 密钥来源:配置项 JWT_SECRET - 过期时间:配置项 JWT_EXPIRE_HOURS(小时) - 生成流程 - 以用户 ID 作为 sub,加入过期时间,使用 HS256 签名编码 - 验证流程 - 使用相同密钥与算法解码,提取 sub(用户 ID),再按 ID 查询用户 - 刷新策略 - 代码中未实现刷新令牌机制;当前为一次性访问令牌,过期后需重新登录 ```mermaid flowchart TD Start(["开始"]) --> Encode["构造载荷{sub, exp}
HS256 签名"] Encode --> Token["生成 access_token"] Token --> Verify["解码 access_token
校验算法与密钥"] Verify --> ParseSub["提取 sub(用户ID)"] ParseSub --> Lookup["按ID查询用户"] Lookup --> Done(["结束"]) ``` **图表来源** - [backend/app/services/auth.py:27-37](file://backend/app/services/auth.py#L27-L37) - [backend/app/api/deps.py:26-37](file://backend/app/api/deps.py#L26-L37) - [backend/app/config.py:9-10](file://backend/app/config.py#L9-L10) **章节来源** - [backend/app/services/auth.py:27-37](file://backend/app/services/auth.py#L27-L37) - [backend/app/api/deps.py:26-37](file://backend/app/api/deps.py#L26-L37) - [backend/app/config.py:9-10](file://backend/app/config.py#L9-L10) ### 用户注册流程 - 输入校验:邮箱格式、密码最小长度、姓名长度 - 去重检查:按邮箱查询,若已存在则抛出错误 - 密码处理:bcrypt 哈希 - 持久化:创建用户记录并提交事务 - 返回:用户信息(不含敏感字段) ```mermaid flowchart TD RStart(["注册入口"]) --> Validate["Pydantic 校验"] Validate --> CheckDup["按邮箱查重"] CheckDup --> Dup{"已存在?"} Dup -- 是 --> Err["抛出错误"] Dup -- 否 --> Hash["bcrypt 哈希密码"] Hash --> Persist["创建用户并提交"] Persist --> RDone(["返回用户信息"]) Err --> RDone ``` **图表来源** - [backend/app/schemas/auth.py:8-11](file://backend/app/schemas/auth.py#L8-L11) - [backend/app/services/auth.py:40-55](file://backend/app/services/auth.py#L40-L55) **章节来源** - [backend/app/schemas/auth.py:8-11](file://backend/app/schemas/auth.py#L8-L11) - [backend/app/services/auth.py:40-55](file://backend/app/services/auth.py#L40-L55) - [tests/test_auth.py:25-58](file://tests/test_auth.py#L25-L58) ### 登录认证过程 - 输入校验:邮箱与密码 - 用户查找:按邮箱查询 - 凭据验证:bcrypt 校验密码哈希 - 会话管理:签发访问令牌(Bearer) - 权限分配:当前用户解析后,后续路由可依赖注入获取用户 ```mermaid 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}" ``` **图表来源** - [backend/app/api/auth.py:42-57](file://backend/app/api/auth.py#L42-L57) - [backend/app/services/auth.py:58-71](file://backend/app/services/auth.py#L58-L71) **章节来源** - [backend/app/api/auth.py:42-57](file://backend/app/api/auth.py#L42-L57) - [backend/app/services/auth.py:58-71](file://backend/app/services/auth.py#L58-L71) - [tests/test_auth.py:61-84](file://tests/test_auth.py#L61-L84) ### 邮箱验证系统 - 验证码生成与发送 - 生成6位随机验证码(100000-999999) - 设置验证码过期时间为10分钟 - 日志输出验证码(模拟邮件发送) - 验证码校验 - 检查验证码是否匹配 - 验证过期时间(当前时间之前视为过期) - 成功后设置邮箱为已验证,清除验证码和过期时间 - 重置令牌管理 - 生成UUID作为重置令牌 - 设置令牌过期时间为1小时 - 日志输出重置链接(模拟邮件发送) - 重置密码流程 - 验证令牌是否存在且未过期 - 更新用户密码为新密码的哈希值 - 清除重置令牌和过期时间 ```mermaid flowchart TD Start(["邮箱验证入口"]) --> SendCode["生成6位验证码
设置10分钟过期"] SendCode --> StoreCode["存储验证码和过期时间"] StoreCode --> LogCode["日志输出验证码"] LogCode --> VerifyCode["验证验证码"] VerifyCode --> CheckExpire{"是否过期?"} CheckExpire -- 是 --> Fail["验证失败"] CheckExpire -- 否 --> SetVerified["设置邮箱已验证
清除验证码"] SetVerified --> Success["验证成功"] Start2(["密码重置入口"]) --> SendLink["生成UUID令牌
设置1小时过期"] SendLink --> StoreToken["存储重置令牌和过期时间"] StoreToken --> LogLink["日志输出重置链接"] LogLink --> ResetPwd["重置密码"] ResetPwd --> CheckToken{"令牌有效?"} CheckToken -- 否 --> Fail2["重置失败"] CheckToken -- 是 --> UpdatePwd["更新密码哈希
清除令牌"] UpdatePwd --> Success2["重置成功"] ``` **图表来源** - [backend/app/services/auth.py:74-140](file://backend/app/services/auth.py#L74-L140) **章节来源** - [backend/app/services/auth.py:74-140](file://backend/app/services/auth.py#L74-L140) - [backend/app/api/auth.py:65-90](file://backend/app/api/auth.py#L65-L90) ### 密码重置流程 - 忘记密码 - 接收邮箱地址 - 生成重置令牌并存储 - 发送重置链接到邮箱 - 重置密码 - 接收令牌和新密码 - 验证令牌有效性(存在且未过期) - 更新用户密码为新密码的哈希值 - 清除重置令牌 ```mermaid sequenceDiagram participant U as "用户" participant F as "忘记密码页面" participant A as "认证路由" participant S as "认证服务" participant DB as "数据库" U->>F : "输入邮箱" F->>A : "POST /forgot-password" A->>S : "send_reset_link(email)" S->>DB : "生成UUID令牌并存储" DB-->>S : "存储成功" S-->>A : "发送重置链接" A-->>F : "返回成功消息" U->>A : "POST /reset-password" A->>S : "reset_password(token, new_password)" S->>DB : "验证令牌并更新密码" DB-->>S : "更新成功" S-->>A : "返回成功" A-->>U : "返回重置成功" ``` **图表来源** - [frontend/app/(auth)/forgot-password/page.tsx:24-37](file://frontend/app/(auth)/forgot-password/page.tsx#L24-L37) - [frontend/app/(auth)/reset-password/page.tsx:41-65](file://frontend/app/(auth)/reset-password/page.tsx#L41-L65) - [backend/app/api/auth.py:65-76](file://backend/app/api/auth.py#L65-L76) - [backend/app/services/auth.py:110-140](file://backend/app/services/auth.py#L110-L140) **章节来源** - [frontend/app/(auth)/forgot-password/page.tsx:18-101](file://frontend/app/(auth)/forgot-password/page.tsx#L18-L101) - [frontend/app/(auth)/reset-password/page.tsx:19-148](file://frontend/app/(auth)/reset-password/page.tsx#L19-L148) - [backend/app/api/auth.py:65-76](file://backend/app/api/auth.py#L65-L76) - [backend/app/services/auth.py:110-140](file://backend/app/services/auth.py#L110-L140) ### 邮箱验证流程 - 验证码发送 - 接收邮箱地址 - 生成6位验证码并存储 - 发送验证码到邮箱 - 验证码验证 - 接收邮箱和验证码 - 验证验证码是否匹配且未过期 - 成功后设置邮箱为已验证 - 重新发送验证码 - 支持在验证码过期后重新发送 ```mermaid sequenceDiagram participant U as "用户" participant V as "邮箱验证页面" participant A as "认证路由" participant S as "认证服务" participant DB as "数据库 U->>V : "输入邮箱" V->>A : "POST /resend-verification" A->>S : "send_verification_code(email)" S->>DB : "生成验证码并存储" DB-->>S : "存储成功" S-->>A : "发送验证码" A-->>V : "返回成功消息" U->>V : "输入验证码" V->>A : "POST /verify-email" A->>S : "verify_email(email, code)" S->>DB : "验证验证码" DB-->>S : "验证结果" S-->>A : "返回验证结果" A-->>U : "返回验证结果" ``` **图表来源** - [frontend/app/(auth)/verify-email/page.tsx:37-69](file://frontend/app/(auth)/verify-email/page.tsx#L37-L69) - [backend/app/api/auth.py:79-90](file://backend/app/api/auth.py#L79-L90) - [backend/app/services/auth.py:74-107](file://backend/app/services/auth.py#L74-L107) **章节来源** - [frontend/app/(auth)/verify-email/page.tsx:19-155](file://frontend/app/(auth)/verify-email/page.tsx#L19-L155) - [backend/app/api/auth.py:79-90](file://backend/app/api/auth.py#L79-L90) - [backend/app/services/auth.py:74-107](file://backend/app/services/auth.py#L74-L107) ### 用户资料修改功能 - 资料更新 - 支持更新姓名和头像URL - 仅更新提供的字段 - 返回更新后的用户信息 - 密码更改 - 需要当前用户的认证 - 验证旧密码正确性 - 更新为新密码的哈希值 ```mermaid flowchart TD Start(["资料修改入口"]) --> CheckFields["检查提供的字段"] CheckFields --> UpdateName{"是否提供姓名?"} UpdateName -- 是 --> SetName["更新姓名"] UpdateName -- 否 --> CheckAvatar{"是否提供头像URL?"} SetName --> CheckAvatar CheckAvatar -- 是 --> SetAvatar["更新头像URL"] CheckAvatar -- 否 --> Commit["提交事务"] SetAvatar --> Commit Commit --> Refresh["刷新用户信息"] Refresh --> Done(["返回更新后的用户"]) Start2(["密码更改入口"]) --> VerifyOld["验证旧密码"] VerifyOld --> CheckOld{"旧密码正确?"} CheckOld -- 否 --> Fail["更改失败"] CheckOld -- 是 --> HashNew["哈希新密码"] HashNew --> UpdatePwd["更新密码"] UpdatePwd --> Commit2["提交事务"] Commit2 --> Done2(["返回成功"]) ``` **图表来源** - [backend/app/schemas/auth.py:33-41](file://backend/app/schemas/auth.py#L33-L41) - [backend/app/services/auth.py:143-175](file://backend/app/services/auth.py#L143-L175) **章节来源** - [backend/app/schemas/auth.py:33-41](file://backend/app/schemas/auth.py#L33-L41) - [backend/app/services/auth.py:143-175](file://backend/app/services/auth.py#L143-L175) - [backend/app/api/auth.py:93-115](file://backend/app/api/auth.py#L93-L115) ### 依赖注入与当前用户获取 - OAuth2 密码流:tokenUrl 指向 /api/v1/auth/login - 依赖函数 get_current_user: - 从 Authorization 头解析 Bearer 令牌 - 调用 verify_token 解码 - 提取 sub(UUID),查询用户 - 不存在或解码失败返回未授权 - 在路由中使用:Depends(get_current_user) 获取当前用户 ```mermaid 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 ``` **图表来源** - [backend/app/api/deps.py:16-43](file://backend/app/api/deps.py#L16-L43) - [backend/app/services/auth.py:35-37](file://backend/app/services/auth.py#L35-L37) **章节来源** - [backend/app/api/deps.py:16-43](file://backend/app/api/deps.py#L16-L43) - [backend/app/api/auth.py:60-62](file://backend/app/api/auth.py#L60-L62) - [tests/test_auth.py:87-104](file://tests/test_auth.py#L87-L104) ### 前端集成与会话管理 - NextAuth 配置 - 凭据提供者:向后端发送邮箱/密码 - authorize:调用后端登录接口,成功则将 access_token 与用户信息写入 JWT - 会话策略:jwt - 回调:将 access_token 与用户 ID 写入 token/session - 页面交互 - 登录页:收集邮箱/密码,调用 signIn("credentials"),错误提示"邮箱或密码错误" - 注册页:校验两次密码一致,调用后端注册,随后自动登录 - 忘记密码页:输入邮箱,调用后端发送重置链接 - 重置密码页:从URL获取令牌,输入新密码,调用后端重置密码 - 邮箱验证页:输入验证码,调用后端验证,支持重新发送验证码 - API 客户端 - 自动在请求头添加 Authorization: Bearer token - 统一错误处理与响应解析 - 新增邮箱验证相关接口 ```mermaid sequenceDiagram participant L as "登录页" participant FP as "忘记密码页" participant RP as "重置密码页" participant VE 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" FP->>API : "POST /api/v1/auth/forgot-password" RP->>API : "POST /api/v1/auth/reset-password" VE->>API : "POST /api/v1/auth/verify-email" ``` **图表来源** - [frontend/app/(auth)/login/page.tsx:26-42](file://frontend/app/(auth)/login/page.tsx#L26-L42) - [frontend/app/(auth)/forgot-password/page.tsx:24-37](file://frontend/app/(auth)/forgot-password/page.tsx#L24-L37) - [frontend/app/(auth)/reset-password/page.tsx:41-65](file://frontend/app/(auth)/reset-password/page.tsx#L41-L65) - [frontend/app/(auth)/verify-email/page.tsx:37-69](file://frontend/app/(auth)/verify-email/page.tsx#L37-L69) - [frontend/lib/auth.ts:13-32](file://frontend/lib/auth.ts#L13-L32) - [frontend/lib/api.ts:55-84](file://frontend/lib/api.ts#L55-L84) - [frontend/app/api/auth/[...nextauth]/route.ts:1-6](file://frontend/app/api/auth/[...nextauth]/route.ts#L1-L6) **章节来源** - [frontend/lib/auth.ts:5-56](file://frontend/lib/auth.ts#L5-L56) - [frontend/app/(auth)/login/page.tsx:19-93](file://frontend/app/(auth)/login/page.tsx#L19-L93) - [frontend/app/(auth)/register/page.tsx:20-128](file://frontend/app/(auth)/register/page.tsx#L20-L128) - [frontend/app/(auth)/forgot-password/page.tsx:18-101](file://frontend/app/(auth)/forgot-password/page.tsx#L18-L101) - [frontend/app/(auth)/reset-password/page.tsx:19-148](file://frontend/app/(auth)/reset-password/page.tsx#L19-L148) - [frontend/app/(auth)/verify-email/page.tsx:19-155](file://frontend/app/(auth)/verify-email/page.tsx#L19-L155) - [frontend/lib/api.ts:55-84](file://frontend/lib/api.ts#L55-L84) - [frontend/app/api/auth/[...nextauth]/route.ts:1-6](file://frontend/app/api/auth/[...nextauth]/route.ts#L1-L6) ## 依赖分析 - 组件耦合 - API 层依赖服务层与数据库依赖注入 - 服务层依赖配置与密码/JWT工具 - 依赖注入层依赖服务层的令牌验证与数据库查询 - 外部依赖 - 数据库:PostgreSQL(异步驱动) - 缓存:Redis(配置项存在,当前认证未直接使用) - 加密:bcrypt(passlib) - JWT:jose(python-jose) - 可能的循环依赖 - 当前模块间无明显循环导入 ```mermaid 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-115](file://backend/app/api/auth.py#L1-L115) - [backend/app/api/deps.py:1-43](file://backend/app/api/deps.py#L1-L43) - [backend/app/services/auth.py:1-175](file://backend/app/services/auth.py#L1-L175) - [backend/app/database.py:1-29](file://backend/app/database.py#L1-L29) - [backend/app/config.py:1-17](file://backend/app/config.py#L1-L17) - [backend/app/main.py:1-48](file://backend/app/main.py#L1-L48) **章节来源** - [backend/app/api/auth.py:1-115](file://backend/app/api/auth.py#L1-L115) - [backend/app/api/deps.py:1-43](file://backend/app/api/deps.py#L1-L43) - [backend/app/services/auth.py:1-175](file://backend/app/services/auth.py#L1-L175) - [backend/app/database.py:1-29](file://backend/app/database.py#L1-L29) - [backend/app/config.py:1-17](file://backend/app/config.py#L1-L17) - [backend/app/main.py:1-48](file://backend/app/main.py#L1-L48) ## 性能考虑 - 数据库连接 - 使用异步引擎与会话工厂,减少阻塞 - 会话配置:关闭自动提交/刷写,expire_on_commit 控制回话生命周期 - 密码哈希 - bcrypt 默认成本适中,建议在生产环境评估成本参数 - JWT - HS256 为对称算法,计算开销低;注意密钥安全与轮换 - 网络与缓存 - Redis 配置存在但未在认证中使用,可考虑用于令牌黑名单或会话缓存(需额外实现) - 邮箱验证 - 验证码和重置令牌都有过期时间控制,避免长期有效的安全风险 ## 故障排查指南 - 常见错误与定位 - 注册重复邮箱:后端抛出值错误,测试断言 400 与错误详情 - 登录凭据错误:后端返回 401 未授权,测试断言错误详情 - 未携带或无效令牌:依赖注入解析失败,返回 401 - 验证码无效或过期:邮箱验证接口返回 400 错误 - 重置令牌无效或过期:密码重置接口返回 400 错误 - 旧密码错误:密码更改接口返回 400 错误 - 前端登录失败 - 检查 NextAuth authorize 是否收到 access_token - 确认后端 /api/v1/auth/login 返回结构 - API 请求失败 - 检查 Authorization 头是否正确附加 Bearer 令牌 - 统一错误处理会将后端错误消息透传 - 邮箱验证问题 - 检查验证码是否正确发送到用户邮箱 - 确认验证码过期时间设置合理 - 验证重置链接是否正确生成和发送 **章节来源** - [tests/test_auth.py:25-104](file://tests/test_auth.py#L25-L104) - [backend/app/api/auth.py:65-115](file://backend/app/api/auth.py#L65-L115) - [backend/app/api/deps.py:20-41](file://backend/app/api/deps.py#L20-L41) - [frontend/lib/api.ts:17-39](file://frontend/lib/api.ts#L17-L39) ## 结论 本认证系统采用清晰的分层设计:模式层负责输入校验,服务层封装密码与令牌逻辑、邮箱验证与密码重置服务,API 层提供 REST 接口,依赖注入统一解析当前用户。前端通过 NextAuth 与后端无缝衔接,形成完整的登录/注册与会话管理闭环。新增的邮箱验证系统提供了完整的忘记密码、重置密码和邮箱验证功能,增强了用户体验和安全性。当前实现具备良好的扩展性,建议在生产环境中强化令牌刷新、黑名单与密钥轮换策略,并结合 Redis 实现更完善的会话与令牌治理。 ## 附录 - 安全最佳实践 - 密钥管理:确保 JWT_SECRET 不泄露,定期轮换 - 传输安全:启用 HTTPS,避免明文传输 - 输入校验:前端与后端双重校验,防止越界与注入 - 令牌策略:考虑引入刷新令牌与黑名单机制 - 会话策略:前端 JWT 策略简单可靠,建议配合 HttpOnly Cookie 与 SameSite 策略进一步加固 - 邮箱验证:验证码和重置令牌都有过期时间控制,避免长期有效的安全风险 - 密码安全:密码最小长度限制,使用强哈希算法 - 令牌存储策略 - 前端:NextAuth JWT 存储在浏览器会话中 - 后端:当前未实现服务端会话存储,建议结合 Redis 实现 - 权限检查 - 当前仅实现"当前用户"解析;如需细粒度权限,可在服务层增加角色/资源权限映射并在路由中校验 - 数据库迁移 - 新增邮箱验证相关字段:email_verified、verification_code、verification_code_expires、reset_token、reset_token_expires、avatar_url、is_admin - 所有新增字段都有适当的默认值和约束