Files
backend/.qoder/repowiki/zh/content/API参考/Yggdrasil协议API/认证服务.md

464 lines
19 KiB
Markdown
Raw Normal View History

# 认证服务
<cite>
**本文引用的文件**
- [routes.go](file://internal/handler/routes.go)
- [yggdrasil_handler.go](file://internal/handler/yggdrasil_handler.go)
- [yggdrasil_service.go](file://internal/service/yggdrasil_service.go)
- [yggdrasil_service_test.go](file://internal/service/yggdrasil_service_test.go)
- [yggdrasil_repository.go](file://internal/repository/yggdrasil_repository.go)
- [jwt.go](file://pkg/auth/jwt.go)
- [redis.go](file://pkg/redis/redis.go)
- [response.go](file://internal/model/response.go)
</cite>
## 目录
1. [简介](#简介)
2. [项目结构](#项目结构)
3. [核心组件](#核心组件)
4. [架构总览](#架构总览)
5. [详细组件分析](#详细组件分析)
6. [依赖关系分析](#依赖关系分析)
7. [性能考量](#性能考量)
8. [故障排查指南](#故障排查指南)
9. [结论](#结论)
10. [附录](#附录)
## 简介
本文件面向Yggdrasil认证服务的API文档聚焦/authserver路由下的以下端点
- POST /authenticate用户凭据认证返回访问令牌与可用角色信息
- POST /validate校验访问令牌有效性
- POST /refresh在令牌过期或需要更新时刷新令牌
- POST /invalidate撤销单个访问令牌
- POST /signout基于邮箱+密码登出,撤销该用户所有令牌
文档将说明各端点的HTTP方法、请求体结构、响应数据格式、错误码含义并结合会话数据存储机制Redis与TTL设置15分钟解释authenticate如何验证用户名/邮箱与密码、validate如何检查令牌有效性、refresh如何在令牌过期后重新生成新令牌。同时给出与内部用户系统的映射逻辑如通过GetYggdrasilPasswordById查询密码
## 项目结构
Yggdrasil认证服务位于路由组“/api/v1/yggdrasil/authserver”由处理器模块负责接收请求、调用服务层完成业务逻辑并通过Redis与数据库进行会话与令牌持久化。
```mermaid
graph TB
subgraph "路由层"
R["routes.go<br/>注册/authserver路由"]
H["yggdrasil_handler.go<br/>认证处理器"]
end
subgraph "服务层"
S["yggdrasil_service.go<br/>会话与加入服务器逻辑"]
T["token_service.go<br/>令牌生成/验证/刷新/失效"]
end
subgraph "数据访问层"
YR["yggdrasil_repository.go<br/>Yggdrasil密码查询"]
end
subgraph "基础设施"
J["jwt.go<br/>JWT工具"]
RC["redis.go<br/>Redis客户端"]
RM["response.go<br/>通用响应结构"]
end
R --> H
H --> S
H --> T
S --> YR
T --> YR
H --> J
H --> RC
H --> RM
```
图表来源
- [routes.go](file://internal/handler/routes.go#L87-L111)
- [yggdrasil_handler.go](file://internal/handler/yggdrasil_handler.go#L156-L425)
- [yggdrasil_service.go](file://internal/service/yggdrasil_service.go#L1-L202)
- [yggdrasil_repository.go](file://internal/repository/yggdrasil_repository.go#L1-L17)
- [jwt.go](file://pkg/auth/jwt.go#L1-L71)
- [redis.go](file://pkg/redis/redis.go#L1-L175)
- [response.go](file://internal/model/response.go#L1-L86)
章节来源
- [routes.go](file://internal/handler/routes.go#L87-L111)
## 核心组件
- 路由注册:在路由中将/authserver下的五个端点挂载到对应处理器函数。
- 认证处理器实现authenticate、validate、refresh、invalidate、signout等端点的请求解析、调用服务层、构造响应。
- 令牌服务:封装令牌生成、验证、刷新、失效等逻辑,维护令牌表与清理策略。
- 会话服务封装JoinServer/HasJoinedServer使用Redis存储会话数据TTL为15分钟。
- 数据访问通过repository层访问数据库如查询Yggdrasil密码。
- 基础设施JWT工具用于令牌签发与校验Redis客户端用于会话数据持久化。
章节来源
- [routes.go](file://internal/handler/routes.go#L87-L111)
- [yggdrasil_handler.go](file://internal/handler/yggdrasil_handler.go#L156-L425)
- [yggdrasil_service.go](file://internal/service/yggdrasil_service.go#L1-L202)
- [yggdrasil_repository.go](file://internal/repository/yggdrasil_repository.go#L1-L17)
- [jwt.go](file://pkg/auth/jwt.go#L1-L71)
- [redis.go](file://pkg/redis/redis.go#L1-L175)
- [response.go](file://internal/model/response.go#L1-L86)
## 架构总览
下图展示/authserver端点的请求-处理-响应流程以及与服务层、Redis、数据库的关系。
```mermaid
sequenceDiagram
participant C as "客户端"
participant H as "yggdrasil_handler.go"
participant S as "yggdrasil_service.go"
participant T as "token_service.go"
participant YR as "yggdrasil_repository.go"
participant DB as "数据库"
participant RD as "Redis"
rect rgb(255,255,255)
Note over C,H : authenticate/validate/refresh/invalidate/signout
end
C->>H : POST /api/v1/yggdrasil/authserver/{endpoint}
H->>DB : 读取/写入数据如用户、角色、令牌
H->>RD : 读取/写入会话数据JoinServer/HasJoinedServer
H->>T : 调用令牌相关服务生成/验证/刷新/失效
H->>YR : 查询Yggdrasil密码凭据校验
T->>DB : 操作token表创建/删除/查询
S->>RD : Set/Get会话数据TTL=15分钟
H-->>C : JSON响应含状态码与数据
```
图表来源
- [yggdrasil_handler.go](file://internal/handler/yggdrasil_handler.go#L156-L425)
- [yggdrasil_service.go](file://internal/service/yggdrasil_service.go#L81-L202)
- [yggdrasil_repository.go](file://internal/repository/yggdrasil_repository.go#L1-L17)
- [redis.go](file://pkg/redis/redis.go#L60-L175)
## 详细组件分析
### 端点POST /authenticate
- 方法与路径
- 方法POST
- 路径:/api/v1/yggdrasil/authserver/authenticate
- 请求体结构
- agent对象描述客户端信息通常包含名称与版本
- clientToken字符串客户端令牌可选
- identifier字符串用户名或邮箱必填
- password字符串密码必填
- requestUser布尔是否返回用户属性可选
- 响应数据
- accessToken字符串访问令牌
- clientToken字符串客户端令牌
- availableProfiles数组可用角色列表每个元素为序列化后的角色信息
- selectedProfile对象当前选定的角色可选
- user对象当requestUser=true时返回包含id与properties
- 错误码
- 400请求参数错误
- 403用户名不存在或密码错误
- 500服务器内部错误
- 处理流程要点
- 根据identifier判断是邮箱还是用户名分别查询用户或角色
- 通过repository查询Yggdrasil密码并与请求密码比对
- 生成新令牌包含accessToken、clientToken并返回可用角色列表
- 如requestUser为true附加用户属性
- 与内部用户系统映射
- 通过GetYggdrasilPasswordById查询Yggdrasil密码并与请求密码比对
- 通过GetUserIDByEmail或GetProfileByProfileName获取用户ID
- 通过GetProfileByUserId获取角色列表自动选择唯一角色
```mermaid
sequenceDiagram
participant C as "客户端"
participant H as "Authenticate()"
participant DB as "数据库"
participant YR as "yggdrasil_repository.go"
participant T as "token_service.go"
C->>H : JSON请求identifier/password等
H->>DB : 根据identifier定位用户/角色
H->>YR : GetYggdrasilPasswordById(userId)
YR-->>H : 返回Yggdrasil密码
H->>H : 校验密码
H->>T : NewToken(userId, UUID, clientToken)
T-->>H : 返回accessToken/clientToken/availableProfiles
H-->>C : 200 JSONaccessToken/clientToken/availableProfiles/selectedProfile/user
```
图表来源
- [yggdrasil_handler.go](file://internal/handler/yggdrasil_handler.go#L156-L246)
- [yggdrasil_repository.go](file://internal/repository/yggdrasil_repository.go#L1-L17)
- [yggdrasil_service.go](file://internal/service/yggdrasil_service.go#L1-L79)
- [token_service.go](file://internal/service/token_service.go#L24-L79)
章节来源
- [yggdrasil_handler.go](file://internal/handler/yggdrasil_handler.go#L156-L246)
- [yggdrasil_repository.go](file://internal/repository/yggdrasil_repository.go#L1-L17)
- [yggdrasil_service.go](file://internal/service/yggdrasil_service.go#L1-L79)
- [token_service.go](file://internal/service/token_service.go#L24-L79)
### 端点POST /validate
- 方法与路径
- 方法POST
- 路径:/api/v1/yggdrasil/authserver/validate
- 请求体结构
- accessToken字符串访问令牌必填
- clientToken字符串客户端令牌可选
- 响应数据
- 当令牌有效204 No Content无body
- 当令牌无效403 Forbiddenvalid=false
- 处理流程要点
- 调用ValidToken校验accessToken与clientToken若提供
- 有效返回204无效返回403
```mermaid
sequenceDiagram
participant C as "客户端"
participant H as "ValidToken()"
participant T as "token_service.go"
C->>H : JSON请求accessToken, clientToken
H->>T : ValidToken(accessToken, clientToken)
alt 有效
H-->>C : 204 No Content
else 无效
H-->>C : 403 {valid : false}
end
```
图表来源
- [yggdrasil_handler.go](file://internal/handler/yggdrasil_handler.go#L248-L267)
- [token_service.go](file://internal/service/token_service.go#L116-L141)
章节来源
- [yggdrasil_handler.go](file://internal/handler/yggdrasil_handler.go#L248-L267)
- [token_service.go](file://internal/service/token_service.go#L116-L141)
### 端点POST /refresh
- 方法与路径
- 方法POST
- 路径:/api/v1/yggdrasil/authserver/refresh
- 请求体结构
- accessToken字符串访问令牌必填
- clientToken字符串客户端令牌可选
- requestUser布尔是否返回用户属性可选
- selectedProfile对象包含id字段可选
- 响应数据
- accessToken字符串新的访问令牌
- clientToken字符串客户端令牌
- selectedProfile对象选定的角色可选
- user对象当requestUser=true时返回包含id与properties
- 处理流程要点
- 通过accessToken获取UUID与用户ID
- 校验selectedProfile若提供与用户匹配
- 调用RefreshToken生成新令牌删除旧令牌
- 返回新令牌与可选数据
- 与内部用户系统映射
- 通过GetUUIDByAccessToken与GetUserIDByAccessToken获取用户与角色信息
- 通过ValidateProfileByUserID校验角色归属
```mermaid
sequenceDiagram
participant C as "客户端"
participant H as "RefreshToken()"
participant T as "token_service.go"
participant DB as "数据库"
C->>H : JSON请求accessToken, clientToken, selectedProfile, requestUser
H->>DB : GetUUIDByAccessToken / GetUserIDByAccessToken
H->>H : 校验selectedProfile归属
H->>T : RefreshToken(oldAccessToken, clientToken, selectedProfileID)
T-->>H : 返回newAccessToken, clientToken
H-->>C : 200 JSONnewAccessToken, clientToken, selectedProfile, user
```
图表来源
- [yggdrasil_handler.go](file://internal/handler/yggdrasil_handler.go#L269-L361)
- [token_service.go](file://internal/service/token_service.go#L151-L238)
章节来源
- [yggdrasil_handler.go](file://internal/handler/yggdrasil_handler.go#L269-L361)
- [token_service.go](file://internal/service/token_service.go#L151-L238)
### 端点POST /invalidate
- 方法与路径
- 方法POST
- 路径:/api/v1/yggdrasil/authserver/invalidate
- 请求体结构
- accessToken字符串访问令牌必填
- clientToken字符串客户端令牌可选
- 响应数据
- 204 No Content
- 处理流程要点
- 调用InvalidToken删除对应accessToken
```mermaid
sequenceDiagram
participant C as "客户端"
participant H as "InvalidToken()"
participant T as "token_service.go"
C->>H : JSON请求accessToken, clientToken
H->>T : InvalidToken(accessToken)
T-->>H : 删除完成
H-->>C : 204 No Content
```
图表来源
- [yggdrasil_handler.go](file://internal/handler/yggdrasil_handler.go#L363-L378)
- [token_service.go](file://internal/service/token_service.go#L240-L257)
章节来源
- [yggdrasil_handler.go](file://internal/handler/yggdrasil_handler.go#L363-L378)
- [token_service.go](file://internal/service/token_service.go#L240-L257)
### 端点POST /signout
- 方法与路径
- 方法POST
- 路径:/api/v1/yggdrasil/authserver/signout
- 请求体结构
- username字符串邮箱必填
- password字符串密码必填
- 响应数据
- 204 No Content
- 处理流程要点
- 校验邮箱格式
- 通过邮箱获取用户并查询Yggdrasil密码
- 对比请求密码与存储密码
- 调用InvalidUserTokens撤销该用户所有令牌
```mermaid
sequenceDiagram
participant C as "客户端"
participant H as "SignOut()"
participant DB as "数据库"
participant T as "token_service.go"
C->>H : JSON请求username, password
H->>H : 校验邮箱格式
H->>DB : GetUserByEmail / GetYggdrasilPasswordById
H->>H : 校验密码
H->>T : InvalidUserTokens(userId)
T-->>H : 删除完成
H-->>C : 204 No Content
```
图表来源
- [yggdrasil_handler.go](file://internal/handler/yggdrasil_handler.go#L380-L425)
- [yggdrasil_repository.go](file://internal/repository/yggdrasil_repository.go#L1-L17)
- [token_service.go](file://internal/service/token_service.go#L259-L277)
章节来源
- [yggdrasil_handler.go](file://internal/handler/yggdrasil_handler.go#L380-L425)
- [yggdrasil_repository.go](file://internal/repository/yggdrasil_repository.go#L1-L17)
- [token_service.go](file://internal/service/token_service.go#L259-L277)
### 会话数据存储与TTLJoinServer/HasJoinedServer
- 存储机制
- 使用Redis存储玩家加入服务器的会话数据键规则为“Join_”前缀+serverId
- 数据结构包含accessToken、userName、selectedProfile、ip
- TTL设置
- 会话数据TTL为15分钟
- 读写流程
- JoinServer校验参数与Token获取角色信息序列化会话数据并写入Redis
- HasJoinedServer从Redis读取会话数据反序列化后校验用户名与IP可选
```mermaid
flowchart TD
Start(["进入JoinServer"]) --> Validate["校验serverId/accessToken/selectedProfile非空"]
Validate --> Format["格式化UUID与IP校验"]
Format --> GetToken["根据accessToken查询Token"]
GetToken --> MatchProfile["selectedProfile与Token绑定Profile匹配"]
MatchProfile --> LoadProfile["加载角色信息"]
LoadProfile --> BuildData["构建SessionData结构"]
BuildData --> Marshal["序列化SessionData"]
Marshal --> Store["Redis Set(key='Join_serverId', value, TTL=15m)"]
Store --> End(["完成"])
subgraph "HasJoinedServer"
HStart["读取serverId/username"] --> HLoad["Redis Get('Join_serverId')"]
HLoad --> Parse["反序列化SessionData"]
Parse --> CheckUser["校验userName匹配"]
CheckUser --> CheckIP{"提供IP?"}
CheckIP --> |是| IPMatch["校验IP匹配"]
CheckIP --> |否| Success["通过"]
IPMatch --> Success
end
```
图表来源
- [yggdrasil_service.go](file://internal/service/yggdrasil_service.go#L81-L202)
- [yggdrasil_service_test.go](file://internal/service/yggdrasil_service_test.go#L1-L351)
- [redis.go](file://pkg/redis/redis.go#L60-L175)
章节来源
- [yggdrasil_service.go](file://internal/service/yggdrasil_service.go#L19-L31)
- [yggdrasil_service.go](file://internal/service/yggdrasil_service.go#L81-L202)
- [yggdrasil_service_test.go](file://internal/service/yggdrasil_service_test.go#L1-L351)
- [redis.go](file://pkg/redis/redis.go#L60-L175)
## 依赖关系分析
- 路由到处理器:/api/v1/yggdrasil/authserver/* 映射到yggdrasil_handler.go中的对应函数
- 处理器到服务:认证处理器调用令牌服务与会话服务
- 服务到仓库令牌服务与会话服务通过repository层访问数据库
- 会话服务到RedisJoinServer/HasJoinedServer直接使用Redis客户端
- 响应结构统一使用通用响应结构参考response.go
```mermaid
graph LR
Routes["routes.go"] --> Handler["yggdrasil_handler.go"]
Handler --> TokenSvc["token_service.go"]
Handler --> YggSvc["yggdrasil_service.go"]
YggSvc --> Redis["redis.go"]
TokenSvc --> Repo["yggdrasil_repository.go"]
Handler --> Resp["response.go"]
```
图表来源
- [routes.go](file://internal/handler/routes.go#L87-L111)
- [yggdrasil_handler.go](file://internal/handler/yggdrasil_handler.go#L156-L425)
- [yggdrasil_service.go](file://internal/service/yggdrasil_service.go#L1-L202)
- [yggdrasil_repository.go](file://internal/repository/yggdrasil_repository.go#L1-L17)
- [redis.go](file://pkg/redis/redis.go#L1-L175)
- [response.go](file://internal/model/response.go#L1-L86)
章节来源
- [routes.go](file://internal/handler/routes.go#L87-L111)
- [yggdrasil_handler.go](file://internal/handler/yggdrasil_handler.go#L156-L425)
- [yggdrasil_service.go](file://internal/service/yggdrasil_service.go#L1-L202)
- [yggdrasil_repository.go](file://internal/repository/yggdrasil_repository.go#L1-L17)
- [redis.go](file://pkg/redis/redis.go#L1-L175)
- [response.go](file://internal/model/response.go#L1-L86)
## 性能考量
- Redis读写JoinServer/HasJoinedServer使用GetBytes/SetTTL为15分钟适合短生命周期的会话数据
- 令牌清理NewToken后异步触发CheckAndCleanupExcessTokens限制用户最多保留10个令牌降低数据库压力
- 超时控制:服务层对数据库查询设置默认超时,避免阻塞
- 并发安全:刷新令牌采用先创建新令牌再删除旧令牌的双写策略,减少事务复杂度
章节来源
- [yggdrasil_service.go](file://internal/service/yggdrasil_service.go#L81-L202)
- [token_service.go](file://internal/service/token_service.go#L81-L114)
- [redis.go](file://pkg/redis/redis.go#L60-L175)
## 故障排查指南
- 400 Bad Request
- 请求体解析失败或参数缺失
- 会话加入/验证参数缺失serverId/username
- 403 Forbidden
- 认证失败(用户名不存在或密码错误)
- 令牌无效或clientToken不匹配
- 用户与角色不匹配
- 500 Internal Server Error
- 生成令牌失败、读取/写入Redis失败、数据库查询异常
- 常见问题定位
- 确认identifier是否为邮箱或用户名
- 确认clientToken是否与accessToken匹配validate/refresh
- 检查Redis连接与TTL设置JoinServer/HasJoinedServer
- 检查用户角色与selectedProfile是否匹配refresh
章节来源
- [yggdrasil_handler.go](file://internal/handler/yggdrasil_handler.go#L156-L425)
- [yggdrasil_service.go](file://internal/service/yggdrasil_service.go#L81-L202)
- [yggdrasil_service_test.go](file://internal/service/yggdrasil_service_test.go#L1-L351)
## 结论
本认证服务围绕/authserver路由提供了完整的Yggdrasil认证能力覆盖凭据认证、令牌验证、刷新、失效与登出。通过Redis实现15分钟TTL的会话数据存储结合令牌服务的清理策略与严格的参数校验确保系统在安全性与性能之间取得平衡。与内部用户系统的映射清晰凭据校验通过getYggdrasilPasswordById完成角色管理与令牌绑定完善。
## 附录
- 错误码对照
- 400请求参数错误
- 403权限不足/令牌无效/用户不匹配
- 500服务器内部错误
- 响应结构
- 通用响应结构参考response.go中的Response/Error结构
- 会话数据结构
- SessionData包含accessToken、userName、selectedProfile、ip
章节来源
- [response.go](file://internal/model/response.go#L1-L86)
- [yggdrasil_service.go](file://internal/service/yggdrasil_service.go#L19-L31)