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