JWT 与房间鉴权(服务端详解)
xymj-colyseus-server 将 HTTP 签发的 JWT 与 Colyseus 进房 打通:服务端通过 @RequireAuth()、JWTUtil 与 Redis 会话存储完成校验。与 Colyseus 官方概念对照:Authentication、Room Authentication。
1. 两条通道:HTTP API vs WebSocket 房间
| 通道 | 典型用法 | 服务端校验 |
|---|---|---|
| HTTP | Authorization: Bearer <accessToken> | authMiddleware:校验 JWT 签名,写入 req.user |
| Colyseus 房间 | joinOrCreate / joinById 的 options | @RequireAuth():校验 JWT + Redis 会话 |
HTTP 与房间 共用同一套访问令牌(access token 字符串),但房间侧额外依赖 Redis(见下文)。
2. 访问令牌从哪里来
以下路径都会在成功后签发 访问令牌(响应里的 token 或业务返回体中的 tokens.accessToken,以接口为准),并把令牌写入 Redis:
POST /api/auth/register、POST /api/auth/login:AuthService写入access_token:${userId}、refresh_token:${userId}。GET /api/minigame/weixin/login、GET /api/minigame/douyin/login:AccessTokenAndLoginService在创建/查找本地用户后同样写入上述 Redis 键。
JWT 由 jsonwebtoken 使用配置项 JWT_SECRET(及过期时间 JWT_EXPIRES_IN 等,见 配置说明)签名;payload 至少包含 userId,并可含 username、email、openid、platform 等扩展字段(小游戏登录会带平台相关声明)。
3. 加入房间时必须带什么
对被 @RequireAuth() 保护的 onCreate / onJoin,客户端应在 options 中传入:
(客户端代码)
token: "<accessToken>"或使用同义字段 accessToken(二者等价,实现见服务端 RequireAuth)。
缺省时服务端抛出「未提供认证令牌」,连接将被拒绝。
4. @RequireAuth() 校验顺序(核心逻辑)
装饰器写在 Room 的 onJoin / onCreate 等方法上的写法与注意事项(示例代码)见 鉴权与权限 · 注解(装饰器)。
实现位于服务端 src/utils/decorators/RequireAuth.ts,流程概括为:
NODE_ENV === 'test':直接放行(便于单测不依赖 Redis)。- 从 房间 options 读取
token或accessToken。 JWTUtil.verifyAccessToken:验证签名与有效期;失败则拒绝。- Redis:
- 若存在键
access_token:${userId}:必须与本次传入的 token 字符串完全一致,否则「令牌不匹配」。 - 若 没有
access_token:则要求存在refresh_token:${userId},否则视为「令牌已过期或无效」。
- 若存在键
通过后,会把 userId、username、email 以及完整 tokenPayload 写回 options,房间逻辑里可直接使用(例如聊天频道里的 guildId / teamId 仍由客户端 options 传入,见 消息协议)。
设计含义(接入时要注意)
- 仅 JWT 合法还不够:默认还要求 Redis 里仍有登录会话;单机调试若清空 Redis,会出现 JWT 未过期但进房失败。
- 多端登录 / 顶号:新登录会覆盖 Redis 中的
access_token;旧客户端仍持旧 token 时,会因「令牌不匹配」被拒绝进房(有利于会话一致性,可按产品需求再扩展)。 userId语义:账号密码与小游戏统一为库表用户主键(UUID 字符串);小游戏登录不再用 openid 作为 JWT 的userId。
5. HTTP 接口侧的 JWT
authMiddleware(src/middleware/auth.middleware.ts)从 Authorization: Bearer <token> 解析令牌,校验签名后挂载到 req.user。与房间的 Redis 二次校验 独立:某些 HTTP 路由若只用该中间件,行为与 @RequireAuth() 不完全相同,以具体控制器是否叠加校验为准。
6. 与各引擎客户端的衔接
客户端需在 建立 Colyseus 连接之后、加入房间时 把 HTTP 拿到的 access token 放进 join options(具体 API 见各引擎官方文档):
- Unity:
JoinOrCreate<MyState>("room_name", new Dictionary<string, object> { ["token"] = accessToken })(字段名与官方示例一致即可)。 - Cocos / JS:
client.joinOrCreate("room_name", { token: accessToken })。 - Godot:join 参数字典中传入
token或accessToken(与 Colyseus Godot 文档中的 options 一致)。
更完整的引擎安装与官方教程索引见:
7. 常见问题
- JWT 刚刷新但进房仍失败:确认服务端是否同步更新了 Redis 的
access_token:${userId};仅本地换新 JWT 而不同步 Redis,会与@RequireAuth不一致。 - Redis 挂了或未连接:
RequireAuth中取 Redis 可能失败,表现为授权失败;生产环境应保证 Redis 可用或与代码策略一致。 - 压测房:
loadtest_room等为无鉴权场景专用,不参与 JWT 进房流程;勿与业务房间混淆。