IdentityTokenClaims class

Пакет: com.hypixel.hytale.server.core.auth

Файл: com/hypixel/hytale/server/core/auth/JWTValidator.java

Поля (9)

МодификаторыТипИмя
public String[] entitlements
public Long expiresAt
public Long issuedAt
public String issuer
public Long notBefore
public String scope
public String skin
public String subject
public String username

Методы (5)

МодификаторыВозвратСигнатура
public String[] getScopespublic String[] getScopes()
public UUID getSubjectAsUUIDpublic UUID getSubjectAsUUID()
public boolean hasScopepublic boolean hasScope(@Nonnull String var1)
if if(this.subject == null)
public String toLogSummarypublic String toLogSummary()

Исходный код

Показать/скрыть
class="kw">package com.hypixel.hytale.server.core.auth;

class="kw">import com.hypixel.hytale.logger.HytaleLogger;
class="kw">import com.nimbusds.jose.JWSAlgorithm;
class="kw">import com.nimbusds.jose.crypto.Ed25519Verifier;
class="kw">import com.nimbusds.jose.jwk.JWK;
class="kw">import com.nimbusds.jose.jwk.JWKSet;
class="kw">import com.nimbusds.jose.jwk.OctetKeyPair;
class="kw">import com.nimbusds.jwt.JWTClaimsSet;
class="kw">import com.nimbusds.jwt.SignedJWT;
class="kw">import java.io.IOException;
class="kw">import java.io.InputStream;
class="kw">import java.nio.charset.StandardCharsets;
class="kw">import java.nio.file.Files;
class="kw">import java.nio.file.Path;
class="kw">import java.security.cert.X509Certificate;
class="kw">import java.text.ParseException;
class="kw">import java.time.Duration;
class="kw">import java.time.Instant;
class="kw">import java.util.ArrayList;
class="kw">import java.util.List;
class="kw">import java.util.Map;
class="kw">import java.util.UUID;
class="kw">import java.util.concurrent.CompletableFuture;
class="kw">import java.util.concurrent.locks.ReentrantLock;
class="kw">import java.util.logging.Level;
class="kw">import javax.annotation.Nonnull;
class="kw">import javax.annotation.Nullable;

class="kw">public class JWTValidator {
   class="kw">private class="kw">static class="kw">final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass();
   class="kw">private class="kw">static class="kw">final long CLOCK_SKEW_SECONDS = 300L;
   class="kw">private class="kw">static class="kw">final JWSAlgorithm SUPPORTED_ALGORITHM = JWSAlgorithm.EdDSA;
   class="kw">private class="kw">static class="kw">final int MIN_SIGNATURE_LENGTH = 80;
   class="kw">private class="kw">static class="kw">final int MAX_SIGNATURE_LENGTH = 90;
   class="kw">private class="kw">static class="kw">final Duration JWKS_REFRESH_MIN_INTERVAL = Duration.ofMinutes(5L);
   class="kw">private class="kw">static class="kw">final String JWKS_BUNDLED_RESOURCE = "/jwks_bundled.json";
   class="kw">private class="kw">static class="kw">final String JWKS_CACHE_FILE = ".jwks.json";
   class="kw">private class="kw">final SessionServiceClient sessionServiceClient;
   class="kw">private class="kw">final String expectedIssuer;
   class="kw">private class="kw">final String expectedAudience;
   class="kw">private class="kw">volatile JWKSet cachedJwkSet;
   class="kw">private class="kw">final ReentrantLock jwksFetchLock = new ReentrantLock();
   class="kw">private class="kw">volatile CompletableFuture<JWKSet> pendingFetch = null;
   class="kw">private class="kw">volatile Instant lastJwksRefresh;

   class="kw">public JWTValidator(@Nonnull SessionServiceClient var1, @Nonnull String var2, @Nonnull String var3) {
      this.sessionServiceClient = var1;
      this.expectedIssuer = var2;
      this.expectedAudience = var3;
      this.preSeedJwksCache();
   }

   @Nullable
   class="kw">private class="kw">static String validateJwtStructure(@Nonnull String var0, @Nonnull String var1) {
      if (var0.isEmpty()) {
         class="kw">return var1 + " is empty";
      }

      String[] var2 = var0.split("\\.", -1);
      if (var2.length != 3) {
         class="kw">return String.format("%s has invalid format (expected 3 parts, got %d)", var1, var2.length);
      }

      if (var2[2].isEmpty()) {
         class="kw">return var1 + " has empty signature - possible signature stripping attack";
      }

      int var3 = var2[2].length();
      class="kw">return var3 >= 80 && var3 <= 90 ? null : String.format("%s has invalid signature length: %d (expected %d-%d)", var1, var3, 80, 90);
   }

   @Nullable
   class="kw">public JWTValidator.JWTClaims validateToken(@Nonnull String var1, @Nullable X509Certificate var2) {
      String var3 = validateJwtStructure(var1, "Access token");
      if (var3 != null) {
         LOGGER.at(Level.WARNING).log("%s", var3);
         class="kw">return null;
      }

      try {
         SignedJWT var4 = SignedJWT.parse(var1);
         JWSAlgorithm var5 = var4.getHeader().getAlgorithm();
         if (!SUPPORTED_ALGORITHM.equals(var5)) {
            LOGGER.at(Level.WARNING).log("Unsupported JWT algorithm: %s (expected EdDSA)", var5);
            class="kw">return null;
         }

         if (!this.verifySignatureWithRetry(var4)) {
            LOGGER.at(Level.WARNING).log("JWT signature verification failed");
            class="kw">return null;
         }

         JWTClaimsSet var6 = var4.getJWTClaimsSet();
         JWTValidator.JWTClaims var7 = new JWTValidator.JWTClaims();
         var7.issuer = var6.getIssuer();
         var7.audience = var6.getAudience() != null && !var6.getAudience().isEmpty() ? (String)var6.getAudience().get(0) : null;
         var7.subject = var6.getSubject();
         var7.username = var6.getStringClaim("username");
         var7.issuedAt = var6.getIssueTime() != null ? var6.getIssueTime().toInstant().getEpochSecond() : null;
         var7.expiresAt = var6.getExpirationTime() != null ? var6.getExpirationTime().toInstant().getEpochSecond() : null;
         var7.notBefore = var6.getNotBeforeTime() != null ? var6.getNotBeforeTime().toInstant().getEpochSecond() : null;
         Map var8 = var6.getJSONObjectClaim("cnf");
         if (var8 != null) {
            var7.certificateFingerprint = (String)var8.get("x5t#S256");
         }

         if (!this.expectedIssuer.equals(var7.issuer)) {
            LOGGER.at(Level.WARNING).log("Invalid issuer: expected %s, got %s. Token: %s", this.expectedIssuer, var7.issuer, var7.toLogSummary());
            class="kw">return null;
         } else if (!this.expectedAudience.equals(var7.audience)) {
            LOGGER.at(Level.WARNING).log("Invalid audience: expected %s, got %s. Token: %s", this.expectedAudience, var7.audience, var7.toLogSummary());
            class="kw">return null;
         } else {
            long var9 = Instant.now().getEpochSecond();
            if (var7.expiresAt == null) {
               LOGGER.at(Level.WARNING).log("Access token missing expiration claim. Token: %s", var7.toLogSummary());
               class="kw">return null;
            } else if (var9 >= var7.expiresAt + 300L) {
               LOGGER.at(Level.WARNING).log("Token expired (exp: %d, now: %d). Token: %s", var7.expiresAt, var9, var7.toLogSummary());
               class="kw">return null;
            } else if (var7.notBefore != null && var9 < var7.notBefore - 300L) {
               LOGGER.at(Level.WARNING).log("Token not yet valid (nbf: %d, now: %d). Token: %s", var7.notBefore, var9, var7.toLogSummary());
               class="kw">return null;
            } else if (var7.issuedAt != null && var7.issuedAt > var9 + 300L) {
               LOGGER.at(Level.WARNING).log("Token issued in the future (iat: %d, now: %d). Token: %s", var7.issuedAt, var9, var7.toLogSummary());
               class="kw">return null;
            } else if (!CertificateUtil.validateCertificateBinding(var7.certificateFingerprint, var2)) {
               LOGGER.at(Level.WARNING).log("Certificate binding validation failed. Token: %s", var7.toLogSummary());
               class="kw">return null;
            } else if (var7.getSubjectAsUUID() == null) {
               LOGGER.at(Level.WARNING).log("Access token has invalid or missing subject UUID. Token: %s", var7.toLogSummary());
               class="kw">return null;
            } else {
               LOGGER.at(Level.INFO).log("JWT validated successfully for user %s. Token: %s", var7.username, var7.toLogSummary());
               class="kw">return var7;
            }
         }
      } catch (ParseException var11) {
         ((HytaleLogger.Api)LOGGER.at(Level.WARNING).withCause(var11)).log("Failed to parse JWT");
         class="kw">return null;
      } catch (Exception var12) {
         ((HytaleLogger.Api)LOGGER.at(Level.WARNING).withCause(var12)).log("JWT validation error");
         class="kw">return null;
      }
   }

   class="kw">private boolean verifySignature(SignedJWT var1, JWKSet var2) {
      try {
         String var3 = var1.getHeader().getKeyID();
         OctetKeyPair var4 = null;

         for (JWK var6 : var2.getKeys()) {
            if (var6 class="kw">instanceof OctetKeyPair var7 && (var3 == null || var3.equals(var6.getKeyID()))) {
               var4 = var7;
               break;
            }
         }

         if (var4 == null) {
            LOGGER.at(Level.WARNING).log("No Ed25519 key found for kid=%s", var3);
            class="kw">return false;
         }

         Ed25519Verifier var9 = new Ed25519Verifier(var4);
         boolean var10 = var1.verify(var9);
         if (var10) {
            LOGGER.at(Level.FINE).log("JWT signature verified with key kid=%s", var3);
         }

         class="kw">return var10;
      } catch (Exception var8) {
         ((HytaleLogger.Api)LOGGER.at(Level.WARNING).withCause(var8)).log("JWT signature verification failed");
         class="kw">return false;
      }
   }

   @Nullable
   class="kw">private JWKSet getJwkSet() {
      class="kw">return this.getJwkSet(false);
   }

   @Nullable
   class="kw">private JWKSet getJwkSet(boolean var1) {
      if (!var1 && this.cachedJwkSet != null) {
         class="kw">return this.cachedJwkSet;
      }

      this.jwksFetchLock.lock();

      try {
         if (!var1 && this.cachedJwkSet != null) {
            class="kw">return this.cachedJwkSet;
         }

         CompletableFuture var2 = this.pendingFetch;
         if (var2 != null && !var2.isDone()) {
            this.jwksFetchLock.unlock();

            try {
               class="kw">return var2.join();
            } class="kw">finally {
               this.jwksFetchLock.lock();
            }
         }

         if (var1) {
            LOGGER.at(Level.INFO).log("Force refreshing JWKS cache (key rotation or verification failure)");
         }

         this.pendingFetch = CompletableFuture.supplyAsync(this::fetchJwksFromService);
      } class="kw">finally {
         this.jwksFetchLock.unlock();
      }

      class="kw">return this.pendingFetch.join();
   }

   @Nullable
   class="kw">private JWKSet fetchJwksFromService() {
      SessionServiceClient.JwksResponse var1 = this.sessionServiceClient.getJwks();
      if (var1 != null && var1.keys != null && var1.keys.length != 0) {
         try {
            ArrayList var2 = new ArrayList<>();

            for (SessionServiceClient.JwkKey var6 : var1.keys) {
               JWK var7 = this.convertToJWK(var6);
               if (var7 != null) {
                  var2.add(var7);
               }
            }

            if (var2.isEmpty()) {
               LOGGER.at(Level.WARNING).log("No valid JWKs found in JWKS response");
               class="kw">return this.cachedJwkSet;
            } else {
               JWKSet var9 = new JWKSet(var2);
               this.cachedJwkSet = var9;
               this.lastJwksRefresh = Instant.now();
               this.saveJwksCacheToDisk(var9);
               LOGGER.at(Level.INFO).log("JWKS loaded with %d keys (cached permanently)", var2.size());
               class="kw">return var9;
            }
         } catch (Exception var8) {
            ((HytaleLogger.Api)LOGGER.at(Level.WARNING).withCause(var8)).log("Failed to parse JWKS");
            class="kw">return this.cachedJwkSet;
         }
      } else {
         LOGGER.at(Level.WARNING).log("Failed to fetch JWKS or no keys available");
         class="kw">return this.cachedJwkSet;
      }
   }

   class="kw">private boolean verifySignatureWithRetry(SignedJWT var1) {
      JWKSet var2 = this.getJwkSet();
      if (var2 == null) {
         class="kw">return false;
      } else if (this.verifySignature(var1, var2)) {
         class="kw">return true;
      } else if (!this.canForceRefreshJwks()) {
         LOGGER.at(Level.FINE).log("Signature verification failed but JWKS was refreshed recently; skipping refresh");
         class="kw">return false;
      } else {
         LOGGER.at(Level.INFO).log("Signature verification failed with cached JWKS, retrying with fresh keys");
         JWKSet var3 = this.getJwkSet(true);
         class="kw">return var3 != null && var3 != var2 ? this.verifySignature(var1, var3) : false;
      }
   }

   class="kw">private boolean canForceRefreshJwks() {
      Instant var1 = this.lastJwksRefresh;
      class="kw">return var1 == null ? true : Duration.between(var1, Instant.now()).compareTo(JWKS_REFRESH_MIN_INTERVAL) >= 0;
   }

   @Nullable
   class="kw">private JWK convertToJWK(SessionServiceClient.JwkKey var1) {
      if (!"OKP".equals(var1.kty)) {
         LOGGER.at(Level.WARNING).log("Unsupported key type: %s (expected OKP)", var1.kty);
         class="kw">return null;
      }

      try {
         String var2 = String.format("{\"kty\":\"OKP\",\"crv\":\"%s\",\"x\":\"%s\",\"kid\":\"%s\",\"alg\":\"EdDSA\"}", var1.crv, var1.x, var1.kid);
         class="kw">return JWK.parse(var2);
      } catch (Exception var3) {
         ((HytaleLogger.Api)LOGGER.at(Level.WARNING).withCause(var3)).log("Failed to parse Ed25519 key");
         class="kw">return null;
      }
   }

   class="kw">private void preSeedJwksCache() {
      JWKSet var1 = this.loadJwksCacheFromDisk();
      if (var1 != null) {
         this.cachedJwkSet = var1;
         LOGGER.at(Level.INFO).log("JWKS pre-seeded from disk cache (%d keys)", var1.getKeys().size());
      } else {
         JWKSet var2 = this.loadEmbeddedJwks();
         if (var2 != null) {
            this.cachedJwkSet = var2;
            LOGGER.at(Level.INFO).log("JWKS pre-seeded from embedded resource (%d keys)", var2.getKeys().size());
         } else {
            LOGGER.at(Level.INFO).log("No pre-seeded JWKS available, will fetch from network on first validation");
         }
      }
   }

   @Nullable
   class="kw">private JWKSet loadEmbeddedJwks() {
      try (InputStream var1 = this.getClass().getResourceAsStream("/jwks_bundled.json")) {
         if (var1 == null) {
            LOGGER.at(Level.FINE).log("No embedded JWKS resource found");
            class="kw">return null;
         } else {
            String var2 = new String(var1.readAllBytes(), StandardCharsets.UTF_8);
            class="kw">return JWKSet.parse(var2);
         }
      } catch (IOException | ParseException var6) {
         ((HytaleLogger.Api)LOGGER.at(Level.WARNING).withCause(var6)).log("Failed to load embedded JWKS");
         class="kw">return null;
      }
   }

   @Nullable
   class="kw">private JWKSet loadJwksCacheFromDisk() {
      Path var1 = Path.of(".jwks.json");
      if (!Files.exists(var1)) {
         class="kw">return null;
      }

      try {
         String var2 = Files.readString(var1, StandardCharsets.UTF_8);
         JWKSet var3 = JWKSet.parse(var2);
         LOGGER.at(Level.FINE).log("Loaded JWKS cache from disk: %s", var1);
         class="kw">return var3;
      } catch (IOException | ParseException var4) {
         ((HytaleLogger.Api)LOGGER.at(Level.WARNING).withCause(var4)).log("Failed to load JWKS cache from disk");
         class="kw">return null;
      }
   }

   class="kw">private void saveJwksCacheToDisk(@Nonnull JWKSet var1) {
      try {
         Files.writeString(Path.of(".jwks.json"), var1.toString(), StandardCharsets.UTF_8);
         LOGGER.at(Level.FINE).log("Saved JWKS cache to disk");
      } catch (IOException var3) {
         ((HytaleLogger.Api)LOGGER.at(Level.WARNING).withCause(var3)).log("Failed to save JWKS cache to disk");
      }
   }

   @Nullable
   class="kw">public JWTValidator.IdentityTokenClaims validateOfflineToken(@Nonnull String var1) {
      JWTValidator.IdentityTokenClaims var2 = this.validateIdentityToken(var1);
      if (var2 == null) {
         class="kw">return null;
      } else if (!var2.hasScope("hytale:offline")) {
         LOGGER.at(Level.WARNING).log("Offline token missing required scope: expected %s, got %s", "hytale:offline", var2.scope);
         class="kw">return null;
      } else {
         class="kw">return var2;
      }
   }

   class="kw">public void invalidateJwksCache() {
      this.jwksFetchLock.lock();

      try {
         this.cachedJwkSet = null;
         this.pendingFetch = null;
      } class="kw">finally {
         this.jwksFetchLock.unlock();
      }
   }

   @Nullable
   class="kw">public JWTValidator.IdentityTokenClaims validateIdentityToken(@Nonnull String var1) {
      String var2 = validateJwtStructure(var1, "Identity token");
      if (var2 != null) {
         LOGGER.at(Level.WARNING).log("%s", var2);
         class="kw">return null;
      }

      try {
         SignedJWT var3 = SignedJWT.parse(var1);
         JWSAlgorithm var4 = var3.getHeader().getAlgorithm();
         if (!SUPPORTED_ALGORITHM.equals(var4)) {
            LOGGER.at(Level.WARNING).log("Unsupported identity token algorithm: %s (expected EdDSA)", var4);
            class="kw">return null;
         }

         if (!this.verifySignatureWithRetry(var3)) {
            LOGGER.at(Level.WARNING).log("Identity token signature verification failed");
            class="kw">return null;
         }

         JWTClaimsSet var5 = var3.getJWTClaimsSet();
         JWTValidator.IdentityTokenClaims var6 = new JWTValidator.IdentityTokenClaims();
         var6.issuer = var5.getIssuer();
         var6.subject = var5.getSubject();
         var6.issuedAt = var5.getIssueTime() != null ? var5.getIssueTime().toInstant().getEpochSecond() : null;
         var6.expiresAt = var5.getExpirationTime() != null ? var5.getExpirationTime().toInstant().getEpochSecond() : null;
         var6.notBefore = var5.getNotBeforeTime() != null ? var5.getNotBeforeTime().toInstant().getEpochSecond() : null;
         var6.scope = var5.getStringClaim("scope");
         Map var7 = var5.getJSONObjectClaim("profile");
         if (var7 != null) {
            var6.username = (String)var7.get("username");
            var6.skin = (String)var7.get("skin");
            if (var7.get("entitlements") class="kw">instanceof List var9) {
               var6.entitlements = var9.stream().filter(String.class::isInstance).map(String.class::cast).toArray(String[]::new);
            }
         }

         if (!this.expectedIssuer.equals(var6.issuer)) {
            LOGGER.at(Level.WARNING)
               .log("Invalid identity token issuer: expected %s, got %s. Token: %s", this.expectedIssuer, var6.issuer, var6.toLogSummary());
            class="kw">return null;
         } else {
            long var12 = Instant.now().getEpochSecond();
            if (var6.expiresAt == null) {
               LOGGER.at(Level.WARNING).log("Identity token missing expiration claim. Token: %s", var6.toLogSummary());
               class="kw">return null;
            } else if (var12 >= var6.expiresAt + 300L) {
               LOGGER.at(Level.WARNING).log("Identity token expired (exp: %d, now: %d). Token: %s", var6.expiresAt, var12, var6.toLogSummary());
               class="kw">return null;
            } else if (var6.notBefore != null && var12 < var6.notBefore - 300L) {
               LOGGER.at(Level.WARNING).log("Identity token not yet valid (nbf: %d, now: %d). Token: %s", var6.notBefore, var12, var6.toLogSummary());
               class="kw">return null;
            } else if (var6.issuedAt != null && var6.issuedAt > var12 + 300L) {
               LOGGER.at(Level.WARNING).log("Identity token issued in the future (iat: %d, now: %d). Token: %s", var6.issuedAt, var12, var6.toLogSummary());
               class="kw">return null;
            } else if (var6.getSubjectAsUUID() == null) {
               LOGGER.at(Level.WARNING).log("Identity token has invalid or missing subject UUID. Token: %s", var6.toLogSummary());
               class="kw">return null;
            } else {
               LOGGER.at(Level.INFO).log("Identity token validated successfully for user %s. Token: %s", var6.username, var6.toLogSummary());
               class="kw">return var6;
            }
         }
      } catch (ParseException var10) {
         ((HytaleLogger.Api)LOGGER.at(Level.WARNING).withCause(var10)).log("Failed to parse identity token");
         class="kw">return null;
      } catch (Exception var11) {
         ((HytaleLogger.Api)LOGGER.at(Level.WARNING).withCause(var11)).log("Identity token validation error");
         class="kw">return null;
      }
   }

   @Nullable
   class="kw">public JWTValidator.SessionTokenClaims validateSessionToken(@Nonnull String var1) {
      String var2 = validateJwtStructure(var1, "Session token");
      if (var2 != null) {
         LOGGER.at(Level.WARNING).log("%s", var2);
         class="kw">return null;
      }

      try {
         SignedJWT var3 = SignedJWT.parse(var1);
         JWSAlgorithm var4 = var3.getHeader().getAlgorithm();
         if (!SUPPORTED_ALGORITHM.equals(var4)) {
            LOGGER.at(Level.WARNING).log("Unsupported session token algorithm: %s (expected EdDSA)", var4);
            class="kw">return null;
         } else if (!this.verifySignatureWithRetry(var3)) {
            LOGGER.at(Level.WARNING).log("Session token signature verification failed");
            class="kw">return null;
         } else {
            JWTClaimsSet var5 = var3.getJWTClaimsSet();
            JWTValidator.SessionTokenClaims var6 = new JWTValidator.SessionTokenClaims();
            var6.issuer = var5.getIssuer();
            var6.subject = var5.getSubject();
            var6.issuedAt = var5.getIssueTime() != null ? var5.getIssueTime().toInstant().getEpochSecond() : null;
            var6.expiresAt = var5.getExpirationTime() != null ? var5.getExpirationTime().toInstant().getEpochSecond() : null;
            var6.notBefore = var5.getNotBeforeTime() != null ? var5.getNotBeforeTime().toInstant().getEpochSecond() : null;
            var6.scope = var5.getStringClaim("scope");
            if (!this.expectedIssuer.equals(var6.issuer)) {
               LOGGER.at(Level.WARNING)
                  .log("Invalid session token issuer: expected %s, got %s. Token: %s", this.expectedIssuer, var6.issuer, var6.toLogSummary());
               class="kw">return null;
            } else {
               long var7 = Instant.now().getEpochSecond();
               if (var6.expiresAt == null) {
                  LOGGER.at(Level.WARNING).log("Session token missing expiration claim. Token: %s", var6.toLogSummary());
                  class="kw">return null;
               } else if (var7 >= var6.expiresAt + 300L) {
                  LOGGER.at(Level.WARNING).log("Session token expired (exp: %d, now: %d). Token: %s", var6.expiresAt, var7, var6.toLogSummary());
                  class="kw">return null;
               } else if (var6.notBefore != null && var7 < var6.notBefore - 300L) {
                  LOGGER.at(Level.WARNING).log("Session token not yet valid (nbf: %d, now: %d). Token: %s", var6.notBefore, var7, var6.toLogSummary());
                  class="kw">return null;
               } else if (var6.issuedAt != null && var6.issuedAt > var7 + 300L) {
                  LOGGER.at(Level.WARNING).log("Session token issued in the future (iat: %d, now: %d). Token: %s", var6.issuedAt, var7, var6.toLogSummary());
                  class="kw">return null;
               } else if (var6.getSubjectAsUUID() == null) {
                  LOGGER.at(Level.WARNING).log("Session token has invalid or missing subject UUID. Token: %s", var6.toLogSummary());
                  class="kw">return null;
               } else {
                  LOGGER.at(Level.INFO).log("Session token validated successfully. Token: %s", var6.toLogSummary());
                  class="kw">return var6;
               }
            }
         }
      } catch (ParseException var9) {
         ((HytaleLogger.Api)LOGGER.at(Level.WARNING).withCause(var9)).log("Failed to parse session token");
         class="kw">return null;
      } catch (Exception var10) {
         ((HytaleLogger.Api)LOGGER.at(Level.WARNING).withCause(var10)).log("Session token validation error");
         class="kw">return null;
      }
   }

   class="kw">public class="kw">static class IdentityTokenClaims {
      class="kw">public String issuer;
      class="kw">public String subject;
      class="kw">public String username;
      class="kw">public String[] entitlements;
      class="kw">public String skin;
      class="kw">public Long issuedAt;
      class="kw">public Long expiresAt;
      class="kw">public Long notBefore;
      class="kw">public String scope;

      class="kw">public IdentityTokenClaims() {
      }

      @Nullable
      class="kw">public UUID getSubjectAsUUID() {
         if (this.subject == null) {
            class="kw">return null;
         }

         try {
            class="kw">return UUID.fromString(this.subject);
         } catch (IllegalArgumentException var2) {
            class="kw">return null;
         }
      }

      @Nonnull
      class="kw">public String[] getScopes() {
         class="kw">return this.scope != null && !this.scope.isEmpty() ? this.scope.split(" ", 0) : new String[0];
      }

      class="kw">public boolean hasScope(@Nonnull String var1) {
         for (String var5 : this.getScopes()) {
            if (var5.equals(var1)) {
               class="kw">return true;
            }
         }

         class="kw">return false;
      }

      @Nonnull
      class="kw">public String toLogSummary() {
         class="kw">return JWTValidator.TokenClaimsLogFormatter.format(this.issuer, null, this.subject, this.scope, this.issuedAt, this.expiresAt, this.notBefore, null);
      }
   }

   class="kw">public class="kw">static class JWTClaims {
      class="kw">public String issuer;
      class="kw">public String audience;
      class="kw">public String subject;
      class="kw">public String username;
      class="kw">public Long issuedAt;
      class="kw">public Long expiresAt;
      class="kw">public Long notBefore;
      class="kw">public String certificateFingerprint;

      class="kw">public JWTClaims() {
      }

      @Nullable
      class="kw">public UUID getSubjectAsUUID() {
         if (this.subject == null) {
            class="kw">return null;
         }

         try {
            class="kw">return UUID.fromString(this.subject);
         } catch (IllegalArgumentException var2) {
            class="kw">return null;
         }
      }

      @Nonnull
      class="kw">public String toLogSummary() {
         class="kw">return JWTValidator.TokenClaimsLogFormatter.format(
            this.issuer, this.audience, this.subject, null, this.issuedAt, this.expiresAt, this.notBefore, this.certificateFingerprint
         );
      }
   }

   class="kw">public class="kw">static class SessionTokenClaims {
      class="kw">public String issuer;
      class="kw">public String subject;
      class="kw">public Long issuedAt;
      class="kw">public Long expiresAt;
      class="kw">public Long notBefore;
      class="kw">public String scope;

      class="kw">public SessionTokenClaims() {
      }

      @Nullable
      class="kw">public UUID getSubjectAsUUID() {
         if (this.subject == null) {
            class="kw">return null;
         }

         try {
            class="kw">return UUID.fromString(this.subject);
         } catch (IllegalArgumentException var2) {
            class="kw">return null;
         }
      }

      @Nonnull
      class="kw">public String[] getScopes() {
         class="kw">return this.scope != null && !this.scope.isEmpty() ? this.scope.split(" ", 0) : new String[0];
      }

      class="kw">public boolean hasScope(@Nonnull String var1) {
         for (String var5 : this.getScopes()) {
            if (var5.equals(var1)) {
               class="kw">return true;
            }
         }

         class="kw">return false;
      }

      @Nonnull
      class="kw">public String toLogSummary() {
         class="kw">return JWTValidator.TokenClaimsLogFormatter.format(this.issuer, null, this.subject, this.scope, this.issuedAt, this.expiresAt, this.notBefore, null);
      }
   }

   class="kw">static class="kw">final class TokenClaimsLogFormatter {
      class="kw">private TokenClaimsLogFormatter() {
      }

      @Nonnull
      class="kw">static String format(
         @Nullable String var0,
         @Nullable String var1,
         @Nullable String var2,
         @Nullable String var3,
         @Nullable Long var4,
         @Nullable Long var5,
         @Nullable Long var6,
         @Nullable String var7
      ) {
         long var8 = Instant.now().getEpochSecond();
         StringBuilder var10 = new StringBuilder();
         var10.append("iss=").append(var0 == null ? "null" : var0);
         if (var1 != null) {
            var10.append(", aud=").append(var1);
         }

         var10.append(", sub=").append(var2 == null ? "null" : var2);
         if (var3 != null) {
            var10.append(", scope=").append(var3);
         }

         var10.append(", iat=").append(formatInstant(var4));
         var10.append(", exp=").append(formatInstantWithDelta(var5, var8));
         if (var6 != null) {
            var10.append(", nbf=").append(formatInstant(var6));
         }

         if (var4 != null && var5 != null) {
            var10.append(", lifetime=").append(var5 - var4).append("s");
         }

         if (var7 != null) {
            String var11 = var7.length() > 12 ? var7.substring(0, 12) + "..." : var7;
            var10.append(", cnf=").append(var11);
         }

         class="kw">return var10.toString();
      }

      @Nonnull
      class="kw">private class="kw">static String formatInstant(@Nullable Long var0) {
         class="kw">return var0 == null ? "null" : Instant.ofEpochSecond(var0).toString();
      }

      @Nonnull
      class="kw">private class="kw">static String formatInstantWithDelta(@Nullable Long var0, long var1) {
         if (var0 == null) {
            class="kw">return "null";
         }

         long var3 = var0 - var1;
         String var5 = var3 >= 0L ? "+" : "";
         class="kw">return Instant.ofEpochSecond(var0) + " (" + var5 + var3 + "s)";
      }
   }
}