AuthState enum
Пакет: com.hypixel.hytale.server.core.io.handlers.login
Файл: com/hypixel/hytale/server/core/io/handlers/login/HandshakeHandler.java
Поля (1)
| Модификаторы | Тип | Имя |
|---|---|---|
|
REQUESTING_AUTH_GRANT,
AWAITING_AUTH_TOKEN,
PROCESSING_AUTH_TOKEN,
EXCHANGING_SERVER_TOKEN, |
AUTHENTICATED |
Исходный код
Показать/скрыть
class="kw">package com.hypixel.hytale.server.core.io.handlers.login;
class="kw">import com.hypixel.hytale.logger.HytaleLogger;
class="kw">import com.hypixel.hytale.protocol.HostAddress;
class="kw">import com.hypixel.hytale.protocol.PlayerSkin;
class="kw">import com.hypixel.hytale.protocol.ToServerPacket;
class="kw">import com.hypixel.hytale.protocol.io.ChannelConnection;
class="kw">import com.hypixel.hytale.protocol.io.ConnectionHandler;
class="kw">import com.hypixel.hytale.protocol.packets.auth.AuthGrant;
class="kw">import com.hypixel.hytale.protocol.packets.auth.AuthToken;
class="kw">import com.hypixel.hytale.protocol.packets.auth.ServerAuthToken;
class="kw">import com.hypixel.hytale.protocol.packets.connection.ClientDisconnect;
class="kw">import com.hypixel.hytale.protocol.packets.connection.ClientType;
class="kw">import com.hypixel.hytale.server.core.Constants;
class="kw">import com.hypixel.hytale.server.core.HytaleServer;
class="kw">import com.hypixel.hytale.server.core.HytaleServerConfig;
class="kw">import com.hypixel.hytale.server.core.Message;
class="kw">import com.hypixel.hytale.server.core.auth.AuthConfig;
class="kw">import com.hypixel.hytale.server.core.auth.JWTValidator;
class="kw">import com.hypixel.hytale.server.core.auth.PlayerAuthentication;
class="kw">import com.hypixel.hytale.server.core.auth.ServerAuthManager;
class="kw">import com.hypixel.hytale.server.core.auth.SessionServiceClient;
class="kw">import com.hypixel.hytale.server.core.cosmetics.CosmeticsModule;
class="kw">import com.hypixel.hytale.server.core.io.ProtocolVersion;
class="kw">import com.hypixel.hytale.server.core.io.handlers.GenericConnectionPacketHandler;
class="kw">import com.hypixel.hytale.server.core.modules.singleplayer.SingleplayerModule;
class="kw">import java.security.SecureRandom;
class="kw">import java.security.cert.X509Certificate;
class="kw">import java.util.UUID;
class="kw">import java.util.logging.Level;
class="kw">import javax.annotation.Nonnull;
class="kw">import javax.annotation.Nullable;
class="kw">public class="kw">abstract class HandshakeHandler class="kw">extends GenericConnectionPacketHandler {
class="kw">private class="kw">static class="kw">final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass();
class="kw">private class="kw">static class="kw">volatile SessionServiceClient sessionServiceClient;
class="kw">private class="kw">static class="kw">volatile JWTValidator jwtValidator;
class="kw">private class="kw">volatile HandshakeHandler.AuthState authState = HandshakeHandler.AuthState.REQUESTING_AUTH_GRANT;
class="kw">private class="kw">volatile boolean authTokenPacketReceived = false;
class="kw">private class="kw">volatile String authenticatedUsername;
class="kw">private class="kw">volatile PlayerSkin authenticatedSkin;
class="kw">private class="kw">final ClientType clientType;
class="kw">private class="kw">final String identityToken;
class="kw">private class="kw">final byte[] referralData;
class="kw">private class="kw">final HostAddress referralSource;
@Nullable
class="kw">private class="kw">volatile JWTValidator.IdentityTokenClaims identityClaims;
class="kw">public HandshakeHandler(
@Nonnull ChannelConnection var1,
@Nonnull ProtocolVersion var2,
@Nonnull String var3,
@Nonnull ClientType var4,
@Nonnull String var5,
@Nullable byte[] var6,
@Nullable HostAddress var7
) {
super(var1, var2, var3);
this.clientType = var4;
this.identityToken = var5;
this.referralData = var6;
this.referralSource = var7;
}
class="kw">private class="kw">static SessionServiceClient getSessionServiceClient() {
if (sessionServiceClient == null) {
class="kw">synchronized (HandshakeHandler.class) {
if (sessionServiceClient == null) {
sessionServiceClient = new SessionServiceClient("https://sessions.hytale.com");
}
}
}
class="kw">return sessionServiceClient;
}
class="kw">private class="kw">static JWTValidator getJwtValidator() {
if (jwtValidator == null) {
class="kw">synchronized (HandshakeHandler.class) {
if (jwtValidator == null) {
jwtValidator = new JWTValidator(getSessionServiceClient(), "https://sessions.hytale.com", AuthConfig.getServerAudience());
}
}
}
class="kw">return jwtValidator;
}
@Override
class="kw">public void accept(@Nonnull ToServerPacket var1) {
class="kw">switch (var1.getId()) {
case 1:
this.handle((ClientDisconnect)var1);
break;
case 12:
this.handle((AuthToken)var1);
break;
class="kw">default:
this.disconnect(Message.translation("client.general.disconnect.protocol.unexpectedPacket").param("packetId", var1.getId()));
}
}
@Override
class="kw">public void registered0(ConnectionHandler var1) {
HytaleServerConfig.TimeoutProfile var2 = HytaleServer.get().getConfig().getConnectionTimeouts();
this.enterStage("auth", var2.getAuth());
this.identityClaims = getJwtValidator().validateIdentityToken(this.identityToken);
if (this.identityClaims == null) {
LOGGER.at(Level.WARNING).log("Identity token validation failed for %s", this.getChannel().formatRemoteAddress());
this.disconnect(Message.translation("client.general.disconnect.invalidIdentityToken"));
} else if (this.identityClaims.subject == null || this.identityClaims.subject.isEmpty()) {
LOGGER.at(Level.WARNING).log("Identity token UUID missing for %s", this.getChannel().formatRemoteAddress());
this.disconnect(Message.translation("client.general.disconnect.invalidIdentityToken"));
} else if (this.identityClaims.username != null && !this.identityClaims.username.isEmpty()) {
String var3 = this.clientType == ClientType.Editor ? "hytale:editor" : "hytale:client";
if (!this.identityClaims.hasScope(var3)) {
LOGGER.at(Level.WARNING)
.log(
"Identity token missing required scope for %s from %s (clientType: %s, required: %s, actual: %s)",
this.identityClaims.username,
this.getChannel().formatRemoteAddress(),
this.clientType,
var3,
this.identityClaims.scope
);
this.disconnect(Message.translation("client.general.disconnect.identityTokenMissingScope").param("scope", var3));
} else {
LOGGER.at(Level.INFO)
.log(
"Identity token validated for %s (UUID: %s, scope: %s) from %s, requesting auth grant",
this.identityClaims.username,
this.identityClaims.subject,
this.identityClaims.scope,
this.getChannel().formatRemoteAddress()
);
this.continueStage("auth:grant", var2.getAuthGrant(), () -> this.authState != HandshakeHandler.AuthState.REQUESTING_AUTH_GRANT);
this.requestAuthGrant();
}
} else {
LOGGER.at(Level.WARNING).log("Identity token username missing for %s", this.getChannel().formatRemoteAddress());
this.disconnect(Message.translation("client.general.disconnect.invalidIdentityToken"));
}
}
class="kw">private void requestAuthGrant() {
String var1 = ServerAuthManager.getInstance().getSessionToken();
if (var1 != null && !var1.isEmpty()) {
ChannelConnection var2 = this.getChannel();
getSessionServiceClient().requestAuthorizationGrantAsync(this.identityToken, AuthConfig.getServerAudience(), var1).thenAccept(var2x -> {
if (var2.isActive()) {
if (var2x == null) {
var2.execute(() -> this.disconnect(Message.translation("client.general.disconnect.authGrantFailed")));
} else {
String var3 = ServerAuthManager.getInstance().getIdentityToken();
if (var3 != null && !var3.isEmpty()) {
String var4 = var3;
var2.execute(() -> {
if (var2.isActive()) {
if (this.authState != HandshakeHandler.AuthState.REQUESTING_AUTH_GRANT) {
LOGGER.at(Level.WARNING).log("State changed during auth grant request, current state: %s", this.authState);
} else {
this.clearTimeout();
LOGGER.at(Level.INFO).log("Sending AuthGrant to %s (with server identity: %s)", var2.formatRemoteAddress(), !var4.isEmpty());
this.write(new AuthGrant(var2x, var4));
this.authState = HandshakeHandler.AuthState.AWAITING_AUTH_TOKEN;
HytaleServerConfig.TimeoutProfile var4 = HytaleServer.get().getConfig().getConnectionTimeouts();
this.continueStage("auth:token", var4.getAuthToken(), () -> this.authState != HandshakeHandler.AuthState.AWAITING_AUTH_TOKEN);
}
}
});
} else {
LOGGER.at(Level.SEVERE).log("Server identity token not available - cannot complete mutual authentication");
var2.execute(() -> this.disconnect(Message.translation("client.general.disconnect.serverAuthUnavailable")));
}
}
}
}).exceptionally(var2x -> {
((HytaleLogger.Api)LOGGER.at(Level.WARNING).withCause(var2x)).log("Error requesting auth grant");
var2.execute(() -> this.disconnect(Message.translation("client.general.disconnect.authError")));
class="kw">return null;
});
} else {
LOGGER.at(Level.SEVERE).log("Server session token not available - cannot request auth grant");
this.disconnect(Message.translation("client.general.disconnect.serverAuthUnavailable"));
}
}
class="kw">public void handle(@Nonnull ClientDisconnect var1) {
this.disconnectReason.setClientDisconnectType(var1.type);
if (this.identityClaims != null) {
LOGGER.at(Level.INFO)
.log(
"%s (%s) at %s left with reason: %s - %s",
this.identityClaims.subject,
this.identityClaims.username,
this.getChannel().formatRemoteAddress(),
var1.type.name(),
var1.reason.name()
);
}
this.getChannel().closeApplicationConnection();
}
class="kw">public void handle(@Nonnull AuthToken var1) {
ChannelConnection var2 = this.getChannel();
if (this.authState != HandshakeHandler.AuthState.AWAITING_AUTH_TOKEN) {
LOGGER.at(Level.WARNING).log("Received unexpected AuthToken packet in state %s from %s", this.authState, var2.formatRemoteAddress());
this.disconnect(Message.translation("client.general.disconnect.protocol.unexpectedAuthToken"));
} else if (this.authTokenPacketReceived) {
LOGGER.at(Level.WARNING).log("Received duplicate AuthToken packet from %s", var2.formatRemoteAddress());
this.disconnect(Message.translation("client.general.disconnect.protocol.duplicateAuthToken"));
} else {
this.authTokenPacketReceived = true;
this.authState = HandshakeHandler.AuthState.PROCESSING_AUTH_TOKEN;
this.clearTimeout();
String var3 = var1.accessToken;
if (var3 != null && !var3.isEmpty()) {
String var4 = var1.serverAuthorizationGrant;
X509Certificate var5 = var2.getClientCertificate();
LOGGER.at(Level.INFO)
.log(
"Received AuthToken from %s, validating JWT (mTLS cert present: %s, server auth grant: %s)",
var2.formatRemoteAddress(),
var5 != null,
var4 != null && !var4.isEmpty()
);
JWTValidator.JWTClaims var6 = getJwtValidator().validateToken(var3, var5);
if (var6 == null) {
LOGGER.at(Level.WARNING).log("JWT validation failed for %s", var2.formatRemoteAddress());
this.disconnect(Message.translation("client.general.disconnect.invalidAccessToken"));
} else if (this.identityClaims == null) {
LOGGER.at(Level.WARNING).log("Identity token is null prior to validating authentication token for %s", this.getChannel().formatRemoteAddress());
this.disconnect(Message.translation("client.general.disconnect.invalidIdentityToken"));
} else {
String var7 = var6.subject;
String var8 = var6.username;
if (var7 == null || !var7.equals(this.identityClaims.subject)) {
LOGGER.at(Level.WARNING)
.log("JWT UUID mismatch for %s (expected: %s, got: %s)", this.getChannel().formatRemoteAddress(), this.identityClaims.subject, var7);
this.disconnect(Message.translation("client.general.disconnect.tokenUuidMismatch"));
} else if (var8 == null || var8.isEmpty()) {
LOGGER.at(Level.WARNING).log("JWT missing username for %s", var2.formatRemoteAddress());
this.disconnect(Message.translation("client.general.disconnect.tokenMissingUsername"));
} else if (!var8.equals(this.identityClaims.username)) {
LOGGER.at(Level.WARNING)
.log("JWT username mismatch for %s (expected: %s, got: %s)", this.getChannel().formatRemoteAddress(), this.identityClaims.username, var8);
this.disconnect(Message.translation("client.general.disconnect.tokenUsernameMismatch"));
} else {
this.authenticatedUsername = var8;
if (this.identityClaims.skin != null && !this.identityClaims.skin.isEmpty()) {
try {
PlayerSkin var9 = CosmeticsModule.get().parseSkinFromJson(this.identityClaims.skin);
if (var9 == null) {
this.disconnect(Message.translation("client.general.disconnect.invalidSkin").param("details", "failed to parse skin data"));
class="kw">return;
}
CosmeticsModule.get().validateSkin(var9);
this.authenticatedSkin = var9;
} catch (CosmeticsModule.InvalidSkinException var10) {
this.disconnect(Message.translation("client.general.disconnect.invalidSkin").param("details", var10.getMessage()));
class="kw">return;
}
}
if (var4 != null && !var4.isEmpty()) {
this.authState = HandshakeHandler.AuthState.EXCHANGING_SERVER_TOKEN;
HytaleServerConfig.TimeoutProfile var11 = HytaleServer.get().getConfig().getConnectionTimeouts();
this.continueStage(
"auth:server-exchange", var11.getAuthServerExchange(), () -> this.authState != HandshakeHandler.AuthState.EXCHANGING_SERVER_TOKEN
);
this.exchangeServerAuthGrant(var4);
} else {
LOGGER.at(Level.WARNING).log("Client did not provide server auth grant for mutual authentication");
this.disconnect(Message.translation("client.general.disconnect.mutualAuthRequired"));
}
}
}
} else {
LOGGER.at(Level.WARNING).log("Received AuthToken packet with empty access token from %s", var2.formatRemoteAddress());
this.disconnect(Message.translation("client.general.disconnect.invalidAccessToken"));
}
}
}
class="kw">private void exchangeServerAuthGrant(@Nonnull String var1) {
ServerAuthManager var2 = ServerAuthManager.getInstance();
String var3 = var2.getServerCertificateFingerprint();
if (var3 == null) {
LOGGER.at(Level.SEVERE).log("Server certificate fingerprint not available for mutual auth");
this.disconnect(Message.translation("client.general.disconnect.serverAuthUnavailable"));
} else {
String var4 = var2.getSessionToken();
LOGGER.at(Level.FINE).log("Server session token available: %s, identity token available: %s", var4 != null, var2.getIdentityToken() != null);
if (var4 == null) {
LOGGER.at(Level.SEVERE).log("Server session token not available for auth grant exchange");
LOGGER.at(Level.FINE)
.log("Auth mode: %s, has session token: %s, has identity token: %s", var2.getAuthStatus(), var2.hasSessionToken(), var2.hasIdentityToken());
this.disconnect(Message.translation("client.general.disconnect.serverAuthUnavailable"));
} else {
LOGGER.at(Level.FINE).log("Using session token (first 20 chars): %s...", var4.length() > 20 ? var4.substring(0, 20) : var4);
ChannelConnection var5 = this.getChannel();
getSessionServiceClient().exchangeAuthGrantForTokenAsync(var1, var3, var4).thenAccept(var2x -> {
if (var5.isActive()) {
var5.execute(() -> {
if (var5.isActive()) {
if (this.authState != HandshakeHandler.AuthState.EXCHANGING_SERVER_TOKEN) {
LOGGER.at(Level.WARNING).log("State changed during server token exchange, current state: %s", this.authState);
} else if (var2x == null) {
LOGGER.at(Level.SEVERE).log("Failed to exchange server auth grant for access token");
this.disconnect(Message.translation("client.general.disconnect.serverAuthFailed"));
} else {
byte[] var3 = this.generatePasswordChallengeIfNeeded();
LOGGER.at(Level.INFO).log("Sending ServerAuthToken to %s (with password challenge: %s)", var5.formatRemoteAddress(), var3 != null);
this.write(new ServerAuthToken(var2x, var3));
this.completeAuthentication(var3);
}
}
});
}
}).exceptionally(var2x -> {
((HytaleLogger.Api)LOGGER.at(Level.WARNING).withCause(var2x)).log("Error exchanging server auth grant");
var5.execute(() -> {
if (this.authState == HandshakeHandler.AuthState.EXCHANGING_SERVER_TOKEN) {
this.disconnect(Message.translation("client.general.disconnect.serverAuthFailed"));
}
});
class="kw">return null;
});
}
}
}
class="kw">private byte[] generatePasswordChallengeIfNeeded() {
String var1 = HytaleServer.get().getConfig().getPassword();
if (var1 != null && !var1.isEmpty()) {
if (Constants.SINGLEPLAYER) {
UUID var2 = SingleplayerModule.getUuid();
if (var2 != null && var2.equals(this.identityClaims.getSubjectAsUUID())) {
class="kw">return null;
}
}
byte[] var3 = new byte[32];
new SecureRandom().nextBytes(var3);
class="kw">return var3;
} else {
class="kw">return null;
}
}
class="kw">private void completeAuthentication(byte[] var1) {
UUID var2 = this.identityClaims.getSubjectAsUUID();
if (var2 == null) {
LOGGER.at(Level.SEVERE).log("Identity token subject is not a valid UUID: %s", this.identityClaims.subject);
this.disconnect(Message.translation("client.general.disconnect.invalidIdentityToken"));
} else {
this.auth = new PlayerAuthentication(var2, this.authenticatedUsername);
if (this.referralData != null) {
this.auth.setReferralData(this.referralData);
}
if (this.referralSource != null) {
this.auth.setReferralSource(this.referralSource);
}
if (this.authenticatedSkin != null) {
this.auth.setSkin(this.authenticatedSkin);
}
this.authState = HandshakeHandler.AuthState.AUTHENTICATED;
this.clearTimeout();
LOGGER.at(Level.INFO)
.log(
"Mutual authentication complete for %s (%s) from %s",
this.authenticatedUsername,
this.identityClaims.subject,
this.getChannel().formatRemoteAddress()
);
this.onAuthenticated(var1);
}
}
class="kw">protected class="kw">abstract void onAuthenticated(byte[] var1);
class="kw">private enum AuthState {
REQUESTING_AUTH_GRANT,
AWAITING_AUTH_TOKEN,
PROCESSING_AUTH_TOKEN,
EXCHANGING_SERVER_TOKEN,
AUTHENTICATED;
AuthState() {
}
}
}