From 4db131082c0da6d8d6961f962a51a89d675e363d Mon Sep 17 00:00:00 2001 From: ypsdf1 Date: Mon, 28 Sep 2026 00:03:07 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=B0=B8=E4=B9=85=E5=B0=81=E7=A6=81?= =?UTF-8?q?=E7=8E=A9=E5=AE=B6=E7=A6=81=E6=AD=A2=E7=99=BB=E5=BD=95Web?= =?UTF-8?q?=E5=90=8E=E5=8F=B0=E3=80=81=E6=AD=A3=E7=89=88=E9=AA=8C=E8=AF=81?= =?UTF-8?q?=E7=B2=98=E8=B4=B4=E9=A1=B5=E5=93=8D=E5=BA=94=E5=BC=82=E5=B8=B8?= =?UTF-8?q?=E6=97=B6=E5=9B=9E=E6=9F=A5=E7=9C=9F=E5=AE=9E=E7=8A=B6=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - core.php 新增 playerWebBanReason():查 web_player_bans 名字封禁(永久/未过期),expire_time 兼容Java推送的毫秒单位;validateWebAccess 接入,商城/CDK/余额/token校验一律拒绝 - api/sync.php 四个登录入口全部拦截:validate_weblogin_token、web_login_request、send_email_code、verify_email_code - api/minecraft_auth.php:success/error 清空输出缓冲保证纯JSON;新增 action=status 会话真实状态查询;粘贴页提交响应异常时回查状态,已验证则如实提示通过,不再误报"服务器响应异常" --- api/minecraft_auth.php | 53 +++++++++++++++++++++++++++++++++++++++--- api/sync.php | 26 ++++++++++++++++++++- core.php | 40 +++++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+), 4 deletions(-) diff --git a/api/minecraft_auth.php b/api/minecraft_auth.php index cc7b097..5408b7e 100644 --- a/api/minecraft_auth.php +++ b/api/minecraft_auth.php @@ -64,11 +64,15 @@ if (mcAuthIpBlacklisted()) { // ==================================================================== function success($data = null) { + // ★ 清空所有输出缓冲,保证响应体是纯JSON(否则前端 JSON.parse 失败会误报"服务器响应异常") + while (ob_get_level() > 0) { @ob_end_clean(); } echo json_encode(['success' => true, 'data' => $data], JSON_UNESCAPED_UNICODE); exit; } function error($msg, $code = 400) { + // ★ 清空所有输出缓冲,保证响应体是纯JSON + while (ob_get_level() > 0) { @ob_end_clean(); } http_response_code($code); echo json_encode(['success' => false, 'error' => $msg], JSON_UNESCAPED_UNICODE); exit; @@ -599,6 +603,29 @@ switch ($action) { break; } + // ===== 粘贴页/前端轮询:查询会话真实状态(仅凭session_id)===== + // 用途:前端提交响应异常(非JSON/网关错误)时回查真实结果,避免"已验证通过却提示响应异常"的误导 + case 'status': { + $sessionId = getParam('session_id'); + if (empty($sessionId)) error('缺少session_id参数'); + + $stmt = $db->prepare("SELECT * FROM mc_auth_sessions WHERE session_id = ?"); + $stmt->execute([$sessionId]); + $session = $stmt->fetch(PDO::FETCH_ASSOC); + if (!$session) error('会话不存在或已过期'); + + $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']; + } + success($result); + break; + } + // ===== 浏览器回调:处理Microsoft OAuth ===== case 'callback': { $code = getParam('code'); @@ -899,6 +926,24 @@ if(ph.has('code'))return p.get('code'); }catch(e){} return input; } +function enableBtn(){var btn=document.getElementById('submitBtn');btn.disabled=false;btn.textContent='提交验证';} +function showRealStatus(httpStatus,raw){ +// ★ 提交响应异常时回查会话真实状态:已验证就如实告知"已通过",不再误导为"服务器响应异常" +var x2=new XMLHttpRequest(); +x2.open('GET','minecraft_auth.php?action=status&session_id='+encodeURIComponent(sessionId),true); +x2.onreadystatechange=function(){ +if(x2.readyState!==4)return; +var st=null; +try{var d2=JSON.parse(x2.responseText); if(d2.success&&d2.data)st=d2.data.status;}catch(e){} +var rawPrev=String(raw||'').replace(/\s+/g,' ').substring(0,160); +if(st==='verified'){showMsg('✅ 服务器已验证通过(提交时响应异常已自动忽略),请关闭此页面返回游戏','ok');} +else if(st==='failed'){showMsg('❌ 验证失败,请重新提交 (HTTP '+httpStatus+')','err');enableBtn();} +else if(st==='expired'){showMsg('❌ 会话已过期,请回游戏重新发起验证','err');enableBtn();} +else if(st==='pending'){showMsg('⚠️ 响应异常(HTTP '+httpStatus+'),服务器仍在验证中,请稍后再次点击提交…','err');enableBtn();} +else{showMsg('❌ 服务器响应异常(HTTP '+httpStatus+'): '+rawPrev,'err');enableBtn();} +}; +x2.send(); +} function doSubmit(code){ if(submitting)return; submitting=true; @@ -910,10 +955,12 @@ xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded'); xhr.onreadystatechange=function(){ if(xhr.readyState===4){ submitting=false; -try{var d=JSON.parse(xhr.responseText); +var body=''; +try{body=xhr.responseText||'';}catch(e){} +try{var d=JSON.parse(body); if(d.success){showMsg('✅ 验证成功!请关闭此页面返回游戏','ok');} -else{showMsg('❌ '+(d.error||'验证失败'),'err');btn.disabled=false;btn.textContent='提交验证';} -}catch(e){showMsg('❌ 服务器响应异常','err');btn.disabled=false;btn.textContent='提交验证';} +else{showMsg('❌ '+(d.error||d.message||'验证失败'),'err');enableBtn();} +}catch(e){showRealStatus(xhr.status,body);} }}; xhr.send('session_id='+encodeURIComponent(sessionId)+'&code='+encodeURIComponent(code)); } diff --git a/api/sync.php b/api/sync.php index 8de7b65..23c44c5 100644 --- a/api/sync.php +++ b/api/sync.php @@ -2001,6 +2001,12 @@ function apiValidateWebloginToken() { error('登录Token已过期'); } + // ★ 封禁拦截:被封禁玩家禁止登录Web后台(永久封禁/未过期封禁均拦截) + $banReason = playerWebBanReason($row['player_name'], $db); + if ($banReason !== null) { + error('账号已被封禁:' . $banReason); + } + success(['player' => $row['player_name']], 'Token有效'); } catch (\Throwable $e) { error('Token验证失败: ' . $e->getMessage()); @@ -2379,6 +2385,12 @@ function webLoginRequest() { $db = getDB(); + // ★ 封禁拦截:被封禁玩家禁止走密码登录 + $banReason = playerWebBanReason($player, $db); + if ($banReason !== null) { + error('账号已被封禁:' . $banReason); + } + // 检查玩家是否已注册 if (!isPlayerRegistered($player)) { error('玩家未注册,请先在游戏中注册'); @@ -3421,6 +3433,12 @@ function sendEmailCode() { error('玩家未在游戏中注册,请先在游戏中使用 /register 注册账号'); } + // ★ 封禁拦截:被封禁玩家禁止发送登录验证码 + $banReason = playerWebBanReason($player); + if ($banReason !== null) { + error('账号已被封禁:' . $banReason); + } + // ★ 从数据库读取玩家绑定的邮箱(唯一可信来源) $db = getDB(); $stmt = $db->prepare("SELECT email FROM users WHERE player_name = :player"); @@ -3526,9 +3544,15 @@ function verifyEmailCode() { $tokenInfo = validateTokenSilent($webToken); if (!$tokenInfo) error('无效的登录token'); - // 验证玩家名是否匹配 + // ★ 验证玩家名是否匹配 if ($tokenInfo['player'] !== $player) error('玩家名不匹配'); + // ★ 封禁拦截:被封禁玩家禁止走邮箱验证码登录 + $banReason = playerWebBanReason($player); + if ($banReason !== null) { + error('账号已被封禁:' . $banReason); + } + // ★ 从数据库读取玩家绑定的邮箱(唯一可信来源) $db = getDB(); $stmt = $db->prepare("SELECT email FROM users WHERE player_name = :player"); diff --git a/core.php b/core.php index 87ae21b..0ffce95 100644 --- a/core.php +++ b/core.php @@ -1431,6 +1431,39 @@ function isPlayerRegistered($playerName) { * @param string $password 用户输入的密码(关键操作时需要) * @param string $ipAddress 客户端IP */ +/** + * ★ 封禁拦截:查询玩家是否处于【名字封禁】生效期(Java /ban 通过 sync_bans 同步的 web_player_bans) + * 返回 null = 未封禁(或数据不可用时放行);否则返回封禁原因字符串(供前端提示)。 + * expire_time 单位兼容:Java 推送的是毫秒(BanEntry.getExpiration().getTime()),0 = 永久。 + * 覆盖范围:token登录 / 密码登录 / 邮箱验证码登录 / 商城·CDK·余额等 validateWebAccess 入口。 + */ +function playerWebBanReason($player, $db = null) { + if ($player === null || $player === '') return null; + try { + if ($db === null) $db = getDB(); + // 表不存在 = 从未同步过封禁名单 → 直接放行(避免读路径执行DDL) + $exists = $db->querySingle("SELECT name FROM sqlite_master WHERE type='table' AND name='web_player_bans'"); + if (!$exists) return null; + $stmt = $db->prepare("SELECT reason, expire_time FROM web_player_bans WHERE ban_type = 'name' AND LOWER(target) = LOWER(:p)"); + if (!$stmt) return null; + $stmt->bindValue(':p', $player, SQLITE3_TEXT); + $res = $stmt->execute(); + if (!$res) return null; + $now = time(); + while ($row = $res->fetchArray(SQLITE3_ASSOC)) { + $exp = (int)$row['expire_time']; + if ($exp > 100000000000) $exp = intdiv($exp, 1000); // 毫秒 → 秒 + if ($exp === 0 || $exp > $now) { + $reason = trim((string)($row['reason'] ?? '')); + return $reason !== '' ? $reason : '违反服务器规则'; + } + } + } catch (\Throwable $e) { + @error_log('[playerWebBanReason] ' . $e->getMessage()); + } + return null; +} + function validateWebAccess($webToken, $action = 'view', $password = null, $ipAddress = null) { if (!$ipAddress) $ipAddress = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0'; @@ -1459,6 +1492,13 @@ function validateWebAccess($webToken, $action = 'view', $password = null, $ipAdd $playerName = $row['player_name']; + // ★ 封禁拦截:被封禁玩家禁止使用Web端(商城/CDK/余额/登录校验一律拒绝) + $banReason = playerWebBanReason($playerName, $db); + if ($banReason !== null) { + @error_log("[validateWebAccess] DENIED: player banned. player=$playerName"); + return ['ok' => false, 'mode' => 'denied', 'message' => '账号已被封禁:' . $banReason]; + } + // 在线玩家表(Java插件同步,这里确保表存在) try { $db->exec("CREATE TABLE IF NOT EXISTS online_players (player_name TEXT PRIMARY KEY, login_time INTEGER DEFAULT 0)");