package game import ( "strconv" "strings" jwt "github.com/golang-jwt/jwt/v5" "hyapp/pkg/appcode" "hyapp/pkg/xerr" gamedomain "hyapp/services/game-service/internal/domain/game" ) func (s *Service) appSessionFromAccessToken(app string, token string) (gamedomain.LaunchSession, error) { // ZeeOne/灵仙都直接把 App access token 作为厂商 sessionId/token;game-service 不持有 JWT secret, // 这里只消费 token 内部用户快照并校验 exp/app_code,入口签名仍由厂商 AppSecret 负责。 claims := jwt.MapClaims{} _, _, err := new(jwt.Parser).ParseUnverified(strings.TrimSpace(token), claims) if err != nil { return gamedomain.LaunchSession{}, xerr.New(xerr.Unauthorized, "token invalid") } if typ := strings.TrimSpace(jwtStringClaim(claims, "typ")); typ != "" && typ != "access" { return gamedomain.LaunchSession{}, xerr.New(xerr.Unauthorized, "token invalid") } expiresAt, err := claims.GetExpirationTime() if err != nil || expiresAt == nil || !expiresAt.After(s.now()) { return gamedomain.LaunchSession{}, xerr.New(xerr.SessionExpired, "token expired") } tokenApp := appcode.Normalize(jwtStringClaim(claims, "app_code")) if tokenApp != "" && tokenApp != appcode.Normalize(app) { return gamedomain.LaunchSession{}, xerr.New(xerr.Unauthorized, "token invalid") } userID, err := strconv.ParseInt(strings.TrimSpace(jwtStringClaim(claims, "user_id")), 10, 64) if err != nil || userID <= 0 { return gamedomain.LaunchSession{}, xerr.New(xerr.Unauthorized, "token invalid") } displayUserID := firstNonEmpty( jwtStringClaim(claims, "display_user_id"), jwtStringClaim(claims, "default_display_user_id"), strconv.FormatInt(userID, 10), ) return gamedomain.LaunchSession{ AppCode: tokenApp, SessionID: jwtStringClaim(claims, "sid"), UserID: userID, DisplayUserID: displayUserID, Status: gamedomain.SessionActive, ExpiresAtMS: expiresAt.UnixMilli(), }, nil } func jwtStringClaim(claims jwt.MapClaims, key string) string { value, _ := claims[key].(string) return strings.TrimSpace(value) }