第57批续10:webbind绑邮箱密码验证接入统一风控+空行清理
Check Source / check (push) Successful in 4s
Auto Release / build (push) Successful in 2m21s
Simple Test / test (push) Successful in 1s
Test Env / test (push) Successful in 1s

- Main.java: /webbind approve 密码验证从独立webbindPwdFails内存计数器
  接入RiskControlManager(与/login完全一致):密码错5次→Bukkit真实封禁
  5分钟+踢出+报警邮件+重连拒绝+到期自动解封
- Main.java: 删除已无引用的webbindPwdFails字段
- RiskControlManager.java: 按8a693e规则清理方法内部空行(11行)
This commit is contained in:
youpaishidifu
2026-10-26 19:20:05 +08:00
parent 74e2c60d60
commit 325d363f4b
2 changed files with 24 additions and 26 deletions
+24 -15
View File
@@ -142,7 +142,6 @@ public class Main extends JavaPlugin
new HashMap<>();
private static final long IP_SKIP_MS = 300000L;
// ★ 第56批:/webbind 批准密码验证失败计数(玩家名 -> 失败次数)
private final ConcurrentHashMap<String, Integer> webbindPwdFails = new ConcurrentHashMap<>();
// ★ 第57批续5: /webbind approve 改聊天框输入密码(玩家名 -> 请求ID)
private final ConcurrentHashMap<String, String> pendingWebbindApprove = new ConcurrentHashMap<>();
private SalesStatsManager salesStats;
@@ -6271,28 +6270,36 @@ public class Main extends JavaPlugin
"reject", "", status -> {});
return;
}
// 密码验证
int failCount2 = webbindPwdFails.getOrDefault(p.getName(), 0);
if (failCount2 >= 5) {
// ★ 第57批续10:接入统一风控(RiskControlManager),与 /login 完全一致
// 密码错5次 → Bukkit真实封禁5分钟 + 踢出 + 报警邮件(不再仅提示)
if (riskControl != null && riskControl.isBanned(p.getName())) {
int rem2 = riskControl.getBanRemainingSeconds(p.getName());
pendingWebbindApprove.remove(p.getName());
p.sendMessage("§c§l[风控] §f连续5次失败,已被临时限制");
p.sendMessage("§c§l[风控] §f密码错误次数过多,账号已被临时封禁 §e"
+ rem2 + " §f秒");
return;
}
String pwdSalt2 = (String) db.getField(p.getName(), "password_salt");
String pwdHash2 = PasswordUtils.hash(input, pwdSalt2);
String pwdResult2 = db.checkPasswordWithFallback(p.getName(), pwdHash2);
if (pwdResult2 == null) {
failCount2++;
webbindPwdFails.put(p.getName(), failCount2);
p.sendMessage("§c密码错误(" + failCount2 + "/5)");
if (failCount2 >= 5) {
p.sendMessage("§c§l[风控] §f连续5次失败,已被临时限制");
int remaining2 = riskControl != null
? riskControl.recordFailAttempt(p) : 4;
if (remaining2 < 0) {
// 已达阈值:triggerBan 已真实封禁并踢出
return;
}
p.sendMessage("§c密码错误");
if (remaining2 > 0) {
p.sendMessage("§c§l[风控] §f剩余 §e" + remaining2 + " §f次尝试机会");
} else {
p.sendMessage("§c§l[风控] §f连续5次失败,已被临时封禁");
pendingWebbindApprove.remove(p.getName());
}
return;
}
// 验证通过,清零计数
webbindPwdFails.remove(p.getName());
// 验证通过,重置风控计数
if (riskControl != null) riskControl.onLoginSuccess(p.getName());
String reqId2 = pendingBindReq;
pendingWebbindApprove.remove(p.getName());
p.sendMessage("§e正在提交批准…");
@@ -9710,9 +9717,11 @@ public class Main extends JavaPlugin
// ★ 第56批:approve 时需要密码(Java端直接验证,不再依赖PHP复核)
if (bindApprove) {
// ★ 第57批续5: 改聊天框输入密码(不再要求命令行参数密码)
int failCount = webbindPwdFails.getOrDefault(wp.getName(), 0);
if (failCount >= 5) {
wp.sendMessage("§c§l[风控] §f密码验证失败次数过多,已被临时限制。请稍后再试");
// ★ 第57批续10:接入统一风控,封禁期内直接拒绝
if (riskControl != null && riskControl.isBanned(wp.getName())) {
int rem = riskControl.getBanRemainingSeconds(wp.getName());
wp.sendMessage("§c§l[风控] §f密码错误次数过多,账号已被临时封禁 §e"
+ rem + " §f秒,请稍后再试");
return true;
}
pendingWebbindApprove.put(wp.getName(), bindReqId);
@@ -88,23 +88,18 @@ public class RiskControlManager {
public int recordFailAttempt(Player player) {
String name = player.getName();
int maxAttempts = getMaxFailAttempts();
// 已被封禁,直接返回
if (isBanned(name)) {
return -1;
}
int current = failAttempts
.getOrDefault(name, 0) + 1;
failAttempts.put(name, current);
int remaining = Math.max(0, maxAttempts - current);
// 达到阈值 → 触发封禁
if (current >= maxAttempts) {
triggerBan(player);
}
return remaining;
}
@@ -117,16 +112,13 @@ public class RiskControlManager {
long durationMs = getBanDurationMs();
long until = System.currentTimeMillis() + durationMs;
banUntil.put(name, until);
String ip = plugin.getPlayerIP(player);
int banMinutes = (int) (durationMs / 60000);
plugin.getLogger().warning(
"[风控] 玩家 " + name
+ " 密码错误达到阈值("
+ getMaxFailAttempts() + "次)"
+ ",封禁 " + banMinutes + " 分钟");
// ★ 真实封禁:调用Bukkit BanList API阻止玩家重新加入服务器
String banReason = "§c§l[安全风控] 密码连续错误"
+ getMaxFailAttempts() + "次,"
@@ -145,7 +137,6 @@ public class RiskControlManager {
}
// 踢出玩家(无论BanList是否成功,立即踢出)
player.kickPlayer(banReason);
// 异步发送报警邮件
sendBanAlertEmail(name, ip, banMinutes);
}
@@ -164,7 +155,6 @@ public class RiskControlManager {
+ " 未绑定邮箱,跳过报警邮件");
return;
}
String subject =
"[安全警告] 您的账号已被临时封禁";
String body = "尊敬的玩家 " + playerName + ":\n\n"
@@ -187,7 +177,6 @@ public class RiskControlManager {
+ "如果非本人操作,建议您立即修改密码"
+ "(使用 /sdf1_login pw)。\n\n"
+ "--Sdf1_login 安全系统";
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
try {
plugin.getEmail().sendBody(