feat: 魔改网易登录接口
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
This commit is contained in:
@@ -5,7 +5,7 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
java {
|
java {
|
||||||
withJavadocJar()
|
// withJavadocJar()
|
||||||
withSourcesJar()
|
withSourcesJar()
|
||||||
|
|
||||||
sourceSets["main"].java {
|
sourceSets["main"].java {
|
||||||
|
@@ -148,4 +148,8 @@ public interface ProxyConfig {
|
|||||||
* @return read timeout (in milliseconds)
|
* @return read timeout (in milliseconds)
|
||||||
*/
|
*/
|
||||||
int getReadTimeout();
|
int getReadTimeout();
|
||||||
|
|
||||||
|
String getNeteaseAuthUrl();
|
||||||
|
|
||||||
|
String getNeteaseGameId();
|
||||||
}
|
}
|
||||||
|
@@ -19,7 +19,7 @@ public final class GameProfile {
|
|||||||
|
|
||||||
private final UUID id;
|
private final UUID id;
|
||||||
private final String undashedId;
|
private final String undashedId;
|
||||||
private final String name;
|
private String name;
|
||||||
private final List<Property> properties;
|
private final List<Property> properties;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -80,6 +80,10 @@ public final class GameProfile {
|
|||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an immutable list of profile properties associated with this profile.
|
* Returns an immutable list of profile properties associated with this profile.
|
||||||
*
|
*
|
||||||
@@ -221,4 +225,116 @@ public final class GameProfile {
|
|||||||
+ '}';
|
+ '}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* netease auth response.
|
||||||
|
*/
|
||||||
|
public static class Response {
|
||||||
|
private Integer code = 0;
|
||||||
|
private String message;
|
||||||
|
private String details;
|
||||||
|
private ResponseEntity entity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* default constructor.
|
||||||
|
*
|
||||||
|
* @param code -
|
||||||
|
* @param message -
|
||||||
|
* @param details -
|
||||||
|
* @param entity the game profile
|
||||||
|
*/
|
||||||
|
public Response(Integer code, String message, String details, ResponseEntity entity) {
|
||||||
|
this.code = code;
|
||||||
|
this.message = message;
|
||||||
|
this.details = details;
|
||||||
|
this.entity = entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getCode() {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCode(Integer code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMessage() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMessage(String message) {
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDetails() {
|
||||||
|
return details;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDetails(String details) {
|
||||||
|
this.details = details;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResponseEntity getEntity() {
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEntity(ResponseEntity entity) {
|
||||||
|
this.entity = entity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* netease auth response entity.
|
||||||
|
*/
|
||||||
|
public static class ResponseEntity {
|
||||||
|
private String id;
|
||||||
|
private String name;
|
||||||
|
private List<Property> properties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* default constructor.
|
||||||
|
*
|
||||||
|
* @param id -
|
||||||
|
* @param name -
|
||||||
|
* @param properties -
|
||||||
|
*/
|
||||||
|
public ResponseEntity(String id, String name, List<Property> properties) {
|
||||||
|
this.id = id;
|
||||||
|
this.name = name;
|
||||||
|
this.properties = properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(String id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Property> getProperties() {
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProperties(List<Property> properties) {
|
||||||
|
this.properties = properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "ResponseEntity{"
|
||||||
|
+ "id='" + id + '\''
|
||||||
|
+ ", name='" + name + '\''
|
||||||
|
+ ", properties=" + properties
|
||||||
|
+ '}';
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -92,6 +92,11 @@ public class VelocityConfiguration implements ProxyConfig {
|
|||||||
@Expose
|
@Expose
|
||||||
private boolean forceKeyAuthentication = true; // Added in 1.19
|
private boolean forceKeyAuthentication = true; // Added in 1.19
|
||||||
|
|
||||||
|
@Expose
|
||||||
|
private String authUrl = "http://192.168.46.50:9999/check";
|
||||||
|
@Expose
|
||||||
|
private String gameId = "77140593557373952";
|
||||||
|
|
||||||
private VelocityConfiguration(Servers servers, ForcedHosts forcedHosts, Advanced advanced,
|
private VelocityConfiguration(Servers servers, ForcedHosts forcedHosts, Advanced advanced,
|
||||||
Query query, Metrics metrics) {
|
Query query, Metrics metrics) {
|
||||||
this.servers = servers;
|
this.servers = servers;
|
||||||
@@ -106,7 +111,8 @@ public class VelocityConfiguration implements ProxyConfig {
|
|||||||
PlayerInfoForwarding playerInfoForwardingMode, byte[] forwardingSecret,
|
PlayerInfoForwarding playerInfoForwardingMode, byte[] forwardingSecret,
|
||||||
boolean onlineModeKickExistingPlayers, PingPassthroughMode pingPassthrough,
|
boolean onlineModeKickExistingPlayers, PingPassthroughMode pingPassthrough,
|
||||||
boolean enablePlayerAddressLogging, Servers servers, ForcedHosts forcedHosts,
|
boolean enablePlayerAddressLogging, Servers servers, ForcedHosts forcedHosts,
|
||||||
Advanced advanced, Query query, Metrics metrics, boolean forceKeyAuthentication) {
|
Advanced advanced, Query query, Metrics metrics, boolean forceKeyAuthentication,
|
||||||
|
String authUrl, String gameId) {
|
||||||
this.bind = bind;
|
this.bind = bind;
|
||||||
this.motd = motd;
|
this.motd = motd;
|
||||||
this.showMaxPlayers = showMaxPlayers;
|
this.showMaxPlayers = showMaxPlayers;
|
||||||
@@ -124,6 +130,8 @@ public class VelocityConfiguration implements ProxyConfig {
|
|||||||
this.query = query;
|
this.query = query;
|
||||||
this.metrics = metrics;
|
this.metrics = metrics;
|
||||||
this.forceKeyAuthentication = forceKeyAuthentication;
|
this.forceKeyAuthentication = forceKeyAuthentication;
|
||||||
|
this.authUrl = authUrl;
|
||||||
|
this.gameId = gameId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -407,6 +415,16 @@ public class VelocityConfiguration implements ProxyConfig {
|
|||||||
return forceKeyAuthentication;
|
return forceKeyAuthentication;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getNeteaseAuthUrl() {
|
||||||
|
return authUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getNeteaseGameId() {
|
||||||
|
return gameId;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return MoreObjects.toStringHelper(this)
|
return MoreObjects.toStringHelper(this)
|
||||||
@@ -424,6 +442,8 @@ public class VelocityConfiguration implements ProxyConfig {
|
|||||||
.add("favicon", favicon)
|
.add("favicon", favicon)
|
||||||
.add("enablePlayerAddressLogging", enablePlayerAddressLogging)
|
.add("enablePlayerAddressLogging", enablePlayerAddressLogging)
|
||||||
.add("forceKeyAuthentication", forceKeyAuthentication)
|
.add("forceKeyAuthentication", forceKeyAuthentication)
|
||||||
|
.add("authUrl", authUrl)
|
||||||
|
.add("gameId", gameId)
|
||||||
.toString();
|
.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -521,6 +541,8 @@ public class VelocityConfiguration implements ProxyConfig {
|
|||||||
|| forwardingMode == PlayerInfoForwarding.BUNGEEGUARD)) {
|
|| forwardingMode == PlayerInfoForwarding.BUNGEEGUARD)) {
|
||||||
throw new RuntimeException("The forwarding-secret file must not be empty.");
|
throw new RuntimeException("The forwarding-secret file must not be empty.");
|
||||||
}
|
}
|
||||||
|
final String authUrl = config.getOrElse("auth-url", "http://192.168.46.50:9999/check");
|
||||||
|
final String gameId = config.getOrElse("game-id", "77140593557373952");
|
||||||
|
|
||||||
return new VelocityConfiguration(
|
return new VelocityConfiguration(
|
||||||
bind,
|
bind,
|
||||||
@@ -539,7 +561,9 @@ public class VelocityConfiguration implements ProxyConfig {
|
|||||||
new Advanced(advancedConfig),
|
new Advanced(advancedConfig),
|
||||||
new Query(queryConfig),
|
new Query(queryConfig),
|
||||||
new Metrics(metricsConfig),
|
new Metrics(metricsConfig),
|
||||||
forceKeyAuthentication
|
forceKeyAuthentication,
|
||||||
|
authUrl,
|
||||||
|
gameId
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -17,7 +17,6 @@
|
|||||||
|
|
||||||
package com.velocitypowered.proxy.connection.client;
|
package com.velocitypowered.proxy.connection.client;
|
||||||
|
|
||||||
import static com.google.common.net.UrlEscapers.urlFormParameterEscaper;
|
|
||||||
import static com.velocitypowered.proxy.VelocityServer.GENERAL_GSON;
|
import static com.velocitypowered.proxy.VelocityServer.GENERAL_GSON;
|
||||||
import static com.velocitypowered.proxy.connection.VelocityConstants.EMPTY_BYTE_ARRAY;
|
import static com.velocitypowered.proxy.connection.VelocityConstants.EMPTY_BYTE_ARRAY;
|
||||||
import static com.velocitypowered.proxy.crypto.EncryptionUtils.decryptRsa;
|
import static com.velocitypowered.proxy.crypto.EncryptionUtils.decryptRsa;
|
||||||
@@ -25,6 +24,7 @@ import static com.velocitypowered.proxy.crypto.EncryptionUtils.generateServerId;
|
|||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import com.google.common.primitives.Longs;
|
import com.google.common.primitives.Longs;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
import com.velocitypowered.api.event.connection.PreLoginEvent;
|
import com.velocitypowered.api.event.connection.PreLoginEvent;
|
||||||
import com.velocitypowered.api.event.connection.PreLoginEvent.PreLoginComponentResult;
|
import com.velocitypowered.api.event.connection.PreLoginEvent.PreLoginComponentResult;
|
||||||
import com.velocitypowered.api.network.ProtocolVersion;
|
import com.velocitypowered.api.network.ProtocolVersion;
|
||||||
@@ -50,6 +50,7 @@ import java.net.http.HttpResponse;
|
|||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.ThreadLocalRandom;
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
@@ -198,22 +199,23 @@ public class InitialLoginSessionHandler implements MinecraftSessionHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] decryptedSharedSecret = decryptRsa(serverKeyPair, packet.getSharedSecret());
|
final byte[] decryptedSharedSecret = decryptRsa(serverKeyPair, packet.getSharedSecret());
|
||||||
String serverId = generateServerId(decryptedSharedSecret, serverKeyPair.getPublic());
|
final String serverId = generateServerId(decryptedSharedSecret, serverKeyPair.getPublic());
|
||||||
|
final String playerIp = ((InetSocketAddress) mcConnection.getRemoteAddress()).getHostString();
|
||||||
|
|
||||||
String playerIp = ((InetSocketAddress) mcConnection.getRemoteAddress()).getHostString();
|
JsonObject data = new JsonObject();
|
||||||
String url = String.format(MOJANG_HASJOINED_URL,
|
data.addProperty("username", login.getUsername());
|
||||||
urlFormParameterEscaper().escape(login.getUsername()), serverId);
|
data.addProperty("serverId", serverId);
|
||||||
|
data.addProperty("gameID", server.getConfiguration().getNeteaseGameId());
|
||||||
|
|
||||||
if (server.getConfiguration().shouldPreventClientProxyConnections()) {
|
HttpRequest httpRequest = HttpRequest.newBuilder()
|
||||||
url += "&ip=" + urlFormParameterEscaper().escape(playerIp);
|
.headers(
|
||||||
}
|
"User-Agent", server.getVersion().getName() + "/" + server.getVersion().getVersion(),
|
||||||
|
"Content-Type", "application/json"
|
||||||
final HttpRequest httpRequest = HttpRequest.newBuilder()
|
).POST(HttpRequest.BodyPublishers.ofString(data.toString()))
|
||||||
.setHeader("User-Agent",
|
.uri(URI.create(server.getConfiguration().getNeteaseAuthUrl()))
|
||||||
server.getVersion().getName() + "/" + server.getVersion().getVersion())
|
|
||||||
.uri(URI.create(url))
|
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
final HttpClient httpClient = server.createHttpClient();
|
final HttpClient httpClient = server.createHttpClient();
|
||||||
httpClient.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofString())
|
httpClient.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofString())
|
||||||
.whenCompleteAsync((response, throwable) -> {
|
.whenCompleteAsync((response, throwable) -> {
|
||||||
@@ -240,31 +242,53 @@ public class InitialLoginSessionHandler implements MinecraftSessionHandler {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.statusCode() == 200) {
|
try {
|
||||||
final GameProfile profile = GENERAL_GSON.fromJson(response.body(),
|
if (response.statusCode() == 200) {
|
||||||
GameProfile.class);
|
final GameProfile.Response authResponse = GENERAL_GSON.fromJson(response.body(),
|
||||||
// Not so fast, now we verify the public key for 1.19.1+
|
GameProfile.Response.class);
|
||||||
if (inbound.getIdentifiedKey() != null
|
if (authResponse.getCode() != 0) {
|
||||||
&& inbound.getIdentifiedKey().getKeyRevision() == IdentifiedKey.Revision.LINKED_V2
|
inbound.disconnect(Component.translatable("multiplayer.disconnect.authservers_down"));
|
||||||
&& inbound.getIdentifiedKey() instanceof final IdentifiedKeyImpl key) {
|
logger.error("Error authenticating {} with netease", login.getUsername());
|
||||||
if (!key.internalAddHolder(profile.getId())) {
|
} else {
|
||||||
inbound.disconnect(
|
GameProfile.ResponseEntity entity = authResponse.getEntity();
|
||||||
Component.translatable("multiplayer.disconnect.invalid_public_key"));
|
if (entity.getName() == null || entity.getName().isEmpty()) {
|
||||||
|
entity.setName(login.getUsername());
|
||||||
|
}
|
||||||
|
if (entity.getProperties() == null) {
|
||||||
|
entity.setProperties(new ArrayList<>());
|
||||||
|
}
|
||||||
|
if (entity.getId() == null) {
|
||||||
|
inbound.disconnect(
|
||||||
|
Component.translatable("velocity.error.online-mode-only", NamedTextColor.RED));
|
||||||
|
} else {
|
||||||
|
GameProfile profile = new GameProfile(entity.getId(), entity.getName(), entity.getProperties());
|
||||||
|
// Not so fast, now we verify the public key for 1.19.1+
|
||||||
|
if (inbound.getIdentifiedKey() != null
|
||||||
|
&& inbound.getIdentifiedKey().getKeyRevision() == IdentifiedKey.Revision.LINKED_V2
|
||||||
|
&& inbound.getIdentifiedKey() instanceof final IdentifiedKeyImpl key) {
|
||||||
|
if (!key.internalAddHolder(profile.getId())) {
|
||||||
|
inbound.disconnect(
|
||||||
|
Component.translatable("multiplayer.disconnect.invalid_public_key"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// All went well, initialize the session.
|
||||||
|
mcConnection.setActiveSessionHandler(StateRegistry.LOGIN,
|
||||||
|
new AuthSessionHandler(server, inbound, profile, true));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} else if (response.statusCode() == 204) {
|
||||||
|
// Apparently an offline-mode user logged onto this online-mode proxy.
|
||||||
|
inbound.disconnect(
|
||||||
|
Component.translatable("velocity.error.online-mode-only", NamedTextColor.RED));
|
||||||
|
} else {
|
||||||
|
// Something else went wrong
|
||||||
|
logger.error(
|
||||||
|
"Got an unexpected error code {} whilst contacting Mojang to log in {} ({})",
|
||||||
|
response.statusCode(), login.getUsername(), playerIp);
|
||||||
|
inbound.disconnect(Component.translatable("multiplayer.disconnect.authservers_down"));
|
||||||
}
|
}
|
||||||
// All went well, initialize the session.
|
} catch (Exception e) {
|
||||||
mcConnection.setActiveSessionHandler(StateRegistry.LOGIN,
|
logger.error("Got an unexpected error", e);
|
||||||
new AuthSessionHandler(server, inbound, profile, true));
|
|
||||||
} else if (response.statusCode() == 204) {
|
|
||||||
// Apparently an offline-mode user logged onto this online-mode proxy.
|
|
||||||
inbound.disconnect(
|
|
||||||
Component.translatable("velocity.error.online-mode-only", NamedTextColor.RED));
|
|
||||||
} else {
|
|
||||||
// Something else went wrong
|
|
||||||
logger.error(
|
|
||||||
"Got an unexpected error code {} whilst contacting Mojang to log in {} ({})",
|
|
||||||
response.statusCode(), login.getUsername(), playerIp);
|
|
||||||
inbound.disconnect(Component.translatable("multiplayer.disconnect.authservers_down"));
|
|
||||||
}
|
}
|
||||||
}, mcConnection.eventLoop())
|
}, mcConnection.eventLoop())
|
||||||
.thenRun(() -> {
|
.thenRun(() -> {
|
||||||
|
@@ -69,6 +69,20 @@ ping-passthrough = "DISABLED"
|
|||||||
# If not enabled (default is true) player IP addresses will be replaced by <ip address withheld> in logs
|
# If not enabled (default is true) player IP addresses will be replaced by <ip address withheld> in logs
|
||||||
enable-player-address-logging = true
|
enable-player-address-logging = true
|
||||||
|
|
||||||
|
# 认证链接
|
||||||
|
# 正式环境:http://x19authserver.nie.netease.com/check
|
||||||
|
# 测试环境:http://x19authexpr.nie.netease.com/check
|
||||||
|
# 1.20版本请使用以下接口:
|
||||||
|
# 正式环境:https://x19apigatewayobt.nie.netease.com/pcauth/check
|
||||||
|
# 测试环境:https://x19apigatewayexpr.nie.netease.com/pcauth/check
|
||||||
|
# 另有外网测试认证接口,对应接入test版bc认证,通常情况下不使用,需要启用时会另行沟通。
|
||||||
|
# http://x19authtest.nie.netease.com/check
|
||||||
|
auth-url = "http://192.168.46.50:9999/check"
|
||||||
|
|
||||||
|
# 网络服游戏 id
|
||||||
|
# 在开发者平台中可以查看
|
||||||
|
game-id = "77140593557373952"
|
||||||
|
|
||||||
[servers]
|
[servers]
|
||||||
# Configure your servers here. Each key represents the server's name, and the value
|
# Configure your servers here. Each key represents the server's name, and the value
|
||||||
# represents the IP address of the server to connect to.
|
# represents the IP address of the server to connect to.
|
||||||
|
Reference in New Issue
Block a user