跨开发者账号的Google Games身份认证一致性方案问询
解决跨开发者账号的Google Play Games玩家身份统一问题
我们搭建了一套多Unity游戏对接单Node服务器的系统,目前支持用户通过Google Play在自有游戏中登录,同一玩家在不同自有游戏里靠Google提供的持久化playerId保持身份一致。现在要开放外部开发者账号的游戏接入,但同一玩家在这些外部游戏里获取到的Google Play playerId和自有游戏里的不一样,导致身份无法统一,想问有没有办法关联账号,让所有应用(不管所属开发者账号)返回的playerId保持一致?
当前服务端实现代码:
const { code, gameId } = req.query; if (!code) { throw new BadRequestError('code is required in query params'); } if (!gameId) { throw new BadRequestError('gameId is required in query params'); } const googlePlayJson = JSON.parse( process.env.GOOGLE_PLAY_JSON ) as GooglePlayJson; if (!googlePlayJson) { throw new NotFoundError( 'googlePlayJson not found please contact backend dev' ); } //based on gameId get the correct client id and secret from the array const json = googlePlayJson.find((game) => game.gameId === gameId); if (!json) { throw new NotFoundError( `gameId ${gameId} not found in env json please contact backend dev` ); } const oauth2Client = new OAuth2Client({ clientId: json.clientId, clientSecret: json.clientSecret }); const { tokens } = await oauth2Client.getToken({ code: req.query.code as string }); oauth2Client.setCredentials(tokens); const games = google.games({ version: 'v1', auth: oauth2Client }); const response = await games.players.get({ playerId: 'me', language: 'en' }); const playerId = response.data.playerId; if (!playerId) { throw new NotFoundError('playerId not found'); } const displayName = response.data.displayName; let user = await prisma.user.findUnique({ where: { playerId } }); if (!user) { user = await prisma.user.create({ data: { playerId, name: displayName } }); }
核心结论与解决方案
Google Play Games的playerId本身与开发者账号强绑定,不同开发者账号下的应用,同一玩家的playerId必然不同,无法直接让其统一。但可以通过以下方式实现跨应用的身份统一:
使用Google账号OpenID Connect获取全局统一用户ID
改用Google账号的OpenID Connect协议,获取用户的sub字段——这是Google账号全局唯一且永久不变的用户标识,不受应用所属开发者账号影响。所有接入的应用只要通过Google OAuth2/OpenID Connect授权,就能获取到同一个sub值,以此作为用户的统一身份ID。
具体实现修改步骤
- 调整授权范围:外部游戏在发起Google授权请求时,必须包含
openid email profile这些OpenID Connect相关的scope,不能仅使用Google Play Games的专属scope。 - 修改服务端验证逻辑:不再调用Google Games API获取
playerId,而是解析OAuth返回的ID Token(JWT),从中提取sub字段作为统一用户ID。
修改后的代码示例
const { code, gameId } = req.query; if (!code) { throw new BadRequestError('code is required in query params'); } if (!gameId) { throw new BadRequestError('gameId is required in query params'); } const googlePlayJson = JSON.parse( process.env.GOOGLE_PLAY_JSON ) as GooglePlayJson; if (!googlePlayJson) { throw new NotFoundError( 'googlePlayJson not found please contact backend dev' ); } const json = googlePlayJson.find((game) => game.gameId === gameId); if (!json) { throw new NotFoundError( `gameId ${gameId} not found in env json please contact backend dev` ); } const oauth2Client = new OAuth2Client({ clientId: json.clientId, clientSecret: json.clientSecret }); const { tokens } = await oauth2Client.getToken({ code: req.query.code as string }); // 解析ID Token获取全局唯一的sub字段 const ticket = await oauth2Client.verifyIdToken({ idToken: tokens.id_token, audience: json.clientId, }); const payload = ticket.getPayload(); const unifiedUserId = payload.sub; if (!unifiedUserId) { throw new NotFoundError('unified user id not found'); } const displayName = payload.name; // 使用unifiedUserId作为用户唯一标识查询或创建用户 let user = await prisma.user.findUnique({ where: { unifiedUserId } // 需要在数据库中新增unifiedUserId字段并设置唯一约束 }); if (!user) { user = await prisma.user.create({ data: { unifiedUserId, name: displayName, // 可选:保留原playerId字段,用于关联不同开发者账号下的游戏身份 // playerId: 原playerId } }); }
额外注意事项
- 数据库调整:需在用户表中新增
unifiedUserId字段,设置为唯一索引,替换原playerId的唯一标识逻辑。 - 外部游戏接入要求:外部开发者需在自己的Google Cloud项目中配置OAuth2客户端,将回调地址指向你的服务器,并确保授权请求包含OpenID Connect的scope。
- 兼容性过渡:原有自有游戏可逐步迁移到新逻辑,或同时保留两种身份标识,实现平滑过渡。
内容的提问来源于stack exchange,提问作者MirZa Dev
相关产品推荐
相关产品推荐

