diff --git a/docs/01-架构设计.md b/docs/01-架构设计.md index 02826e4..9f15d3b 100644 --- a/docs/01-架构设计.md +++ b/docs/01-架构设计.md @@ -376,6 +376,19 @@ TieredManager ## 认证设计 +采用 **JWT 双 token 轮转认证机制**,结合 bcrypt 密码哈希和 Refresh Token Rotation 安全策略。详细设计见 [鉴权体系设计](./12-鉴权体系设计.md)。 + +### 核心组件 + +| 组件 | 职责 | +|------|------| +| TokenManager | JWT 生成与验证(HS256 算法) | +| AuthService | 认证业务逻辑(注册/登录/刷新/登出) | +| AuthMiddleware | Gin 中间件,校验 access_token 并注入用户信息 | +| PasswordUtil | bcrypt 密码哈希(cost=10) | + +### 认证流程 + ```mermaid sequenceDiagram participant C as 客户端 @@ -408,9 +421,45 @@ sequenceDiagram G-->>C: {access_token, refresh_token} ``` -**Token 策略**:access_token 15 分钟有效,refresh_token 7 天有效。refresh 时旧 token 失效(轮转),防止重放攻击。 +### Token 策略 -**WebSocket 认证**:连接地址 `ws://host/ws?token=&conversation_id=`。HTTP Upgrade 前校验 token,失败返回 401。 +- **access_token**:15 分钟有效,用于 API 认证和 WebSocket 连接 +- **refresh_token**:7 天有效,用于刷新 access_token +- **Refresh Token Rotation**:每次 refresh 都生成新的 token pair,旧 refresh_token 立即失效 +- **复用检测**:检测到已删除的 refresh_token 被复用时,吊销该用户的所有 refresh_token + +### 安全机制 + +1. **密码安全**:bcrypt 算法(cost=10),自动生成盐值,防彩虹表攻击 +2. **Token 安全**: + - access_token 短有效期(15 分钟),降低泄露风险 + - refresh_token 使用 SHA256 哈希存储,不存储原始 token + - Refresh Token Rotation 防重放攻击 + - 复用检测 + 自动吊销机制 +3. **传输安全**:HTTPS 强制,CORS 限制,HttpOnly Cookie 存储 refresh_token +4. **防攻击策略**: + - 防暴力破解:可选速率限制 + - 防枚举攻击:统一错误信息 + - 防 Token 泄露:复用检测 + 自动吊销 + +### WebSocket 认证 + +连接地址:`ws://host/ws?token=&conversation_id=` + +- HTTP Upgrade 前校验 token +- 校验失败返回 401 Unauthorized +- 校验成功后,user_id 和 username 注入到连接上下文 + +### 配置 + +```yaml +auth: + jwt_secret: "" # JWT 签名密钥(必须通过 CAMTALK_AUTH_JWT_SECRET 环境变量设置) + access_ttl: 15 # access_token 有效期(分钟) + refresh_ttl: 10080 # refresh_token 有效期(分钟,7天) +``` + +> **安全要求**:`JWT_SECRET` **必须**通过环境变量设置,不能写入配置文件。生产环境使用 `openssl rand -hex 32` 生成随机密钥。 ## 部署架构 diff --git a/docs/02-接口文档.md b/docs/02-接口文档.md index 820a0c4..2d06269 100644 --- a/docs/02-接口文档.md +++ b/docs/02-接口文档.md @@ -279,13 +279,27 @@ Client Server #### 认证方式 -需要认证的接口在请求头携带 JWT access token: +采用 **JWT 双 token 轮转认证机制**。详细设计见 [鉴权体系设计](./12-鉴权体系设计.md)。 +**Token 类型**: +- **access_token**:短期令牌(15 分钟),用于 API 认证和 WebSocket 连接 +- **refresh_token**:长期令牌(7 天),用于刷新 access_token + +**请求头格式**: ``` Authorization: Bearer ``` -未认证或 token 过期时返回 `401 Unauthorized`。 +**认证流程**: +1. 用户登录后获取 access_token + refresh_token +2. 请求受保护接口时携带 access_token +3. access_token 过期时,使用 refresh_token 刷新获取新的 token pair +4. refresh_token 采用轮转机制,每次刷新后旧 token 失效 + +**WebSocket 认证**: +- 连接地址:`ws://host/ws?token=&conversation_id=` +- HTTP Upgrade 前校验 token +- 校验失败返回 401 Unauthorized #### 错误响应格式 @@ -380,7 +394,7 @@ interface LoginRequest { | 400 | `INVALID_INPUT` | 请求参数缺失或格式错误 | | 401 | `INVALID_CREDENTIALS` | 用户名或密码错误 | -#### 刷新 Token +#### 刷新 Token(Refresh Token Rotation) ``` POST /api/auth/refresh @@ -397,12 +411,56 @@ interface RefreshRequest { **成功响应** `200 OK`:同 `AuthResponse` 结构(返回新的 access_token + refresh_token,旧 refresh_token 失效——Token 轮转)。 +**安全机制**: +- **Token 轮转**:每次 refresh 都会生成新的 token pair,旧 refresh_token 立即失效 +- **复用检测**:如果检测到已删除的 refresh_token 被复用,立即吊销该用户的所有 refresh_token +- **强制重新登录**:吊销后,该用户所有设备都需要重新登录 + **错误响应**: | 状态码 | code | 场景 | |--------|------|------| | 401 | `INVALID_TOKEN` | refresh_token 无效或已过期 | +**前端集成示例**: + +```typescript +// axios 响应拦截器 +api.interceptors.response.use( + (response) => response, + async (error) => { + const originalRequest = error.config; + + // 如果是 401 且不是 refresh 请求,尝试刷新 token + if (error.response?.status === 401 && !originalRequest._retry) { + originalRequest._retry = true; + + try { + const refreshToken = getRefreshToken(); + const response = await api.post('/api/auth/refresh', { + refresh_token: refreshToken, + }); + + const { access_token, refresh_token } = response.data; + setAccessToken(access_token); + setRefreshToken(refresh_token); + + // 重试原始请求 + originalRequest.headers.Authorization = `Bearer ${access_token}`; + return api(originalRequest); + } catch (refreshError) { + // 刷新失败,跳转登录页 + clearTokens(); + window.location.href = '/login'; + return Promise.reject(refreshError); + } + } + + return Promise.reject(error); + } +); +``` + #### 登出 ``` diff --git a/docs/12-鉴权体系设计.md b/docs/12-鉴权体系设计.md new file mode 100644 index 0000000..3c3fb74 --- /dev/null +++ b/docs/12-鉴权体系设计.md @@ -0,0 +1,544 @@ +# 鉴权体系设计 + +## 概述 + +CamTalk 采用 JWT 双 token 轮转认证机制,结合 bcrypt 密码哈希和 Refresh Token Rotation 安全策略,实现安全可靠的用户认证体系。 + +**设计原则**: +- **安全性**:access_token 短有效期(15 分钟),refresh_token 支持轮转防重放 +- **可靠性**:Refresh Token Rotation 机制,检测复用时自动吊销用户所有令牌 +- **可扩展性**:Repository 接口隔离存储层,支持内存和 PostgreSQL 双实现 + +## 整体架构 + +```mermaid +graph TB + subgraph Client["客户端"] + Browser["浏览器"] + end + + subgraph AuthModule["Auth 模块"] + Service["AuthService
Register / Login / Refresh / Logout"] + TokenMgr["TokenManager
JWT 生成与验证"] + Middleware["AuthMiddleware
Gin 中间件"] + Password["PasswordUtil
bcrypt 哈希"] + end + + subgraph Storage["存储层"] + UserRepo["UserRepository
用户数据"] + TokenStore["RefreshToken 存储
SHA256 哈希"] + end + + Browser -->|"POST /api/auth/*"| Service + Service --> TokenMgr + Service --> Password + Service --> UserRepo + Service --> TokenStore + Middleware -->|"校验 access_token"| TokenMgr + Middleware -->|"写入 user_id/username"| GinContext["Gin Context"] +``` + +## 核心组件 + +### 1. JWT 令牌管理(TokenManager) + +**文件位置**:`backend/internal/auth/jwt.go` + +#### Claims 结构 + +```go +type Claims struct { + UserID string `json:"user_id"` + Username string `json:"username"` + TokenType string `json:"token_type"` // "access" | "refresh" + jwt.RegisteredClaims +} +``` + +**字段说明**: +- `UserID`:用户唯一标识(UUID) +- `Username`:用户名 +- `TokenType`:令牌类型,用于区分 access 和 refresh token +- `RegisteredClaims`:JWT 标准声明(ExpiresAt, IssuedAt, Issuer, ID) + +#### TokenManager 配置 + +```go +type TokenManager struct { + secret []byte // JWT 签名密钥(HS256) + accessTTL time.Duration // access_token 有效期(默认 15 分钟) + refreshTTL time.Duration // refresh_token 有效期(默认 7 天) +} + +func NewTokenManager(secret string, accessTTL, refreshTTL time.Duration) *TokenManager +``` + +#### 令牌生成 + +```go +func (tm *TokenManager) GeneratePair(userID, username string) (access, refresh string, err error) +``` + +**生成逻辑**: +1. **access_token**: + - 签名算法:HS256 + - 有效期:15 分钟 + - 包含:UserID, Username, TokenType="access", ExpiresAt, IssuedAt, Issuer="camtalk" + +2. **refresh_token**: + - 签名算法:HS256 + - 有效期:7 天 + - 包含:UserID, Username, TokenType="refresh", ID=UUID(用于 DB 关联), ExpiresAt, IssuedAt, Issuer="camtalk" + +#### 令牌验证 + +```go +func (tm *TokenManager) ValidateAccess(tokenStr string) (*Claims, error) +func (tm *TokenManager) ValidateRefresh(tokenStr string) (*Claims, error) +``` + +**验证逻辑**: +1. 解析 JWT,验证签名算法为 HMAC +2. 验证签名是否有效 +3. 验证令牌是否过期 +4. 验证 TokenType 是否匹配(access 或 refresh) +5. 返回 Claims 或错误 + +#### Token 哈希 + +```go +func HashToken(token string) string +``` + +**用途**:对 refresh_token 做 SHA256 哈希后存储到数据库,避免直接存储原始 token。 + +### 2. 密码处理(PasswordUtil) + +**文件位置**:`backend/internal/auth/password.go` + +#### 密码哈希 + +```go +func HashPassword(password string) (string, error) +``` + +**实现**: +- 算法:bcrypt +- Cost:10(2^10 次迭代) +- 返回:base64 编码的哈希字符串 + +#### 密码验证 + +```go +func CheckPassword(hashedPassword, password string) error +``` + +**实现**: +- 使用 `bcrypt.CompareHashAndPassword` 验证 +- 返回 nil 表示匹配,否则返回错误 + +### 3. 认证服务(AuthService) + +**文件位置**:`backend/internal/auth/service.go` + +#### 接口定义 + +```go +type Service interface { + Register(ctx context.Context, req RegisterRequest) (*AuthResponse, error) + Login(ctx context.Context, req LoginRequest) (*AuthResponse, error) + Refresh(ctx context.Context, req RefreshRequest) (*AuthResponse, error) + Logout(ctx context.Context, userID, refreshToken string) error +} +``` + +#### 注册流程(Register) + +```go +func (s *authService) Register(ctx context.Context, req RegisterRequest) (*AuthResponse, error) +``` + +**流程**: +1. 检查用户名是否已存在(`FindByUsername`) +2. 如果存在,返回 `ErrUsernameTaken` +3. 使用 bcrypt 哈希密码(`HashPassword`) +4. 创建用户记录(`Create`) +5. 生成 access_token + refresh_token(`GeneratePair`) +6. 保存 refresh_token 的 SHA256 哈希到数据库(`SaveRefreshToken`) +7. 返回 `AuthResponse` + +**错误处理**: +- `ErrUsernameTaken`:用户名已存在 +- 数据库错误:透传底层错误 + +#### 登录流程(Login) + +```go +func (s *authService) Login(ctx context.Context, req LoginRequest) (*AuthResponse, error) +``` + +**流程**: +1. 根据用户名查找用户(`FindByUsername`) +2. 如果用户不存在,返回 `ErrInvalidCredentials` +3. 验证密码(`CheckPassword`) +4. 如果密码错误,返回 `ErrInvalidCredentials` +5. 生成 access_token + refresh_token(`GeneratePair`) +6. 保存 refresh_token 的 SHA256 哈希到数据库(`SaveRefreshToken`) +7. 返回 `AuthResponse` + +**错误处理**: +- `ErrInvalidCredentials`:用户名或密码错误(统一错误信息,防止枚举攻击) + +#### 刷新令牌流程(Refresh)— Refresh Token Rotation + +```go +func (s *authService) Refresh(ctx context.Context, req RefreshRequest) (*AuthResponse, error) +``` + +**流程**: +1. 验证 refresh_token 的签名和有效期(`ValidateRefresh`) +2. 计算 refresh_token 的 SHA256 哈希(`HashToken`) +3. 在数据库中查找该哈希(`FindRefreshToken`) +4. **如果哈希不存在**: + - JWT 校验已通过但 DB 中不存在 → token 已被 rotation 删除 + - 这是 **token 复用行为**,属于安全风险 + - 吊销该用户的所有 refresh_token(`DeleteUserRefreshTokens`) + - 返回 `ErrRefreshTokenUsed` +5. 验证 token 归属的用户与 claims 一致 +6. 删除旧的 refresh_token 哈希(`DeleteRefreshToken`) +7. 生成新的 access_token + refresh_token(`GeneratePair`) +8. 保存新的 refresh_token 哈希到数据库(`SaveRefreshToken`) +9. 查询用户信息(`FindByID`) +10. 返回 `AuthResponse` + +**安全机制**: +- **Token 轮转**:每次 refresh 都会生成新的 token pair,旧 refresh_token 立即失效 +- **复用检测**:如果检测到已删除的 refresh_token 被复用,立即吊销该用户的所有 refresh_token +- **强制重新登录**:吊销后,该用户所有设备都需要重新登录 + +#### 登出流程(Logout) + +```go +func (s *authService) Logout(ctx context.Context, userID, refreshToken string) error +``` + +**流程**: +1. 计算 refresh_token 的 SHA256 哈希(`HashToken`) +2. 从数据库删除该哈希(`DeleteRefreshToken`) + +### 4. Gin 中间件(AuthMiddleware) + +**文件位置**:`backend/internal/auth/middleware.go` + +```go +func AuthMiddleware(tokenMgr *TokenManager) gin.HandlerFunc +``` + +**功能**: +1. 从请求头提取 `Authorization: Bearer ` +2. 验证 access_token(`ValidateAccess`) +3. 如果验证失败,返回 401 Unauthorized +4. 如果验证成功,将 `user_id` 和 `username` 写入 Gin Context +5. 调用 `c.Next()` 继续处理请求 + +**错误响应**: +```json +{ + "code": "INVALID_TOKEN", + "message": "missing authorization header" +} +``` + +```json +{ + "code": "INVALID_TOKEN", + "message": "invalid authorization format" +} +``` + +```json +{ + "code": "INVALID_TOKEN", + "message": "invalid or expired token" +} +``` + +**Context Key**: +- `ContextKeyUserID = "user_id"` +- `ContextKeyUsername = "username"` + +**使用示例**: +```go +// 在路由中使用中间件 +authorized := r.Group("/api") +authorized.Use(auth.AuthMiddleware(tokenMgr)) +{ + authorized.GET("/conversations", handler.ListConversations) + authorized.POST("/conversations", handler.CreateConversation) +} +``` + +## 数据模型 + +### 用户表(users) + +```sql +CREATE TABLE users ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + username VARCHAR(64) UNIQUE NOT NULL, + password_hash VARCHAR(255) NOT NULL, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); +``` + +### Refresh Token 表(refresh_tokens) + +```sql +CREATE TABLE refresh_tokens ( + token_hash VARCHAR(64) PRIMARY KEY, -- SHA256 哈希 + user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + expires_at TIMESTAMP WITH TIME ZONE NOT NULL, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +CREATE INDEX idx_refresh_tokens_user_id ON refresh_tokens(user_id); +CREATE INDEX idx_refresh_tokens_expires_at ON refresh_tokens(expires_at); +``` + +## Repository 接口 + +### UserRepository + +```go +type UserRepository interface { + // Create 创建用户,返回用户 ID + Create(ctx context.Context, username, passwordHash string) (string, error) + + // FindByUsername 根据用户名查找用户 + FindByUsername(ctx context.Context, username string) (*User, error) + + // FindByID 根据 ID 查找用户 + FindByID(ctx context.Context, id string) (*User, error) + + // SaveRefreshToken 保存 refresh_token 哈希 + SaveRefreshToken(ctx context.Context, userID, tokenHash string, expiresAt time.Time) error + + // FindRefreshToken 根据 token 哈希查找用户 ID + FindRefreshToken(ctx context.Context, tokenHash string) (string, error) + + // DeleteRefreshToken 删除指定的 refresh_token + DeleteRefreshToken(ctx context.Context, tokenHash string) error + + // DeleteUserRefreshTokens 删除用户的所有 refresh_token(用于检测复用时吊销) + DeleteUserRefreshTokens(ctx context.Context, userID string) error +} +``` + +## 前端集成 + +### Token 存储 + +**推荐方案**: +- `access_token`:存储在内存中(JavaScript 变量) +- `refresh_token`:存储在 `httpOnly` Cookie 中(防止 XSS 攻击) + +**备选方案**(开发环境): +- 两者都存储在 `localStorage`(便于调试,但存在 XSS 风险) + +### 请求拦截器 + +```typescript +// axios 请求拦截器 +api.interceptors.request.use((config) => { + const accessToken = getAccessToken(); + if (accessToken) { + config.headers.Authorization = `Bearer ${accessToken}`; + } + return config; +}); + +// axios 响应拦截器 +api.interceptors.response.use( + (response) => response, + async (error) => { + const originalRequest = error.config; + + // 如果是 401 且不是 refresh 请求,尝试刷新 token + if (error.response?.status === 401 && !originalRequest._retry) { + originalRequest._retry = true; + + try { + const refreshToken = getRefreshToken(); + const response = await api.post('/api/auth/refresh', { + refresh_token: refreshToken, + }); + + const { access_token, refresh_token } = response.data; + setAccessToken(access_token); + setRefreshToken(refresh_token); + + // 重试原始请求 + originalRequest.headers.Authorization = `Bearer ${access_token}`; + return api(originalRequest); + } catch (refreshError) { + // 刷新失败,跳转登录页 + clearTokens(); + window.location.href = '/login'; + return Promise.reject(refreshError); + } + } + + return Promise.reject(error); + } +); +``` + +### WebSocket 认证 + +```typescript +// 建立 WebSocket 连接时传递 access_token +const wsUrl = `ws://${window.location.host}/ws?token=${accessToken}&conversation_id=${conversationId}`; +const ws = new WebSocket(wsUrl); + +// 连接失败时(401),触发 token 刷新 +ws.onerror = (error) => { + console.error('WebSocket connection failed'); + // 可能需要刷新 token 后重连 +}; +``` + +## 安全考虑 + +### 1. 密码安全 + +- **bcrypt 算法**:使用 bcrypt 进行密码哈希,cost factor 为 10 +- **盐值自动生成**:bcrypt 自动生成随机盐值,无需手动管理 +- **防彩虹表**:每个密码的哈希值都不同,即使密码相同 + +### 2. Token 安全 + +- **短期 access_token**:15 分钟有效期,降低泄露风险 +- **Refresh Token Rotation**:每次 refresh 都生成新 token,旧 token 立即失效 +- **复用检测**:检测到已删除的 refresh_token 被复用时,吊销该用户的所有 token +- **SHA256 哈希存储**:数据库只存储 refresh_token 的哈希值,不存储原始 token + +### 3. 传输安全 + +- **HTTPS 强制**:生产环境必须使用 HTTPS +- **CORS 限制**:配置 `AllowedOrigins` 限制允许的域名 +- **HttpOnly Cookie**:refresh_token 存储在 httpOnly Cookie 中,防止 XSS 攻击 + +### 4. 防攻击策略 + +- **防暴力破解**:可选的速率限制(`RATE_LIMITED` 错误码) +- **防枚举攻击**:登录失败时统一返回 `INVALID_CREDENTIALS`,不区分用户名不存在还是密码错误 +- **防重放攻击**:Refresh Token Rotation 确保每个 refresh_token 只能使用一次 +- **防 Token 泄露**:检测到 token 复用时,立即吊销该用户的所有 token + +## 配置说明 + +### 配置文件 + +```yaml +auth: + jwt_secret: "" # JWT 签名密钥(必须通过环境变量设置) + access_ttl: 15 # access_token 有效期(分钟) + refresh_ttl: 10080 # refresh_token 有效期(分钟,7天) +``` + +### 环境变量 + +| 环境变量 | 说明 | 示例 | +|---------|------|------| +| `CAMTALK_AUTH_JWT_SECRET` | JWT 签名密钥(必须) | `$(openssl rand -hex 32)` | +| `CAMTALK_AUTH_ACCESS_TTL` | access_token 有效期(分钟) | `15` | +| `CAMTALK_AUTH_REFRESH_TTL` | refresh_token 有效期(分钟) | `10080` | + +**安全要求**: +- `JWT_SECRET` **必须**通过环境变量设置,不能写入配置文件 +- 生产环境使用 `openssl rand -hex 32` 生成随机密钥 +- 密钥长度建议至少 32 字节(256 位) + +## 错误码 + +| 错误码 | HTTP 状态码 | 含义 | 客户端处理 | +|--------|-----------|------|-----------| +| `USERNAME_TAKEN` | 409 | 用户名已存在 | 提示换一个用户名 | +| `INVALID_CREDENTIALS` | 401 | 用户名或密码错误 | 提示检查输入 | +| `INVALID_TOKEN` | 401 | JWT 无效或已过期 | 尝试 refresh,失败则重新登录 | + +## 测试用例 + +### 单元测试 + +**文件位置**:`backend/internal/auth/jwt_test.go`, `backend/internal/auth/service_test.go` + +**测试覆盖**: +- Token 生成和验证 +- Token 过期处理 +- Refresh Token Rotation +- Token 复用检测和吊销 +- 密码哈希和验证 +- 边界条件和错误处理 + +### 集成测试 + +**测试场景**: +- 注册 → 登录 → 访问受保护资源 +- Token 刷新流程 +- Token 过期后自动刷新 +- 并发刷新 token(竞态条件) +- Token 复用检测和吊销 + +## 监控指标 + +### 关键指标 + +- **登录成功率**:登录成功次数 / 登录总次数 +- **Token 刷新率**:refresh 请求次数 / 总请求数 +- **Token 复用检测**:检测到 token 复用的次数(安全事件) +- **认证延迟**:JWT 验证的平均耗时 + +### 告警规则 + +- **Token 复用检测**:任何 token 复用事件都应触发告警 +- **异常登录失败率**:短时间内大量登录失败可能表示暴力破解攻击 +- **Token 刷新失败率**:refresh 失败率突然上升可能表示系统问题 + +## 扩展点 + +### 1. 多设备管理 + +当前实现支持同一用户在多个设备上登录(每个设备独立的 refresh_token)。可以扩展为: +- 设备列表管理 +- 单设备登录(踢出其他设备) +- 设备信任等级 + +### 2. OAuth 第三方登录 + +可以扩展 AuthService 支持 OAuth 2.0: +- Google、GitHub 等第三方登录 +- 绑定/解绑第三方账号 +- 统一的用户身份管理 + +### 3. 双因素认证(2FA) + +可以扩展为: +- TOTP(基于时间的一次性密码) +- SMS 验证码 +- 邮箱验证 + +### 4. 会话管理 + +可以扩展为: +- 活跃会话列表 +- 远程登出其他会话 +- 会话过期策略 + +## 参考资料 + +- [JWT 规范](https://tools.ietf.org/html/rfc7519) +- [bcrypt 算法](https://en.wikipedia.org/wiki/Bcrypt) +- [OWASP 认证备忘录](https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html) +- [Refresh Token Rotation](https://auth0.com/blog/refresh-tokens-what-are-they-and-when-to-use-them/) diff --git a/docs/README.md b/docs/README.md index a898a9a..5a58904 100644 --- a/docs/README.md +++ b/docs/README.md @@ -17,6 +17,7 @@ CamTalk 是一款多模态实时 AI 视觉对话助手。用户通过摄像头 | [09-技术名词解释](09-技术名词解释.md) | 前端/后端/AI 服务/Eino 框架技术名词简明解释 | | [10-Eino重构方案](10-Eino重构方案.md) | Eino Graph 替换手写 goroutine 管道的设计方案 | | [11-Eino框架技术文档](11-Eino框架技术文档.md) | Eino 框架在 CamTalk 中的使用指南(Graph、Lambda、Callback、State) | +| [12-鉴权体系设计](12-鉴权体系设计.md) | JWT 双 token 轮转认证、bcrypt 密码哈希、Refresh Token Rotation、安全机制 | ## 推荐阅读顺序 @@ -28,4 +29,5 @@ CamTalk 是一款多模态实时 AI 视觉对话助手。用户通过摄像头 5. **05~07** — 各技术领域的详细设计 6. **09-技术名词解释** — 遇到不熟悉的名词时查阅 7. **10~12** — Eino 重构相关(方案、框架文档、实施记录) +8. **12-鉴权体系设计** — 认证授权机制详细设计(JWT、bcrypt、Refresh Token Rotation)