diff --git a/services/game-service/internal/service/game/baishun_adapter.go b/services/game-service/internal/service/game/baishun_adapter.go index a3b4c6e1..5306f11c 100644 --- a/services/game-service/internal/service/game/baishun_adapter.go +++ b/services/game-service/internal/service/game/baishun_adapter.go @@ -341,6 +341,13 @@ func (s *Service) validBaishunSession(ctx context.Context, app string, token str if token == "" { return gamedomain.LaunchSession{}, xerr.New(xerr.Unauthorized, "token invalid") } + // 百顺的 ss_token 直接复用 App access token;同一个登录 token 可能打开多款百顺游戏。 + // JWT 必须优先解析,否则会先命中旧启动会话的 hash,并被旧 provider_game_id 误判为跨游戏复用。 + if strings.Count(token, ".") == 2 { + if session, err := s.appSessionFromAccessToken(app, token); err == nil { + return session, nil + } + } if session, err := s.validYomiSession(ctx, app, token); err == nil { return session, nil } diff --git a/services/game-service/internal/service/game/service_test.go b/services/game-service/internal/service/game/service_test.go index 5e5485d1..08351335 100644 --- a/services/game-service/internal/service/game/service_test.go +++ b/services/game-service/internal/service/game/service_test.go @@ -1001,6 +1001,54 @@ func TestHandleBaishunSSTokenUserInfoUpdateAndChangeBalance(t *testing.T) { } } +func TestHandleBaishunAccessTokenIgnoresStaleLaunchSessionGameID(t *testing.T) { + secret := "baishun-test-app-key" + userID := int64(312900923573673984) + token := leaderccTestAccessToken(t, "lalu", userID, "163004", 1700003600) + repo := &fakeRepository{ + platform: gamedomain.Platform{ + PlatformCode: "baishun", + AdapterType: gamedomain.AdapterBaishunV1, + CallbackSecretCiphertext: secret, + AdapterConfigJSON: `{"app_id":7482496867,"app_channel":"bobi","uid_mode":"display_user_id"}`, + }, + // 同一个 App access token 会被百顺 H5 作为 ss_token 复用;旧启动会话只说明曾经打开过别的游戏。 + // 回调验权必须以 JWT 里的当前用户身份为准,否则 user_info 会被旧 provider_game_id 拦成 token invalid。 + session: gamedomain.LaunchSession{ + AppCode: "lalu", + SessionID: "old_baishun_launch_session", + UserID: userID, + DisplayUserID: "163004", + PlatformCode: "baishun", + ProviderGameID: "1006", + LaunchTokenHash: stableHash(token), + Status: gamedomain.SessionActive, + ExpiresAtMS: 1700086400000, + }, + } + wallet := &fakeWallet{balanceAfter: 1880} + user := &fakeUser{} + svc := New(Config{}, repo, wallet, user) + svc.now = func() time.Time { return time.UnixMilli(1700000000000) } + + raw, _, err := svc.HandleCallback(context.Background(), &gamev1.CallbackRequest{ + Meta: &gamev1.RequestMeta{RequestId: "req-baishun-stale-session", AppCode: "lalu"}, + PlatformCode: "baishun", + Operation: "get_user_info", + RawBody: baishunTestBody(t, secret, `{"app_id":7482496867,"user_id":"163004","ss_token":"`+token+`","provider_name":"bobi","client_ip":"104.28.244.150","game_id":1043,"currency_type":0}`, 1700000000), + RemoteAddr: "3.1.174.194:443", + }) + if err != nil { + t.Fatalf("HandleCallback get_user_info failed: %v", err) + } + if !strings.Contains(string(raw), `"code":0`) || strings.Contains(string(raw), "token invalid") || !strings.Contains(string(raw), `"user_id":"163004"`) { + t.Fatalf("baishun access token should ignore stale launch session game id: %s", raw) + } + if user.lastGet.GetUserId() != userID { + t.Fatalf("baishun userinfo must query JWT user, got %+v", user.lastGet) + } +} + func TestHandleCallbackChangeCoinUsesWalletAndOrderIdempotency(t *testing.T) { repo := &fakeRepository{ launchable: gamedomain.LaunchableGame{CatalogItem: gamedomain.CatalogItem{GameID: "demo_rocket_001", ProviderGameID: "rocket_001"}},