Files
Sdf1_login/txt/RiskControlManager.txt
T

277 lines
9.4 KiB
Plaintext

package Sdf1_login;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public class RiskControlManager {
private final Main plugin;
// 玩家当前是否处于风控状态
private final Map<String, Boolean> riskActive = new ConcurrentHashMap<>();
// 密码验证连续错误次数
private final Map<String, Integer> passwordAttempts = new ConcurrentHashMap<>();
// 邮箱验证码连续错误次数
private final Map<String, Integer> emailAttempts = new ConcurrentHashMap<>();
// 风控触发时间(5分钟过期)
private final Map<String, Long> riskTriggerTime = new ConcurrentHashMap<>();
// 临时封禁到期时间
private final Map<String, Long> tempBanExpiry = new ConcurrentHashMap<>();
private static final long RISK_TIMEOUT = 5 * 60 * 1000L; // 5分钟
private static final int MAX_ATTEMPTS = 3;
private static final long TEMP_BAN_DURATION = 10 * 60 * 1000L; // 10分钟
public RiskControlManager(Main plugin) {
this.plugin = plugin;
}
/**
* 检查玩家是否被临时封禁
*/
public boolean isTempBanned(String name) {
Long expiry = tempBanExpiry.get(name);
if (expiry == null) return false;
if (System.currentTimeMillis() >= expiry) {
tempBanExpiry.remove(name);
return false;
}
return true;
}
/**
* 获取临时封禁剩余秒数
*/
public int getTempBanRemaining(String name) {
Long expiry = tempBanExpiry.get(name);
if (expiry == null) return 0;
return (int) ((expiry - System.currentTimeMillis()) / 1000);
}
/**
* 检查玩家是否需要风控验证
* 条件:IP变化 + 有背包数据(老玩家)
*/
public boolean shouldTrigger(String name, String currentIP) {
if (isTempBanned(name)) return false;
if (riskActive.getOrDefault(name, false)) return false;
// 获取上次记录的IP
String lastIP = (String) plugin.getDb().getField(name, "last_ip");
if (lastIP == null || lastIP.isEmpty()) return false; // 新玩家不触发
// IP相同不触发
if (lastIP.equals(currentIP)) return false;
// 有背包数据的老玩家 + IP变化 → 触发风控
String invData = (String) plugin.getDb().getField(name, "inventory_data");
if (invData != null && !invData.isEmpty()) {
return true;
}
return false;
}
/**
* 触发风控:冻结玩家,保存并隐藏背包
*/
public void trigger(Player p, String currentIP) {
String name = p.getName();
riskActive.put(name, true);
riskTriggerTime.put(name, System.currentTimeMillis());
passwordAttempts.put(name, 0);
emailAttempts.put(name, 0);
// 保存背包并冻结
plugin.hideInventory(p);
p.sendMessage("§c§l[风控系统] §f检测到异常登录!");
p.sendMessage("§e当前IP: §f" + currentIP);
p.sendMessage("§e请选择验证方式:");
p.sendMessage("§a1. §f密码验证 §7(输入: 密码 <你的密码>)");
p.sendMessage("§a2. §f邮箱验证 §7(输入: 邮箱)");
p.sendMessage("§7风控有效期: 5分钟");
}
/**
* 处理密码验证
*/
public boolean handlePassword(Player p, String input) {
String name = p.getName();
if (!riskActive.getOrDefault(name, false)) return false;
// 检查过期
if (isExpired(name)) { expireRisk(name); return false; }
int attempts = passwordAttempts.getOrDefault(name, 0) + 1;
passwordAttempts.put(name, attempts);
String salt = (String) plugin.getDb().getField(name, "password_salt");
if (salt == null) return false;
String hash = PasswordUtils.hash(input, salt);
if (plugin.getDb().checkPassword(name, hash)) {
passRisk(p);
return true;
}
if (attempts >= MAX_ATTEMPTS) {
failRisk(p);
} else {
p.sendMessage("§c§l[风控系统] §f密码错误!剩余 " + (MAX_ATTEMPTS - attempts) + " 次机会");
}
return true;
}
/**
* 发送邮箱验证码
*/
public void sendEmailCode(Player p) {
String name = p.getName();
if (!riskActive.getOrDefault(name, false)) return;
if (isExpired(name)) { expireRisk(name); return; }
String emailAddr = (String) plugin.getDb().getField(name, "email");
if (emailAddr == null || emailAddr.isEmpty()) {
p.sendMessage("§c§l[风控系统] §f你未绑定邮箱,无法使用邮箱验证");
return;
}
if (!plugin.getEmail().isConfigured()) {
p.sendMessage("§c§l[风控系统] §f邮件服务未配置,请使用密码验证");
return;
}
String code = PasswordUtils.generateVerifyCode();
plugin.getChatInput().setVerifyCode(p, code, "risk_control");
boolean sent = plugin.getEmail().sendVerifyCode(emailAddr, name, code);
if (sent) {
p.sendMessage("§a§l[风控系统] §f验证码已发送到 " + emailAddr);
p.sendMessage("§e请输入: 验证 <验证码>");
} else {
p.sendMessage("§c§l[风控系统] §f验证码发送失败,请使用密码验证");
}
}
/**
* 处理邮箱验证码
*/
public boolean handleEmailCode(Player p, String code) {
String name = p.getName();
if (!riskActive.getOrDefault(name, false)) return false;
if (isExpired(name)) { expireRisk(name); return false; }
int attempts = emailAttempts.getOrDefault(name, 0) + 1;
emailAttempts.put(name, attempts(p, code, "risk_control")) {
passRisk(p);
return true;
}
if (attempts >= MAX_ATTEMPTS) {
failRisk(p);
} else {
p.sendMessage("§c§l[风控系统] §f验证码);
if (plugin.getChatInput().checkVerifyCode错误!剩余 " + (MAX_ATTEMPTS - attempts) + " 次机会");
}
return true;
}
/**
* 验证通过:解冻,记录新IP
*/
private void passRisk(Player p) {
String name = p.getName();
riskActive.remove(name);
passwordAttempts.remove(name);
emailAttempts.remove(name);
riskTriggerTime.remove(name);
// 记录新IP
String ip = plugin.getPlayerIP(p);
if (ip != null) plugin.getDb().setField(name, "last_ip", ip);
plugin.recordIPLogin(p);
// 还原背包
plugin.restoreInventory(p);
plugin.getLoggedIn().add(name);
plugin.getDb().setLoggedIn(name, true);
p.sendMessage("§a§l[风控系统] §f验证通过!已解除风控限制");
p.playSound(p.getLocation(), org.bukkit.Sound.BLOCK_NOTE_BLOCK_CHIME, 1.0f, 0.79f);
}
/**
* 验证失败3次:临时封禁 + 邮件报警
*/
private void failRisk(Player p) {
String name = p.getName();
riskActive.remove(name);
passwordAttempts.remove(name);
emailAttempts.remove(name);
riskTriggerTime.remove(name);
// 临时封禁10分钟
tempBanExpiry.put(name, System.currentTimeMillis() + TEMP_BAN_DURATION);
p.sendMessage("§c§l[风控系统] §f验证失败次数过多,临时封禁10分钟");
// 邮件报警
String emailAddr = (String) plugin.getDb().getField(name, "email");
if (emailAddr != null && !emailAddr.isEmpty() && plugin.getEmail().isConfigured()) {
String ip = plugin.getPlayerIP(p);
plugin.getEmail().sendVerifyCode(emailAddr, name,
"[Sdf1_login] 风控报警: 玩家 " + name + " IP(" + ip + ")验证失败3次,已临时封禁10分钟");
}
// 踢出
p.kickPlayer("§c§l[风控系统] §f验证失败,临时封禁10分钟,请稍后再试");
}
/**
* 检查风控是否过期
*/
private boolean isExpired(String name) {
Long trigger = riskTriggerTime.get(name);
if (trigger == null) return true;
return System.currentTimeMillis() - trigger > RISK_TIMEOUT;
}
/**
* 风控过期处理
*/
private void expireRisk(String name) {
riskActive.remove(name);
passwordAttempts.remove(name);
emailAttempts.remove(name);
riskTriggerTime.remove(name);
}
/**
* 玩家是否处于风控状态
*/
public boolean isRiskActive(String name) {
return riskActive.getOrDefault(name, false);
}
/**
* 定时清理过期数据
*/
public void cleanup() {
long now = System.currentTimeMillis();
riskTriggerTime.entrySet().removeIf(e -> {
if (now - e.getValue() > RISK_TIMEOUT) {
riskActive.remove(e.getKey());
passwordAttempts.remove(e.getKey());
emailAttempts.remove(e.getKey());
return true;
}
return false;
});
tempBanExpiry.entrySet().removeIf(e -> now >= e.getValue());
}
}