ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

OpenFrontIO 认证与授权机制解析:HTTP-only 刷新令牌、短寿命 JWT 与 WebSocket 连接时鉴权

OpenFrontIO 认证与授权机制解析:HTTP-only 刷新令牌、短寿命 JWT 与 WebSocket 连接时鉴权 游戏开发后端【免费下载链接】OpenFrontIOOnline browser-based RTS game项目地址https://gitcode.com/gh_mirrors/op/OpenFrontIO点击查看免费下载导读本文基于 docs/Auth.md 展开系统讲解 OpenFrontIO浏览器端实时 RTS 游戏的完整认证与授权链路从 HTTP-only Cookie 中的长寿命刷新令牌到内存中 15 分钟有效的短寿命 JWT再到 WebSocket 建连时的一次性令牌校验与clientID persistentID身份映射。通过本文你将掌握该项目双令牌模型的取舍依据、JWT 载荷结构与签名校验细节、建连后的轻量级授权设计以及开发模式下 persistentID 直通带来的安全边界。文中所有结论均以当前仓库的源码实现src/server/jwt.ts、src/client/Auth.ts、src/server/Worker.ts 等为依据。一、双令牌模型长期会话与短期凭证的分工OpenFrontIO 的会话体系由两类令牌共同支撑职责完全不同令牌生命周期存储位置用途刷新令牌refresh token30 天 TTLHTTP-only Cookiecredentials: include随请求自动携带长期维持登录态用于向 API 换取新 JWT访问令牌JWT15 分钟 TTL仅内存页面刷新即丢失向游戏服务器证明身份进入对局1.1 令牌交换与轮换用户持刷新令牌向 API 服务器发起交换请求获得一个短寿命 JWT同时刷新令牌被轮换rotate——旧令牌作废签发新令牌。这在客户端对应的实现是 src/client/Auth.ts 中的doRefreshJwt()const response await fetch(getApiBase() /auth/refresh, { method: POST, credentials: include, // 携带 HTTP-only 刷新 Cookie signal: AbortSignal.timeout(10_000), // 10 秒超时兜底避免请求悬挂 });服务端返回{ jwt, expiresIn }客户端据此更新内存中的 JWT 与过期时间const { jwt, expiresIn } json; __expiresAt Date.now() expiresIn * 1000; __jwt jwt;对401的处理是终局的服务端已清除刷新 Cookie说明会话本身已死直接走logOut()src/client/Auth.ts。而对5xx、429或网络失败则视为瞬态错误只清空内存 JWT、保留 Cookie下次userAuth()时再重试避免在中间态误删一个仍然有效的会话。1.2 15 分钟 TTL 的安全权衡JWT 的 15 分钟短 TTL 是刻意的安全设计即便令牌被泄露攻击者能利用的时间窗口也被严格压缩文档原话为 limits damage window if compromised。作为对比刷新令牌虽然寿命长达 30 天但它只存在于 HTTP-only Cookie 中JavaScript 无法读取天然抵御 XSS 窃取。1.3 仅内存存储的 JWTJWT 不写入 localStorage/sessionStorage页面刷新后即丢失。因此客户端在启动或鉴权时若发现内存中没有 JWT会立即尝试通过刷新 Cookie 重新换取见 src/client/Auth.ts 的userAuth()const jwt __jwt; if (!jwt) { if (!shouldRefresh) { return false; } await refreshJwt(); return userAuth(false); }refreshJwt()实现了单飞single-flight并发调用共享同一个__refreshPromise避免多个请求同时刷新导致令牌轮换竞态src/client/Auth.ts。1.4 提前刷新窗口userAuth()在 JWT 剩余有效期不足 3 分钟时就会主动刷新src/client/Auth.ts保证长时间挂机的页面不会带着过期令牌发起请求if (Date.now() __expiresAt - 3 * 60 * 1000) { await refreshJwt(); return userAuth(false); }二、JWT 载荷结构与签名校验2.1 载荷字段TokenPayloadJWT 的载荷由 src/core/ApiSchemas.ts 的TokenPayloadSchema定义基于 zod 校验字段类型说明jtistringJWT ID令牌唯一标识substring主体即 persistentID以 base64url 编码的 UUID 形式存放解码后为带连字符的 UUIDiatnumber签发时间戳issstring签发者issuer客户端会用它校验令牌是否来自正确的 API 服务器audstring受众audience客户端会用它校验令牌是否为本站点签发expnumber过期时间戳roleroot | admin | mod | flagged | banned | string可选账号角色服务端据此执行封禁与权限策略providerstring可选登录渠道如steam用于识别 Steam 认证身份注意sub的编码细节JWT 中携带的是 base64url 形式的 UUID而客户端代码中各处使用的是带连字符的虚线 UUID。因此 src/client/Auth.ts 的isSessionActive()会先把sub用base64urlToUuid()转回虚线形式再比较const raw decodeJwt(__jwt).sub; return base64urlToUuid(raw) sub;2.2 签名算法与密钥获取服务端通过 jose 库的jwtVerify校验令牌算法限定为EdDSAEd25519 曲线并同时校验issuer与audiencesrc/server/jwt.tsconst { payload } await jwtVerify(token, key, { algorithms: [EdDSA], issuer, audience, });公钥来自 API 的 JWKS 端点GET {issuer}/.well-known/jwks.json首次使用时拉取并缓存src/server/ServerEnv.ts。issuer与audience由环境推导src/server/ServerEnv.tsaudience取环境变量DOMAINissuer在本地开发DOMAIN localhost时为http://localhost:8787否则为https://api.{DOMAIN}。JwksSchema严格校验 JWKS 返回的密钥形状EdDSA/Ed25519/OKP解析失败即抛错拒绝启动校验流程src/server/ServerEnv.ts。2.3 客户端侧的 iss/aud 双保险客户端不联网验签而是用decodeJwt仅解码、不验签读取iss与aud后做轻量比对src/client/Auth.tsif (iss ! getApiBase()) { // JWT 不是由正确的服务器签发 logOut(); return false; } const myAud getAudience(); if (myAud ! localhost aud ! myAud) { // JWT 不是为本站点签发的 logOut(); return false; }iss/aud任一不符即判定为非法会话并主动登出防止跨站令牌如从一个域名窃取的 JWT在本站被冒用。三、WebSocket 连接时鉴权一次性校验的核心流程3.1 建连时刻完成全部令牌验证这是本文档最关键的设计JWT 的验证只发生在 WebSocket 建连瞬间。连接建立后不再做任何令牌级验证后续仅靠已建立的身份映射做轻量检查。完整的建连鉴权流水线位于 src/server/Worker.ts 的 websocket 消息处理中校验令牌签名与声明verifyClientToken(clientMsg.token)失败则用CloseCode.InternalErrorCloseReason.InvalidToken关闭连接封禁检查claims?.role banned时立即以CloseCode.Banned断开src/server/Worker.ts断线重连分流clientMsg.type rejoin走rejoinOrClose凭 persistentID 匹配既有席位src/server/Worker.ts首次加入的门禁与改名检查通过planJoinVerifyverifyJoin对接 API 的join_verify端点同时完成 Turnstile 人机校验与用户名/部落标签审查src/server/Worker.ts拉取账号信息对非匿名客户端调用getUserMe()GET /users/me获取 flares、publicId、好友列表、部落标签、信任等级等src/server/Worker.ts创建客户端对象并加入对局见下节。3.2 clientID persistentID 映射的建立鉴权通过后服务端为连接实例化一个 Client 对象其核心字段正是文档所说的映射constructor( public readonly clientID: ClientID, // 本次连接的随机标识 public readonly persistentID: string, // 经 JWT 验证后的持久身份 public readonly claims: TokenPayload | null, ... )clientID是本次 WebSocket 连接的随机标识persistentID是从 JWT 的sub中解出的持久身份。二者一经绑定这个连接有权代表这个持久身份行事这一事实就被固化下来——这正是 docs/Auth.md 中 establishing that this client is authorized to act on behalf of this persistent identity 的落地实现。3.3 建连后的轻量授权以暂停操作为例连接建立后所有游戏内操作不再验签而是基于映射做所有权检查。文档以暂停请求pause为例说明简单的所有权检查即可。在 src/server/IntentAuthorization.ts 中toggle_pause意图的授权逻辑是纯身份比对与令牌无关case toggle_pause: if (!actor.isLobbyCreator !actor.isAdminBot) { return { status: 403, error: only the lobby creator can pause }; } if (game.isListed !actor.isAdminBot) { return { status: 403, error: the host cannot pause a publicly listed game }; } if (!game.hasStarted) { return { status: 409, error: game not started }; } return null;actor.isLobbyCreator来自clientID/persistentID与房主身份的比对全程无需重新解析令牌。这种连接时验一次、之后按映射授权的设计把每次游戏操作的开销从一次签名验证降为一次内存查询对每 tick 都有大量意图流量的 RTS 游戏至关重要。3.4 会话失效的判定与关闭码若建连后账号会话失效如/users/me返回 401/403服务端通过 src/server/jwt.ts 的userMeFailureClose()映射关闭码401/403会话本身被 API 拒绝重试同样令牌不可能成功以终态码UnauthorizedInvalidToken关闭让客户端引导玩家重新登录其他错误5xx、网络、响应格式错误可能是瞬态以InternalErrorAccountLookupFailed关闭客户端可重试。四、开发模式persistentID 直通的安全降级4.1 实现细节文档明确指出本地开发时 API 服务器不运行因此游戏服务退化为仅校验 persistentID。这在 src/server/jwt.ts 中有精确实现if (PersistentIdSchema.safeParse(token).success) { if (ServerEnv.env() GameEnv.Dev) { return { type: success, persistentId: token, claims: null }; } else { return { type: error, message: persistent ID not allowed in production }; } }逻辑分两层传入的 token 若本身就是一个合法 UUIDsrc/core/Schemas.ts 的PersistentIdSchema即z.uuid()则视为 persistentID 直传该直传仅在GAME_ENVdev时放行任何非开发环境GameEnv.Preprod/GameEnv.Prod直接拒绝persistent ID not allowed in production。对应地客户端在 src/client/Auth.ts 中提供了本地持久身份的回退getPlayToken()在拿不到 JWT 时返回 localStorage 中保存的 persistentID键为player_persistent_id并在首次访问时用generateCryptoRandomUUID()生成。源码中两处WARNING: DO NOT EXPOSE THIS ID注释都在强调该 ID 只能作为开发/本地兜底绝不能对外暴露。4.2 安全风险文档明确警告了这一降级的安全性代价只要窃取 persistentID攻击者便对受害者账号拥有无限期控制权stealing a persistentID means the attacker has indefinite control of the victims account。原因有三开发模式下服务端完全跳过签名校验persistentID 是长期固定的存于 localStorage不轮换缺少 JWT 的 15 分钟 TTL 约束没有时间窗口限制。这也解释了为何生产代码对 persistentID 直传是硬拒绝而非警告放行。此外src/server/Worker.ts 还展示了一层配套防线当claims null即匿名 persistentID 身份且部署方配置了ALLOWED_FLARES时匿名用户会被直接拒绝加入LoginRequired关闭码——说明生产环境的匿名游客路径是被显式管控的。五、平台化登录分支Steam 与 CrazyGames 的特殊会话作为补充src/client/Auth.ts 展示了双令牌模型在不同托管平台上的适配这是原文档未展开、但理解其设计必要的内容常规 Web走/auth/refresh依赖 HTTP-only 刷新 CookieSteam 桌面壳app://openfront协议刷新 Cookie 因跨站cross-site不可用改为在 JWT 过期时用 Steam Web-API 票据重新换会话/auth/steam见 src/client/Auth.tsCrazyGames iframe同样因SameSiteLax导致 Cookie 不可用改为用 CrazyGames 用户令牌重新换会话/auth/crazygames见 src/client/Auth.ts。这些分支都遵循同一原则会话状态一律只放内存过期即通过平台凭据重新换取与JWT 仅内存存储的核心设计保持一致。Steam 路径中provider steam声明还作为防滥用信号使 Steam 认证的首次加入可以跳过 Turnstile 人机挑战见 src/server/JoinVerify.ts。六、端到端流程汇总综合文档与源码一次完整的登录与进入对局可以概括为以下链路用户登录OAuth / 令牌 / Steam 票据 → API 设置 30 天 HTTP-only 刷新 Cookie → 换取 15 分钟 JWT内存持有刷新令牌同步轮换 → 加入对局WebSocket 建连时携带 JWT → 服务端 verifyClientTokenEdDSA 验签 iss/aud 校验 → 建立 clientID persistentID 映射Client 对象 → rolebanned 检查 / join_verify 门禁 / getUserMe 账号信息 → 后续所有游戏意图仅做基于映射的轻量授权如房主暂停权限 → JWT 临期3 分钟或页面刷新后自动用刷新 Cookie 重新换取 → 本地开发降级为 persistentID 直通仅 GAME_ENVdev 允许七、结语OpenFrontIO 的认证体系是一个重连接、轻运行的典型设计把最昂贵的签名验证集中到 WebSocket 建连这一唯一入口之后依靠clientID persistentID的固定映射支撑整个对局过程中的高频授权检查通过 30 天 Cookie 与 15 分钟内存 JWT 的双令牌组合在会话持久性与泄露损害窗口之间取得平衡并用环境门禁把不安全的 persistentID 直通严格限制在开发模式。对于构建实时多人游戏或任何长连接系统的开发者这一套流程在安全成本与运行时开销之间的取舍思路具有直接的参考价值。更完整的系统上下文可继续阅读 docs/API.md、docs/MultiServer.md 与 src/server/README.md。赞分享游戏开发后端【免费下载链接】OpenFrontIOOnline browser-based RTS game项目地址https://gitcode.com/gh_mirrors/op/OpenFrontIO点击查看免费下载相关推荐LangWatch认证授权JWT令牌与角色权限控制LangWatch认证授权JWT令牌与角色权限控制 概述 LangWatch作为一款先进的AI应用监控平台采用了现代化的认证授权体系基于NextAuth.WebSocket连接认证最佳实践JWT令牌刷新机制完整指南WebSocket连接认证最佳实践JWT令牌刷新机制完整指南 在现代Web应用中 WebSocket连接认证 和 JWT令牌刷新机制 是确保实时通信安全性的后端网络LobeChat认证授权JWT令牌验证机制完整指南LobeChat认证授权JWT令牌验证机制完整指南 LobeChat作为一款现代化的开源AI聊天框架其安全认证体系采用了业界标准的JWT令牌验证机制为用户人工智能AI 应用大模型AI Agent多智能体工具调用前端后端上一篇Webiny 会话交接剖析Timer 通用化重构与 Elasticsearch/OpenSearch 同步测试修复实战下一篇Codis中的数据备份策略全量备份与增量备份结合方案创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表