Files
backend/.qoder/repowiki/zh/content/API参考/Yggdrasil协议API/会话服务.md
lan a4b6c5011e
Some checks failed
SonarQube Analysis / sonarqube (push) Has been cancelled
chore(git): 更新.gitignore以忽略新的本地文件
2025-11-30 08:33:17 +08:00

15 KiB
Raw Blame History

会话服务

**本文档引用文件** - [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) - [routes.go](file://internal/handler/routes.go) - [profile.go](file://internal/model/profile.go) - [profile_service.go](file://internal/service/profile_service.go) - [redis.go](file://pkg/redis/redis.go)

目录

  1. 简介
  2. API路由结构
  3. 核心API功能详解
  4. 会话数据结构
  5. 反作弊机制
  6. 与Minecraft客户端交互流程
  7. 错误处理与日志
  8. 测试验证

简介

本文档详细描述了Yggdrasil会话服务的核心功能重点聚焦于/sessionserver路由组下的三个关键APIGetProfileByUUIDJoinServerHasJoinedServer。这些API构成了Minecraft服务器会话验证系统的核心负责处理玩家加入服务器的会话建立、验证和玩家档案查询。

系统通过Redis存储会话数据利用Join_为前缀的键名和15分钟的TTL生存时间来管理会话生命周期。整个流程确保了只有经过身份验证的玩家才能加入服务器同时通过IP地址和用户名的双重验证机制防止作弊行为。

Section sources

API路由结构

会话服务的API路由定义在routes.go文件中,位于/yggdrasil/sessionserver路径下。该路由组提供了三个核心端点:

  • GET /session/minecraft/profile/:uuid根据玩家UUID获取其公开档案信息。
  • POST /session/minecraft/join:接收客户端的会话信息,建立服务器会话。
  • GET /session/minecraft/hasJoined:验证某玩家是否已成功加入指定服务器。
graph TB
subgraph "Yggdrasil API"
A[/yggdrasil/sessionserver]
A --> B[GET /session/minecraft/profile/:uuid]
A --> C[POST /session/minecraft/join]
A --> D[GET /session/minecraft/hasJoined]
end

**Diagram sources **

Section sources

核心API功能详解

JoinServer API

JoinServer API是建立服务器会话的核心。当Minecraft客户端成功登录后会调用此API来“加入”一个特定的服务器。

功能流程:

  1. 接收参数API接收serverIdaccessTokenselectedProfile玩家UUID三个必需参数。
  2. 输入验证:对serverId进行格式检查长度不超过100字符不包含<>\"'&等危险字符并验证客户端IP地址格式。
  3. 令牌验证:通过accessToken在数据库中查找对应的令牌记录,确保令牌有效。
  4. 配置文件匹配:将selectedProfile客户端提供的UUID与令牌中关联的ProfileId进行比对,确保玩家使用的是正确的配置文件。
  5. 构建会话数据从数据库中获取该UUID对应的玩家档案提取其用户名Name)。
  6. 存储会话:将accessTokenuserNameselectedProfile和客户端IP地址构建成SessionData结构体序列化后存入Redis。存储的键名为Join_ + serverIdTTL设置为15分钟。
sequenceDiagram
participant Client as "Minecraft客户端"
participant Handler as "yggdrasil_handler"
participant Service as "yggdrasil_service"
participant Redis as "Redis"
participant DB as "数据库"
Client->>Handler : POST /join (serverId, accessToken, selectedProfile)
Handler->>Service : JoinServer(serverId, accessToken, selectedProfile, clientIP)
Service->>DB : 根据accessToken查找Token
DB-->>Service : Token信息
Service->>Service : 验证selectedProfile与Token匹配
Service->>DB : 根据UUID查找Profile
DB-->>Service : Profile (含用户名)
Service->>Service : 构建SessionData对象
Service->>Service : 序列化SessionData
Service->>Redis : Set(Join_serverId, marshaledData, 15min)
Redis-->>Service : 成功
Service-->>Handler : 成功
Handler-->>Client : HTTP 204 No Content

**Diagram sources **

Section sources

HasJoinedServer API

HasJoinedServer API用于服务器端验证一个玩家是否已经通过了会话验证。

功能流程:

  1. 接收参数API接收serverIdusername两个必需参数,以及可选的ip参数。
  2. 输入验证:确保serverIdusername不为空。
  3. 获取会话数据:使用Join_ + serverId作为键名从Redis中获取之前存储的会话数据。
  4. 反序列化将获取到的JSON数据反序列化为SessionData结构体。
  5. 验证匹配
    • 用户名匹配:比较会话数据中的UserName与请求中的username是否完全一致(区分大小写)。
    • IP地址匹配:如果请求中提供了ip参数,则会比较会话数据中的IP与请求的ip是否一致。
  6. 返回结果:如果所有验证通过,则从数据库中获取该username对应的完整玩家档案(包括皮肤、披风等信息)并返回;否则返回验证失败。
sequenceDiagram
participant Server as "Minecraft服务器"
participant Handler as "yggdrasil_handler"
participant Service as "yggdrasil_service"
participant Redis as "Redis"
participant DB as "数据库"
Server->>Handler : GET /hasJoined?serverId=...&username=...&ip=...
Handler->>Service : HasJoinedServer(serverId, username, ip)
Service->>Redis : Get(Join_serverId)
Redis-->>Service : marshaledData (或 nil)
alt 会话不存在
Service-->>Handler : 错误
Handler-->>Server : HTTP 204 No Content
else 会话存在
Service->>Service : 反序列化为SessionData
Service->>Service : 验证UserName == username
Service->>Service : 验证IP (如果提供)
alt 验证失败
Service-->>Handler : 错误
Handler-->>Server : HTTP 204 No Content
else 验证成功
Service->>DB : 根据username查找Profile
DB-->>Service : Profile信息
Service-->>Handler : Profile
Handler-->>Server : HTTP 200 + Profile JSON
end
end

**Diagram sources **

Section sources

GetProfileByUUID API

GetProfileByUUID API提供了一种通过玩家UUID查询其公开档案信息的方式。

功能流程:

  1. 接收参数从URL路径中获取:uuid参数。
  2. 格式化UUID:调用utils.FormatUUID函数将可能存在的十六进制格式UUID转换为标准的带连字符格式。
  3. 查询档案:调用service.GetProfileByUUID方法根据格式化后的UUID在数据库中查找对应的Profile记录。
  4. 序列化响应:将Profile模型转换为包含皮肤Skin和披风CapeURL的ProfileResponse结构体。
  5. 返回结果将序列化后的JSON数据返回给客户端。
sequenceDiagram
participant Client as "客户端"
participant Handler as "yggdrasil_handler"
participant Service as "profile_service"
participant DB as "数据库"
Client->>Handler : GET /profile/ : uuid
Handler->>Handler : FormatUUID(uuid)
Handler->>Service : GetProfileByUUID(uuid)
Service->>DB : FindProfileByUUID(uuid)
DB-->>Service : Profile
Service-->>Handler : Profile
Handler->>Handler : SerializeProfile(Profile)
Handler-->>Client : HTTP 200 + Profile JSON

**Diagram sources **

Section sources

会话数据结构

SessionData结构体定义了存储在Redis中的会话信息JoinServerHasJoinedServer两个API之间通信的核心载体。

type SessionData struct {
    AccessToken     string `json:"accessToken"`
    UserName        string `json:"userName"`
    SelectedProfile string `json:"selectedProfile"`
    IP              string `json:"ip"`
}
  • AccessToken:客户端的访问令牌,用于在JoinServer时验证身份。
  • UserName:玩家的用户名(如Steve),在HasJoinedServer时用于比对。
  • SelectedProfile玩家的UUID用于唯一标识玩家。
  • IP客户端的IP地址用于反作弊验证。

该结构体在JoinServer时被创建并序列化存储,在HasJoinedServer时被反序列化读取并用于验证。

classDiagram
class SessionData {
+string accessToken
+string userName
+string selectedProfile
+string ip
}

**Diagram sources **

Section sources

反作弊机制

系统通过HasJoinedServer API实现了有效的反作弊机制主要依赖于以下两个层面的验证

  1. 令牌绑定验证:在JoinServer阶段,系统强制要求selectedProfileUUID必须与accessToken所关联的配置文件ID完全匹配。这确保了玩家不能使用他人的令牌来冒充身份。

  2. IP地址与时间戳验证

    • IP地址验证HasJoinedServer API可以选择性地接收一个ip参数。如果提供了该参数,系统会将其与JoinServer时记录的IP地址进行比对。如果两者不一致则验证失败。这可以有效防止玩家在一台机器上登录后将令牌分享给另一台机器上的其他玩家使用。
    • 时间戳验证通过将Redis中会话数据的TTL设置为15分钟系统实现了会话的自动过期。这意味着即使令牌和IP验证通过该会话也只在有限时间内有效增加了作弊的难度和成本。

Section sources

与Minecraft客户端交互流程

以下是Minecraft客户端与本会话服务交互的完整流程示例

  1. 客户端登录玩家在Minecraft启动器中输入邮箱和密码启动器调用/authserver/authenticate API进行身份验证并获取accessTokenavailableProfiles
  2. 选择配置文件:启动器列出可用的配置文件,玩家选择一个(如Steve)。
  3. 加入服务器
    • 玩家在游戏内选择一个服务器并点击“加入”。
    • 启动器调用/sessionserver/session/minecraft/join API携带serverId(服务器的哈希值)、accessTokenselectedProfileSteve的UUID
    • 服务端验证信息无误后,将包含accessTokenuserNameSteve)、selectedProfile和客户端IP的SessionData存入Redis键名为Join_ + serverId
  4. 服务器验证
    • 游戏客户端连接到Minecraft服务器。
    • 服务器向本会话服务的/sessionserver/session/minecraft/hasJoined API发起请求携带serverIdusernameSteve和客户端IP。
    • 服务端查找Redis中对应的会话数据并验证userNameip是否匹配。
    • 如果验证通过,服务端返回Steve的完整档案信息包括皮肤URL服务器允许玩家加入游戏否则连接被拒绝。
flowchart TD
A[Minecraft客户端] --> |1. authenticate| B[/authserver/authenticate]
B --> C{获取 accessToken 和 UUID}
C --> D[选择配置文件]
D --> E[点击加入服务器]
E --> |3. join| F[/sessionserver/join]
F --> G[Redis: 存储会话]
E --> H[Minecraft服务器]
H --> |4. hasJoined| I[/sessionserver/hasJoined]
I --> J[Redis: 查找会话]
J --> K{验证通过?}
K --> |是| L[返回玩家档案]
L --> M[允许加入游戏]
K --> |否| N[拒绝连接]

**Diagram sources **

Section sources

错误处理与日志

系统在关键操作点都集成了详细的错误处理和日志记录:

  • 输入验证对所有API的输入参数进行严格校验如空值、格式错误等并返回清晰的错误信息。
  • 业务逻辑错误:对于令牌无效、配置文件不匹配、用户名不匹配等情况,返回特定的错误码和消息。
  • 系统错误对数据库查询失败、Redis操作失败、JSON序列化/反序列化失败等底层错误进行捕获和记录。
  • 日志记录:使用zap日志库,对关键操作(如“玩家成功加入服务器”、“会话验证失败”)进行结构化日志记录,便于问题追踪和审计。

Section sources

测试验证

系统的功能通过单元测试得到了充分验证,确保了核心逻辑的正确性。

  • 常量验证:测试确认SessionKeyPrefix常量值为"Join_"SessionTTL为15分钟。
  • 输入验证:对JoinServerHasJoinedServer的输入参数(空值、格式)进行了全面的边界测试。
  • 逻辑验证:测试了JoinServer的会话键生成逻辑,确保serverId能正确拼接成Join_serverId的格式。
  • 匹配逻辑:验证了HasJoinedServer的用户名和IP地址匹配逻辑确保大小写敏感和IP比对的正确性。
graph TD
A[测试用例] --> B[TestYggdrasilService_Constants]
A --> C[TestJoinServer_InputValidation]
A --> D[TestHasJoinedServer_InputValidation]
A --> E[TestJoinServer_SessionKey]
A --> F[TestHasJoinedServer_UsernameMatching]
A --> G[TestHasJoinedServer_IPMatching]

**Diagram sources **

Section sources