新增Microsoft OAuth正版验证API
1. 新增minecraft_auth.php - 完整的Microsoft OAuth流程 - 支持create_session/check_session/callback/verify_premium - 验证状态持久化到SQLite 2. 配置项在pay_secrets.php中(gitignored,手工FTP上传)
This commit is contained in:
@@ -0,0 +1,585 @@
|
||||
<?php
|
||||
/**
|
||||
* Minecraft正版验证 - Microsoft OAuth代理
|
||||
*
|
||||
* 流程:
|
||||
* 1. Java调用 create_session → 生成授权URL → 返回给Java
|
||||
* 2. 玩家在浏览器打开URL → Microsoft登录 → 回调到本文件callback
|
||||
* 3. callback处理授权码 → 交换token → 获取Minecraft profile → 存入SQLite
|
||||
* 4. Java轮询 check_session → 返回验证状态和profile
|
||||
*
|
||||
* Actions:
|
||||
* create_session - 创建验证会话,返回授权URL
|
||||
* check_session - 检查会话状态(Java轮询)
|
||||
* callback - Microsoft OAuth回调(浏览器重定向)
|
||||
*/
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
|
||||
// 加载密钥配置
|
||||
require_once __DIR__ . '/pay_secrets.php';
|
||||
|
||||
// ====================================================================
|
||||
// 工具函数
|
||||
// ====================================================================
|
||||
|
||||
function success($data = null) {
|
||||
echo json_encode(['success' => true, 'data' => $data], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
function error($msg, $code = 400) {
|
||||
http_response_code($code);
|
||||
echo json_encode(['success' => false, 'error' => $msg], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
function getParam($key, $default = '') {
|
||||
return isset($_GET[$key]) ? trim($_GET[$key]) : $default;
|
||||
}
|
||||
|
||||
function postParam($key, $default = '') {
|
||||
return isset($_POST[$key]) ? trim($_POST[$key]) : $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取SQLite数据库连接(orders.db)
|
||||
*/
|
||||
function getDb() {
|
||||
$possiblePaths = [
|
||||
dirname(__DIR__) . '/data/orders.db',
|
||||
dirname(__DIR__) . '/orders.db',
|
||||
dirname(__DIR__) . '/../data/orders.db',
|
||||
dirname(__DIR__) . '/../orders.db',
|
||||
];
|
||||
foreach ($possiblePaths as $path) {
|
||||
if (file_exists($path)) {
|
||||
$db = new PDO('sqlite:' . $path);
|
||||
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
$db->exec('PRAGMA journal_mode=WAL');
|
||||
return $db;
|
||||
}
|
||||
}
|
||||
// 都不存在,创建在第一个路径
|
||||
$dir = dirname($possiblePaths[0]);
|
||||
if (!is_dir($dir)) @mkdir($dir, 0755, true);
|
||||
$db = new PDO('sqlite:' . $possiblePaths[0]);
|
||||
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
$db->exec('PRAGMA journal_mode=WAL');
|
||||
return $db;
|
||||
}
|
||||
|
||||
/**
|
||||
* 确保mc_auth_sessions表存在
|
||||
*/
|
||||
function ensureTable($db) {
|
||||
$db->exec("CREATE TABLE IF NOT EXISTS mc_auth_sessions (
|
||||
session_id TEXT PRIMARY KEY,
|
||||
player_name TEXT NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'pending',
|
||||
mc_uuid TEXT DEFAULT '',
|
||||
mc_username TEXT DEFAULT '',
|
||||
mc_access_token TEXT DEFAULT '',
|
||||
ip_address TEXT DEFAULT '',
|
||||
created_at INTEGER NOT NULL,
|
||||
verified_at INTEGER DEFAULT 0,
|
||||
expires_at INTEGER NOT NULL
|
||||
)");
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成随机session_id
|
||||
*/
|
||||
function generateSessionId() {
|
||||
return bin2hex(random_bytes(16));
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Microsoft OAuth 工具函数
|
||||
// ====================================================================
|
||||
|
||||
/**
|
||||
* 向Microsoft发送POST请求
|
||||
*/
|
||||
function msPost($url, $data, $headers = []) {
|
||||
$ch = curl_init($url);
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_POSTFIELDS => is_array($data) ? http_build_query($data) : $data,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_TIMEOUT => 30,
|
||||
CURLOPT_SSL_VERIFYPEER => true,
|
||||
CURLOPT_HTTPHEADER => array_merge([
|
||||
'Content-Type: application/x-www-form-urlencoded',
|
||||
'Accept: application/json',
|
||||
], $headers),
|
||||
]);
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$error = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($error) {
|
||||
return ['error' => "cURL error: $error"];
|
||||
}
|
||||
return ['code' => $httpCode, 'body' => json_decode($response, true)];
|
||||
}
|
||||
|
||||
/**
|
||||
* 向Microsoft发送GET请求
|
||||
*/
|
||||
function msGet($url, $headers = []) {
|
||||
$ch = curl_init($url);
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_TIMEOUT => 30,
|
||||
CURLOPT_SSL_VERIFYPEER => true,
|
||||
CURLOPT_HTTPHEADER => array_merge([
|
||||
'Accept: application/json',
|
||||
], $headers),
|
||||
]);
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$error = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($error) {
|
||||
return ['error' => "cURL error: $error"];
|
||||
}
|
||||
return ['code' => $httpCode, 'body' => json_decode($response, true)];
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// 核心OAuth流程
|
||||
// ====================================================================
|
||||
|
||||
/**
|
||||
* 步骤1: 生成Microsoft授权URL
|
||||
*/
|
||||
function getAuthUrl($sessionId) {
|
||||
$clientId = MS_CLIENT_ID;
|
||||
$redirectUri = MS_REDIRECT_URI;
|
||||
$scope = 'service::user.auth.xboxlive.com::MBI_SSL';
|
||||
|
||||
$params = http_build_query([
|
||||
'client_id' => $clientId,
|
||||
'response_type' => 'code',
|
||||
'scope' => $scope,
|
||||
'redirect_uri' => $redirectUri,
|
||||
'state' => $sessionId,
|
||||
]);
|
||||
|
||||
return "https://login.live.com/oauth20_authorize.srf?$params";
|
||||
}
|
||||
|
||||
/**
|
||||
* 步骤2: 用授权码交换Access Token
|
||||
*/
|
||||
function exchangeToken($code) {
|
||||
$result = msPost('https://login.live.com/oauth20_token.srf', [
|
||||
'client_id' => MS_CLIENT_ID,
|
||||
'client_secret' => MS_CLIENT_SECRET,
|
||||
'code' => $code,
|
||||
'grant_type' => 'authorization_code',
|
||||
'redirect_uri' => MS_REDIRECT_URI,
|
||||
]);
|
||||
|
||||
if (isset($result['error'])) {
|
||||
return ['error' => 'Token exchange failed: ' . $result['error']];
|
||||
}
|
||||
if ($result['code'] !== 200) {
|
||||
return ['error' => 'Token exchange HTTP ' . $result['code']];
|
||||
}
|
||||
|
||||
$body = $result['body'];
|
||||
if (!isset($body['access_token'])) {
|
||||
return ['error' => 'No access_token in response', 'response' => $body];
|
||||
}
|
||||
|
||||
return [
|
||||
'access_token' => $body['access_token'],
|
||||
'refresh_token' => $body['refresh_token'] ?? '',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 步骤3: 获取Xbox Live Token
|
||||
*/
|
||||
function getXboxToken($msAccessToken) {
|
||||
$result = msPost('https://user.auth.xboxlive.com/user/authenticate', json_encode([
|
||||
'Properties' => [
|
||||
'AuthMethod' => 'RPS',
|
||||
'SiteName' => 'user.auth.xboxlive.com',
|
||||
'RpsTicket' => $msAccessToken,
|
||||
],
|
||||
'RelyingParty' => 'http://auth.xboxlive.com',
|
||||
'TokenType' => 'JWT',
|
||||
]), [
|
||||
'Content-Type: application/json',
|
||||
'Accept: application/json',
|
||||
]);
|
||||
|
||||
if (isset($result['error'])) {
|
||||
return ['error' => 'Xbox auth failed: ' . $result['error']];
|
||||
}
|
||||
if ($result['code'] !== 200) {
|
||||
return ['error' => 'Xbox auth HTTP ' . $result['code']];
|
||||
}
|
||||
|
||||
$body = $result['body'];
|
||||
if (!isset($body['Token'])) {
|
||||
return ['error' => 'No Xbox token', 'response' => $body];
|
||||
}
|
||||
|
||||
return [
|
||||
'token' => $body['Token'],
|
||||
'uhs' => $body['IssueClaims']['uhs'] ?? '',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 步骤4: 获取XSTS Token
|
||||
*/
|
||||
function getXstsToken($xboxToken, $uhs) {
|
||||
$result = msPost('https://xsts.auth.xboxlive.com/xsts/authorize', json_encode([
|
||||
'Properties' => [
|
||||
'SandboxId' => 'RETAIL',
|
||||
'UserTokens' => [$xboxToken],
|
||||
],
|
||||
'RelyingParty' => 'rp://api.minecraftservices.com/',
|
||||
'TokenType' => 'JWT',
|
||||
]), [
|
||||
'Content-Type: application/json',
|
||||
'Accept: application/json',
|
||||
]);
|
||||
|
||||
if (isset($result['error'])) {
|
||||
return ['error' => 'XSTS auth failed: ' . $result['error']];
|
||||
}
|
||||
if ($result['code'] !== 200) {
|
||||
$body = $result['body'] ?? [];
|
||||
$errCode = $body['XErr'] ?? 'unknown';
|
||||
return ['error' => "XSTS auth HTTP {$result['code']}, XErr: {$errCode}", 'response' => $body];
|
||||
}
|
||||
|
||||
$body = $result['body'];
|
||||
if (!isset($body['Token'])) {
|
||||
return ['error' => 'No XSTS token', 'response' => $body];
|
||||
}
|
||||
|
||||
return [
|
||||
'token' => $body['Token'],
|
||||
'uhs' => $body['IssueClaims']['uhs'] ?? $uhs,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 步骤5: 获取Minecraft Access Token
|
||||
*/
|
||||
function getMinecraftToken($xstsToken, $uhs) {
|
||||
$result = msPost('https://api.minecraftservices.com/authentication/login_with_xbox', json_encode([
|
||||
'identityToken' => "XBL3.0 x={$uhs};{$xstsToken}",
|
||||
]), [
|
||||
'Content-Type: application/json',
|
||||
'Accept: application/json',
|
||||
]);
|
||||
|
||||
if (isset($result['error'])) {
|
||||
return ['error' => 'MC token failed: ' . $result['error']];
|
||||
}
|
||||
if ($result['code'] !== 200) {
|
||||
return ['error' => 'MC token HTTP ' . $result['code'], 'response' => $result['body']];
|
||||
}
|
||||
|
||||
$body = $result['body'];
|
||||
if (!isset($body['access_token'])) {
|
||||
return ['error' => 'No MC access_token', 'response' => $body];
|
||||
}
|
||||
|
||||
return ['access_token' => $body['access_token']];
|
||||
}
|
||||
|
||||
/**
|
||||
* 步骤6: 获取Minecraft Profile
|
||||
*/
|
||||
function getMinecraftProfile($mcAccessToken) {
|
||||
$result = msGet('https://api.minecraftservices.com/minecraft/profile', [
|
||||
'Authorization: Bearer ' . $mcAccessToken,
|
||||
]);
|
||||
|
||||
if (isset($result['error'])) {
|
||||
return ['error' => 'MC profile failed: ' . $result['error']];
|
||||
}
|
||||
if ($result['code'] === 404) {
|
||||
return ['error' => 'No Minecraft profile found (account may not own the game)'];
|
||||
}
|
||||
if ($result['code'] !== 200) {
|
||||
return ['error' => 'MC profile HTTP ' . $result['code'], 'response' => $result['body']];
|
||||
}
|
||||
|
||||
$body = $result['body'];
|
||||
if (!isset($body['id']) || !isset($body['name'])) {
|
||||
return ['error' => 'Invalid MC profile', 'response' => $body];
|
||||
}
|
||||
|
||||
// 格式化UUID(加横杠)
|
||||
$rawUuid = $body['id'];
|
||||
$formattedUuid = substr($rawUuid, 0, 8) . '-'
|
||||
. substr($rawUuid, 8, 4) . '-'
|
||||
. substr($rawUuid, 12, 4) . '-'
|
||||
. substr($rawUuid, 16, 4) . '-'
|
||||
. substr($rawUuid, 20, 12);
|
||||
|
||||
return [
|
||||
'uuid' => $formattedUuid,
|
||||
'name' => $body['name'],
|
||||
'skins' => $body['skins'] ?? [],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 完整OAuth流程(从授权码到Minecraft Profile)
|
||||
*/
|
||||
function completeAuthFlow($code) {
|
||||
// 步骤2: 交换MS Token
|
||||
$tokenResult = exchangeToken($code);
|
||||
if (isset($tokenResult['error'])) return $tokenResult;
|
||||
|
||||
// 步骤3: Xbox Live Token
|
||||
$xboxResult = getXboxToken($tokenResult['access_token']);
|
||||
if (isset($xboxResult['error'])) return $xboxResult;
|
||||
|
||||
// 步骤4: XSTS Token
|
||||
$xstsResult = getXstsToken($xboxResult['token'], $xboxResult['uhs']);
|
||||
if (isset($xstsResult['error'])) return $xstsResult;
|
||||
|
||||
// 步骤5: Minecraft Token
|
||||
$mcResult = getMinecraftToken($xstsResult['token'], $xstsResult['uhs']);
|
||||
if (isset($mcResult['error'])) return $mcResult;
|
||||
|
||||
// 步骤6: Minecraft Profile
|
||||
$profileResult = getMinecraftProfile($mcResult['access_token']);
|
||||
if (isset($profileResult['error'])) return $profileResult;
|
||||
|
||||
return [
|
||||
'uuid' => $profileResult['uuid'],
|
||||
'name' => $profileResult['name'],
|
||||
'access_token' => $mcResult['access_token'],
|
||||
];
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Action处理
|
||||
// ====================================================================
|
||||
|
||||
$action = getParam('action');
|
||||
$db = getDb();
|
||||
ensureTable($db);
|
||||
|
||||
switch ($action) {
|
||||
|
||||
// ===== Java调用:创建验证会话 =====
|
||||
case 'create_session': {
|
||||
$player = getParam('player');
|
||||
$secret = getParam('secret');
|
||||
$ip = $_SERVER['REMOTE_ADDR'] ?? '';
|
||||
|
||||
if (empty($player)) error('缺少player参数');
|
||||
if ($secret !== MC_AUTH_SECRET) error('密钥错误', 403);
|
||||
|
||||
// 检查MS_CLIENT_SECRET是否配置
|
||||
if (empty(MS_CLIENT_SECRET)) {
|
||||
error('Microsoft OAuth未配置:缺少MS_CLIENT_SECRET,请在pay_secrets.php中设置');
|
||||
}
|
||||
|
||||
// 清理该玩家的旧会话
|
||||
$stmt = $db->prepare("DELETE FROM mc_auth_sessions WHERE player_name = ? AND status != 'verified'");
|
||||
$stmt->execute([$player]);
|
||||
|
||||
// 创建新会话
|
||||
$sessionId = generateSessionId();
|
||||
$now = time();
|
||||
$expiresAt = $now + 600; // 10分钟过期
|
||||
|
||||
$stmt = $db->prepare("INSERT INTO mc_auth_sessions (session_id, player_name, status, ip_address, created_at, expires_at) VALUES (?, ?, 'pending', ?, ?, ?)");
|
||||
$stmt->execute([$sessionId, $player, $ip, $now, $expiresAt]);
|
||||
|
||||
// 生成授权URL
|
||||
$authUrl = getAuthUrl($sessionId);
|
||||
|
||||
success([
|
||||
'session_id' => $sessionId,
|
||||
'auth_url' => $authUrl,
|
||||
'expires_in' => 600,
|
||||
]);
|
||||
break;
|
||||
}
|
||||
|
||||
// ===== Java调用:检查会话状态 =====
|
||||
case 'check_session': {
|
||||
$sessionId = getParam('session_id');
|
||||
$secret = getParam('secret');
|
||||
|
||||
if (empty($sessionId)) error('缺少session_id参数');
|
||||
if ($secret !== MC_AUTH_SECRET) error('密钥错误', 403);
|
||||
|
||||
// 查询会话
|
||||
$stmt = $db->prepare("SELECT * FROM mc_auth_sessions WHERE session_id = ?");
|
||||
$stmt->execute([$sessionId]);
|
||||
$session = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$session) {
|
||||
error('会话不存在');
|
||||
}
|
||||
|
||||
// 检查是否过期
|
||||
if ($session['status'] === 'pending' && time() > $session['expires_at']) {
|
||||
$stmt = $db->prepare("UPDATE mc_auth_sessions SET status = 'expired' WHERE session_id = ?");
|
||||
$stmt->execute([$sessionId]);
|
||||
$session['status'] = 'expired';
|
||||
}
|
||||
|
||||
$result = [
|
||||
'status' => $session['status'],
|
||||
'player_name' => $session['player_name'],
|
||||
];
|
||||
|
||||
if ($session['status'] === 'verified') {
|
||||
$result['mc_uuid'] = $session['mc_uuid'];
|
||||
$result['mc_username'] = $session['mc_username'];
|
||||
$result['verified_at'] = $session['verified_at'];
|
||||
} elseif ($session['status'] === 'failed') {
|
||||
$result['error'] = '验证失败,请重试';
|
||||
}
|
||||
|
||||
success($result);
|
||||
break;
|
||||
}
|
||||
|
||||
// ===== 浏览器回调:处理Microsoft OAuth =====
|
||||
case 'callback': {
|
||||
$code = getParam('code');
|
||||
$state = getParam('state'); // session_id
|
||||
$msError = getParam('error');
|
||||
|
||||
// Microsoft返回错误
|
||||
if ($msError) {
|
||||
$errorDesc = getParam('error_description', 'Microsoft登录被拒绝');
|
||||
if ($state) {
|
||||
$stmt = $db->prepare("UPDATE mc_auth_sessions SET status = 'failed' WHERE session_id = ?");
|
||||
$stmt->execute([$state]);
|
||||
}
|
||||
echo "<!DOCTYPE html><html><head><meta charset='utf-8'><title>验证失败</title>
|
||||
<style>body{font-family:sans-serif;display:flex;justify-content:center;align-items:center;height:100vh;margin:0;background:#1a1a2e;color:#fff;}
|
||||
.card{background:#16213e;padding:40px;border-radius:16px;text-align:center;max-width:400px;}
|
||||
.icon{font-size:48px;margin-bottom:16px;}</style></head>
|
||||
<body><div class='card'><div class='icon'>❌</div><h2>验证失败</h2><p>" . htmlspecialchars($errorDesc) . "</p>
|
||||
<p style='color:#888;font-size:14px;'>请关闭此页面并返回游戏重试</p></div></body></html>";
|
||||
exit;
|
||||
}
|
||||
|
||||
if (empty($code) || empty($state)) {
|
||||
echo "<!DOCTYPE html><html><head><meta charset='utf-8'><title>参数错误</title></head><body><h1>参数错误</h1></body></html>";
|
||||
exit;
|
||||
}
|
||||
|
||||
// 查询会话
|
||||
$stmt = $db->prepare("SELECT * FROM mc_auth_sessions WHERE session_id = ?");
|
||||
$stmt->execute([$state]);
|
||||
$session = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$session) {
|
||||
echo "<!DOCTYPE html><html><head><meta charset='utf-8'><title>会话不存在</title></head><body><h1>会话不存在或已过期</h1></body></html>";
|
||||
exit;
|
||||
}
|
||||
|
||||
if (time() > $session['expires_at']) {
|
||||
$stmt = $db->prepare("UPDATE mc_auth_sessions SET status = 'expired' WHERE session_id = ?");
|
||||
$stmt->execute([$state]);
|
||||
echo "<!DOCTYPE html><html><head><meta charset='utf-8'><title>会话过期</title></head><body><h1>会话已过期,请返回游戏重新发起验证</h1></body></html>";
|
||||
exit;
|
||||
}
|
||||
|
||||
// 执行完整OAuth流程
|
||||
$result = completeAuthFlow($code);
|
||||
|
||||
if (isset($result['error'])) {
|
||||
// 验证失败
|
||||
$stmt = $db->prepare("UPDATE mc_auth_sessions SET status = 'failed' WHERE session_id = ?");
|
||||
$stmt->execute([$state]);
|
||||
echo "<!DOCTYPE html><html><head><meta charset='utf-8'><title>验证失败</title>
|
||||
<style>body{font-family:sans-serif;display:flex;justify-content:center;align-items:center;height:100vh;margin:0;background:#1a1a2e;color:#fff;}
|
||||
.card{background:#16213e;padding:40px;border-radius:16px;text-align:center;max-width:400px;}
|
||||
.icon{font-size:48px;margin-bottom:16px;}</style></head>
|
||||
<body><div class='card'><div class='icon'>❌</div><h2>验证失败</h2><p>" . htmlspecialchars($result['error']) . "</p>
|
||||
<p style='color:#888;font-size:14px;'>请关闭此页面并返回游戏重试</p></div></body></html>";
|
||||
exit;
|
||||
}
|
||||
|
||||
// 验证成功!
|
||||
$stmt = $db->prepare("UPDATE mc_auth_sessions SET status = 'verified', mc_uuid = ?, mc_username = ?, mc_access_token = ?, verified_at = ? WHERE session_id = ?");
|
||||
$stmt->execute([$result['uuid'], $result['name'], $result['access_token'], time(), $state]);
|
||||
|
||||
// 显示成功页面
|
||||
$mcName = htmlspecialchars($result['name']);
|
||||
$mcUuid = htmlspecialchars($result['uuid']);
|
||||
echo "<!DOCTYPE html><html><head><meta charset='utf-8'><title>验证成功</title>
|
||||
<style>body{font-family:sans-serif;display:flex;justify-content:center;align-items:center;height:100vh;margin:0;background:#1a1a2e;color:#fff;}
|
||||
.card{background:#16213e;padding:40px;border-radius:16px;text-align:center;max-width:400px;}
|
||||
.icon{font-size:48px;margin-bottom:16px;}
|
||||
.name{font-size:24px;font-weight:bold;color:#4ade80;margin:8px 0;}
|
||||
.uuid{font-size:12px;color:#888;word-break:break-all;}</style></head>
|
||||
<body><div class='card'>
|
||||
<div class='icon'>✅</div>
|
||||
<h2>正版验证成功!</h2>
|
||||
<div class='name'>$mcName</div>
|
||||
<div class='uuid'>$mcUuid</div>
|
||||
<p style='margin-top:20px;color:#4ade80;'>请关闭此页面返回游戏</p>
|
||||
<p style='color:#888;font-size:14px;'>你的正版身份已验证,将自动登录服务器</p>
|
||||
</div></body></html>";
|
||||
exit;
|
||||
}
|
||||
|
||||
// ===== Java调用:直接验证(用于验证Minecraft账号是否正版) =====
|
||||
case 'verify_premium': {
|
||||
$player = getParam('player');
|
||||
$secret = getParam('secret');
|
||||
|
||||
if (empty($player)) error('缺少player参数');
|
||||
if ($secret !== MC_AUTH_SECRET) error('密钥错误', 403);
|
||||
|
||||
// 通过Mojang API验证用户名是否对应正版账号
|
||||
$url = "https://api.mojang.com/users/profiles/minecraft/" . urlencode($player);
|
||||
$result = msGet($url);
|
||||
|
||||
if (isset($result['error'])) {
|
||||
error('Mojang API请求失败: ' . $result['error']);
|
||||
}
|
||||
|
||||
if ($result['code'] === 200 && isset($result['body']['id'])) {
|
||||
$rawUuid = $result['body']['id'];
|
||||
$formattedUuid = substr($rawUuid, 0, 8) . '-'
|
||||
. substr($rawUuid, 8, 4) . '-'
|
||||
. substr($rawUuid, 12, 4) . '-'
|
||||
. substr($rawUuid, 16, 4) . '-'
|
||||
. substr($rawUuid, 20, 12);
|
||||
|
||||
success([
|
||||
'is_premium' => true,
|
||||
'uuid' => $formattedUuid,
|
||||
'name' => $result['body']['name'] ?? $player,
|
||||
]);
|
||||
} else {
|
||||
success([
|
||||
'is_premium' => false,
|
||||
'uuid' => null,
|
||||
'name' => $player,
|
||||
]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
error('未知action: ' . $action);
|
||||
}
|
||||
Reference in New Issue
Block a user