认证接入实战:源码与 Velocity
认证接入篇讲了流程和配置。这篇深入源码——把 net_bc.diff 的关键改动拆给你看,并补充 Velocity 代理的接入思路。
认证流程总览
原版 BungeeCord 玩家登录时,BC 用 GET 请求 Mojang 的 sessionserver.mojang.com/session/minecraft/hasJoined 验证。中国版改成:
玩家登录 → BC 收到 EncryptionResponse
→ 构造 HasJoinedRequest(username, serverId, gameId)
→ POST 到 auth_url(网易认证服)
→ 返回 LoginResult(id, name)
→ 验证通过则放行,失败则踢出核心就三处改动
- GET → POST,body 是 JSON
- 认证地址从 Mojang 改成网易
auth_url - 请求体带上
gameId标识你的游戏
源码实例(基于 net_bc.diff)
1. Configuration.java —— 新增两个配置项
读取 config.yml 里的 game_id 和 auth_url:
@Getter
public class Configuration implements ProxyConfig {
// ... 原有字段 ...
private String gameId; // 新增:游戏 iid
private String url = "http://localhost:9999/check"; // 新增:认证服地址
public void load() {
ConfigurationAdapter adapter = ProxyServer.getInstance().getConfigurationAdapter();
adapter.load();
// ... 原有读取 ...
gameId = adapter.getString("game_id", ""); // 读 game_id
url = adapter.getString("auth_url", url); // 读 auth_url
// ...
}
}@Getter(Lombok)自动生成 getGameId() 和 getUrl()。
2. HttpClient.java —— 新增 POST 支持
原版只有 get,中国版加了 post 方法,支持发 JSON body:
public static void post(String url, Object data, EventLoop eventLoop, Callback<String> callback) {
httpRequest(url, data, eventLoop, callback);
}
private static void httpRequest(String url, final Object data, EventLoop eventLoop,
final Callback<String> callback) {
// ... 解析 URI、端口、DNS 缓存(同 get)...
ChannelFutureListener future = new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()) {
String path = uri.getRawPath()
+ ((uri.getRawQuery() == null) ? "" : "?" + uri.getRawQuery());
HttpRequest request;
if (data == null) {
// 无 body → GET
request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, path);
} else {
// 有 body → POST,Content-Type: application/json
DefaultFullHttpRequest fullRequest =
new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, path);
fullRequest.headers().set(HttpHeaders.Names.CONTENT_TYPE, "application/json");
String content = BungeeCord.getInstance().gson.toJson(data); // 序列化
byte[] raw = content.getBytes("UTF-8");
fullRequest.headers().set(HttpHeaders.Names.CONTENT_LENGTH, raw.length);
fullRequest.content().clear().writeBytes(raw);
request = fullRequest;
}
request.headers().set(HttpHeaders.Names.HOST, uri.getHost());
future.channel().writeAndFlush(request);
} else {
addressCache.invalidate(uri.getHost());
callback.done(null, future.cause());
}
}
};
// ... 建连 ...
}关键点
- 用
DefaultFullHttpRequest(带 body)而非DefaultHttpRequest(无 body) Content-Type: application/json,body 用 Gson 序列化data对象data == null时退回 GET,兼容原行为
3. HasJoinedRequest.java —— 请求体结构
表示发给认证服的参数(构造时传入 username、serverId、gameId):
public class HasJoinedRequest {
private String username;
private String serverId;
private String gameId;
public HasJoinedRequest(String username, String serverId, String gameId) {
this.username = username;
this.serverId = serverId;
this.gameId = gameId;
}
// getter ...
}Gson 序列化后 JSON 形如:
{
"username": "Steve",
"serverId": "abc123...",
"gameId": "<你的游戏 gameid>"
}4. InitialHandler.java —— 认证调用
handle(EncryptionResponse) 里把原版 GET Mojang 改成 POST 网易认证服:
@Override
public void handle(final EncryptionResponse encryptResponse) throws Exception {
// ... 解密握手、算 serverId ...
String serverID = new BigInteger(sha.digest()).toString(16);
// 构造请求体:用户名 + serverId + gameId
final HasJoinedRequest data =
new HasJoinedRequest(username, serverID, this.getGameId());
Callback<String> handler = new Callback<String>() {
@Override
public void done(String result, Throwable error) {
if (error == null) {
// 解析响应
LoginResult obj = BungeeCord.getInstance().gson.fromJson(result, LoginResult.class);
if (obj != null && obj.getId() != null) {
loginProfile = obj;
name = obj.getName();
// ... 完成登录 ...
} else {
disconnect(bungee.getTranslation("offline_mode_player"));
}
} else {
disconnect(bungee.getTranslation("mojang_fail"));
bungee.getLogger().log(Level.SEVERE,
"Error authenticating " + getName() + " with minecraft.net", error);
}
}
};
// 原版:HttpClient.get(authURL, ch.getHandle().eventLoop(), handler);
// 改后:POST 到 auth_url
HttpClient.post(this.getUrl(), data, ch.getHandle().eventLoop(), handler);
}响应结构 LoginResult
网易认证服返回的(玩家 UUID)和 name(玩家名)。obj.getId() != null 即认证通过,取 id/name 完成登录。
BungeeCord 接入小结
| 文件 | 改动 |
|---|---|
Configuration.java | 新增 gameId、url 字段,load() 读 game_id/auth_url |
HttpClient.java | 新增 post() 方法,支持 JSON body |
HasJoinedRequest.java | 新增类,表示请求体结构 |
InitialHandler.java | 认证调用从 get(Mojang) 改 post(网易 auth_url) |
完整改动见官方 net_bc.diff,每行都在里面。
Velocity 接入
官方接受 Velocity
中国版提供的认证 patch(net_bc.diff)针对 BungeeCord 源码,官方未提供 Velocity 现成方案。但 Velocity 同样被接受——关键不在于用 BC 还是 Velocity,而在于认证接好、能通过中国版客户端进入服务器。BC 用现成 patch 省事,Velocity 需参照以下思路自行实现认证插件。
思路
Velocity 的认证逻辑用插件实现,而非改源码:监听玩家预登录事件,异步 POST 到 auth_url 验证,通过则放行、失败则踢出。
| BC 改源码 | Velocity 插件 |
|---|---|
改 InitialHandler | 监听 PreLoginEvent |
改 HttpClient | 用 Java 标准库 HttpClient |
config.yml 读 auth_url/game_id | 插件自定义配置读 |
| 编译进 BC jar | 独立插件 jar |
参考插件骨架
@Plugin(id = "netease-auth", name = "NetEaseAuth", version = "1.0")
public class NetEaseAuthPlugin {
private ProxyServer server;
private String authUrl;
private String gameId;
private final HttpClient httpClient = HttpClient.newHttpClient();
private final Gson gson = new Gson();
@Subscribe
public void onProxyInit(ProxyInitializeEvent event) {
// 从插件 config 读 auth_url、game_id
authUrl = loadConfig("auth_url");
gameId = loadConfig("game_id");
}
@Subscribe(order = PostOrder.FIRST)
public void onPreLogin(PreLoginEvent event) {
String username = event.getUsername();
// PreLoginEvent 同步,网络请求必须异步 + 阻塞等待结果
event.setResult(PreLoginEvent.PreLoginComponentResult.denied(
Component.text("正在验证..."))); // 先占位
// 异步验证
server.getScheduler().buildTask(this, () -> {
try {
// 构造请求体(同 HasJoinedRequest)
Map<String, String> body = new HashMap<>();
body.put("username", username);
body.put("serverId", computeServerId(event)); // 按 MC 协议算
body.put("gameId", gameId);
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create(authUrl))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(gson.toJson(body)))
.timeout(Duration.ofSeconds(5))
.build();
HttpResponse<String> resp = httpClient.send(req,
HttpResponse.BodyHandlers.ofString());
// 解析响应(同 LoginResult)
JsonObject obj = gson.fromJson(resp.body(), JsonObject.class);
if (obj != null && obj.get("id") != null) {
// 验证通过 —— 注意:PreLoginEvent 已结束,
// 实际需用 LoginPhaseEvent 或重连机制,见下方说明
} else {
// 踢出
}
} catch (Exception e) {
// 认证失败处理
}
}).schedule();
}
}这只是骨架,不能直接用
PreLoginEvent 是同步的,事件返回时异步认证还没完成。真正落地需用 Velocity 的 LoginPhaseEvent(1.19.4+)或连接状态机,在认证回调里决定放行/踢出。serverId 的算法、响应字段也需对照 BC 的 InitialHandler 和实际抓包确认。建议直接用官方 BC 方案,Velocity 方案仅作技术探索。
BC vs Velocity 取舍
| 维度 | BungeeCord | Velocity |
|---|---|---|
| 官方认证支持 | ✅ 有 patch + 源码包 | ❌ 无,需自行实现 |
| 上线合规 | 接受 | 同样接受(认证接好、能通过中国版客户端进入即可) |
| 性能 | 够用 | 更好 |
| 接入成本 | 低(用现成的) | 高(自己写+调试+验证) |
按团队技术栈选
BC 和 Velocity 都被接受。BC 有现成认证 patch,接入省事;Velocity 性能更好,但需自行实现认证插件。核心验收标准统一:认证接好、能通过中国版客户端进入服务器。按团队熟悉的技术栈选即可。
源码实例和 Velocity 内容到此。认证接好后,上架前还有最后一关——二审(内容审核)。回到认证接入或Java 网络游戏首页。
