# 认证服务 **本文引用的文件** - [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) ## 目录 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
注册/authserver路由"] H["yggdrasil_handler.go
认证处理器"] end subgraph "服务层" S["yggdrasil_service.go
会话与加入服务器逻辑"] T["token_service.go
令牌生成/验证/刷新/失效"] end subgraph "数据访问层" YR["yggdrasil_repository.go
Yggdrasil密码查询"] end subgraph "基础设施" J["jwt.go
JWT工具"] RC["redis.go
Redis客户端"] RM["response.go
通用响应结构"] 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 Forbidden(valid=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) ### 会话数据存储与TTL(JoinServer/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层访问数据库 - 会话服务到Redis:JoinServer/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/Set,TTL为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)