登录验证
游戏客户端在调用 Combo SDK 的 Login() 成功完成登录后,会得到 Identity Token。
Identity Token 是世游服务端签发的一个 JWT,包含了客户端的可信身份信息。
游戏客户端应当将 Identity Token 发送给游戏服务端,由游戏服务端对其做登录验证,并从中获得游戏客户端的身份信息。
签名算法
Identity Token 是用 HS256 算法进行签名的。
签名的私钥是世游给游戏分配的 Secret Key,游戏服务端需要用它验证 JWT 的签名。
Claims
Identity Token 的 payload 中会包含的 claim 如下:
| Claim | Type | Description |
|---|---|---|
| iss | string | 颁发 JWT 的服务器,即世游 REST API 的端点。 |
| aud | string | 世游分配的游戏 ID,也就是 Game ID,用于标识游戏项目。 |
| sub | string | 用户 ID。使用世游分配的聚合用户 ID,也就是 Combo ID。 |
| iat | integer | JWT 颁发时间。Unix timestamp in seconds。 |
| exp | integer | JWT 的过期时间。Unix timestamp in seconds。 |
| scope | string | JWT 的作用域,固定为 auth。 |
| idp | string | 世游定义的 Identity Provider (IdP) 标识。取值参见 Go SDK 中的 IdP 常量定义。 |
| external_id | string | 和 idp 对应的外部身份的标识。 |
| external_name | string | 和 idp 对应的外部身份中的全名,用于展示在 UI 上。 |
| weixin_session_key | string | 微信小游戏登录的 session_key。仅在 idp 为 minigame_weixin 时才有值。此字段为加密字段,游戏服务端需解密后使用。 |
| distro | string | 游戏客户端的发行版本标识。游戏侧可将它用于服务端数据埋点。 |
| variant | string | 游戏客户端的分包标识。仅在客户端是分包时才有值。游戏侧可将它用于服务端数据埋点。 |
| age | integer | 根据用户的实名认证信息得到的年龄。0 表示未知。 |
| device_id | string | 游戏客户端运行设备的唯一 ID,用于标识设备。 |
| reg_time | integer | 用户 ID(Combo ID)注册时间。Unix timestamp in seconds。 |
session_key 的使用
session_key 是微信小游戏颁发的会话密钥,游戏服务端可用其解密客户端从微信 API 获取的加密用户数据,或验证微信返回数据的签名完整性。详见微信官方文档:用户数据的签名验证和加解密。
获取与缓存:客户端登录成功后,服务端应从 Identity Token 中解密 weixin_session_key,并将得到的 session_key 与用户会话关联缓存,以供后续使用。每次客户端重新调用 Login 时都会生成新的 session_key,服务端应及时更新缓存。
失效处理:若使用 session_key 时出现解密失败或签名验证失败,应视为 session_key 已失效,此时需引导客户端重新调用 Login 获取新的 session_key。
session_key 具有时效性,应仅在当前登录会话中使用,不应长期缓存或跨会话复用。
session_key 必须严格保存在游戏服务端,不得通过任何方式传回给客户端。一旦泄露,攻击者可用其伪造或篡改用户数据。
示例
假定:
- 世游 API 端点
https://api.seayoo.com - Game ID 为
xcom - Secret Key 为
sk_secret
则一个长期有效的,具有合法 Claims 与 Signature 的 Identity Token 示例是:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2FwaS5zZWF5b28uY29tIiwic3ViIjoiOTEyMzEyMjMzNDYxMzAwMDEiLCJhdWQiOiJ4Y29tIiwiZXhwIjo0MTAzODM0ODM3LCJpYXQiOjE3MDM3NDg0MzcsInNjb3BlIjoiYXV0aCIsImlkcCI6InNlYXlvbyIsImV4dGVybmFsX2lkIjoiMTk5OSIsImV4dGVybmFsX25hbWUiOiIqKuWzsCIsImRpc3RybyI6ImFuZHJvaWQiLCJ2YXJpYW50IjoiaHlrYiIsImFnZSI6MTYsImRldmljZV9pZCI6ImU0NmFlZjNjZmE1NzE5NTUiLCJyZWdfdGltZSI6MTcwMzMyNDIxM30.gaoHOMBo5tcLwBFURQ7IsAr1m-xx01HlRQEG9DYQKTI
这个 Identity Token 解码出的 Claims 为:
{
"iss": "https://api.seayoo.com",
"aud": "xcom",
"sub": "91231223346130001",
"iat": 1703748437,
"exp": 4103834837,
"scope": "auth",
"idp": "seayoo",
"external_id": "1999",
"external_name": "**峰",
"distro": "android",
"variant": "hykb",
"age": 16,
"device_id": "e46aef3cfa571955",
"reg_time": 1703324213
}
age 来自行处理防沉迷age 不保证返回精确的年龄信息,仅保证用于防沉迷处理时的准确度够用。例如:
- 当某个用户真实年龄为 35 岁时,
age可能返回 18 - 当某个用户真实年龄为 17 岁时,
age可能返回 16
加密字段
Identity Token 中部分敏感字段在写入 JWT 前会经过加密处理,以防止敏感数据在传输链路中被直接读取。目前采用此机制的字段有:
weixin_session_key
加密算法:AES-256-GCM
加密密钥:游戏的 Secret Key
使用服务端 SDK
使用 Go 或 Node.js 服务端 SDK 时,SDK 在验证 Identity Token 的同时会自动完成解密,游戏服务端可直接读取明文字段值,无需手动处理解密逻辑。
不使用服务端 SDK
如果游戏服务端未使用 Combo 服务端 SDK,需在验证 JWT 签名后,对加密字段手动进行 AES-256-GCM 解密,密钥为游戏的 Secret Key。密文格式及解密实现可参考服务端 SDK 的源码:combo-sdk-go / combo-sdk-node。
验证逻辑
游戏服务端应当对 Identity Token 进行验证,验证不通过说明游戏客户端身份不可信,应当拒绝客户端进入游戏。
验证内容如下:
- Token 能够被解析成一个结构正确的 JWT
- Token 的
iss和当前 API 服务端点匹配,举例https://api.seayoo.com - Token 的
scope为auth - Token 的
aud与世游分配的 Game ID 匹配 - Token 的
exp字段指示的过期时间,服务端的当前时间尚未达到 - Token 的
HS256签名与服务端使用 Secret Key 计算出的签名匹配
如果上述验证均通过,说明 Identity Token 是可信的,游戏服务端可以使用 Claims 用于登录环节的业务逻辑处理。
有效期
Identity Token 的有效期目前为 168 小时(7 天)。
开源库
JWT 的开源库非常多,游戏服务端可在 https://jwt.io/libraries 中选择适合自己的。