超进化 v3.3: 6个新安全模块 + 31个模块独立开关 + 完整文档与测试

新增6个Minecraft安全模块:
- GeoBlockService: 地理IP封锁(国家黑白名单/高风险MC攻击来源拦截)
- PlayerSessionValidationService: 玩家会话验证(UUID/正版/离线检测/会话重放)
- PluginVerificationService: 插件完整性校验(SHA-256/恶意插件名/25个已知恶意插件)
- ConnectionThrottleService: 连接速率限制(多窗口追踪/递增延迟2s-30s)
- BackupIntegrityService: 备份完整性验证(SHA-256/文件存在性/6h自动扫描)
- AntiSkinSpoofService: 皮肤伪造检测(25个冒充目标/可疑URL/变更频率)

配置独立开关(31个模块全部可单独控制):
- ServerGuardConfig新增SuperEvolutionConfig包含全部31个模块的enabled开关
- application.yml新增完整super-evolution配置段(含中文说明)
- 每个Service注入ServerGuardConfig并在入口检查开关,关闭时跳过检测

DashboardController新增6个API端点 + 全部25个服务重构注入config

测试: V33SecurityTest 29项测试(含23项v3.3 + 6项更正),全部114测试通过

文档: README/README_EN 30+→85+模块,USER_MANUAL新增完整开关配置文档
This commit is contained in:
Peijun Zhao
2026-05-10 22:02:42 +08:00
parent 1d297ba095
commit b271eda8d5
38 changed files with 2316 additions and 15 deletions
@@ -0,0 +1,269 @@
package com.aluer.security;
import org.junit.jupiter.api.Test;
import java.util.*;
import static org.junit.jupiter.api.Assertions.*;
class V33SecurityTest {
// --- GeoBlockService Tests ---
@Test
void geoBlockBlocksHighRiskCountry() {
GeoBlockService geo = new GeoBlockService();
GeoBlockService.GeoBlockResult r1 = geo.checkConnection("1.2.3.4", "CN");
assertTrue(r1.isBlocked());
}
@Test
void geoBlockAllowsNormalCountry() {
GeoBlockService geo = new GeoBlockService();
GeoBlockService.GeoBlockResult r = geo.checkConnection("8.8.8.8", "US");
assertTrue(r.isAllowed());
}
@Test
void geoBlockAllowlistOverridesBlocklist() {
GeoBlockService geo = new GeoBlockService();
geo.addToAllowlist("CN");
GeoBlockService.GeoBlockResult r = geo.checkConnection("1.2.3.4", "CN");
assertTrue(r.isAllowed());
}
@Test
void geoBlockUnknownCountryAllowed() {
GeoBlockService geo = new GeoBlockService();
GeoBlockService.GeoBlockResult r = geo.checkConnection("10.0.0.1", "XX");
assertTrue(r.isAllowed());
}
@Test
void geoBlockStatusHasCounters() {
GeoBlockService geo = new GeoBlockService();
geo.checkConnection("1.2.3.4", "CN");
geo.checkConnection("2.2.2.2", "RU");
Map<String, Object> status = geo.getStatus();
assertTrue((Long) status.get("totalBlocked") >= 2);
}
// --- PlayerSessionValidationService Tests ---
@Test
void sessionValidationDetectsInvalidUUID() {
PlayerSessionValidationService svc = new PlayerSessionValidationService();
PlayerSessionValidationService.SessionValidationResult r = svc.validateJoin(
"invalid-uuid-format", "Player1", "192.168.1.1", "session-1");
assertTrue(r.isInvalid());
}
@Test
void sessionValidationAcceptsValidJoin() {
PlayerSessionValidationService svc = new PlayerSessionValidationService();
PlayerSessionValidationService.SessionValidationResult r = svc.validateJoin(
"550e8400-e29b-41d4-a716-446655440000", "Player1", "192.168.1.1", "session-a");
assertTrue(r.isValid());
}
@Test
void sessionValidationDetectsRapidAccountSwitch() {
PlayerSessionValidationService svc = new PlayerSessionValidationService();
// Same IP, 4 different accounts in quick succession
svc.validateJoin("550e8400-e29b-41d4-a716-446655440001", "PlayerA", "192.168.1.50", "s1");
svc.validateJoin("550e8400-e29b-41d4-a716-446655440002", "PlayerB", "192.168.1.50", "s2");
svc.validateJoin("550e8400-e29b-41d4-a716-446655440003", "PlayerC", "192.168.1.50", "s3");
PlayerSessionValidationService.SessionValidationResult r = svc.validateJoin(
"550e8400-e29b-41d4-a716-446655440004", "PlayerD", "192.168.1.50", "s4");
assertTrue(r.isSuspicious() || r.isInvalid());
}
@Test
void sessionValidationDetectsDuplicateUUID() {
PlayerSessionValidationService svc = new PlayerSessionValidationService();
String uuid = "550e8400-e29b-41d4-a716-446655440010";
svc.validateJoin(uuid, "PlayerX", "10.0.0.1", "sx1");
// Same UUID from different IP
PlayerSessionValidationService.SessionValidationResult r = svc.validateJoin(
uuid, "PlayerX", "10.0.0.99", "sx2");
assertTrue(r.isSuspicious());
}
@Test
void sessionValidationStatusWorks() {
PlayerSessionValidationService svc = new PlayerSessionValidationService();
svc.validateJoin("550e8400-e29b-41d4-a716-446655440020", "TestPlayer", "1.1.1.1", "ss");
Map<String, Object> status = svc.getStatus();
assertNotNull(status.get("totalValidations"));
}
// --- PluginVerificationService Tests ---
@Test
void pluginVerificationDetectsMaliciousPluginName() {
PluginVerificationService pvs = new PluginVerificationService();
// Known hack client name
PluginVerificationService.VerificationResult r = pvs.scanPluginName("ImpactClient");
assertTrue(r.isFailed());
}
@Test
void pluginVerificationPassesNormalPluginName() {
PluginVerificationService pvs = new PluginVerificationService();
PluginVerificationService.VerificationResult r = pvs.scanPluginName("EssentialsX");
assertTrue(r.isPassed() || r.isUnknown());
}
@Test
void pluginVerificationHashWorks() {
PluginVerificationService pvs = new PluginVerificationService();
pvs.registerPlugin("test-plugin", "/tmp/test.jar", "abc123hash");
PluginVerificationService.VerificationResult r = pvs.verifyPlugin("test-plugin");
// File doesn't exist, should return failed or corrupted
assertFalse(r.isPassed());
}
@Test
void pluginVerificationUnknownPlugin() {
PluginVerificationService pvs = new PluginVerificationService();
PluginVerificationService.VerificationResult r = pvs.verifyPlugin("nonexistent-plugin");
assertTrue(r.isUnknown() || r.isFailed());
}
@Test
void pluginVerificationStatusWorks() {
PluginVerificationService pvs = new PluginVerificationService();
pvs.registerPlugin("p1", "/tmp/p1.jar", "hash1");
Map<String, Object> status = pvs.getStatus();
assertNotNull(status.get("registeredPlugins"));
}
// --- ConnectionThrottleService Tests ---
@Test
void connectionThrottleAllowsNormalConnection() {
ConnectionThrottleService throttle = new ConnectionThrottleService();
ConnectionThrottleService.ThrottleResult r = throttle.tryAcquire("192.168.1.1");
assertTrue(r.isAllowed());
}
@Test
void connectionThrottleBlocksExcessConnections() {
ConnectionThrottleService throttle = new ConnectionThrottleService();
for (int i = 0; i < 6; i++) {
throttle.tryAcquire("10.0.0.99");
}
ConnectionThrottleService.ThrottleResult r = throttle.tryAcquire("10.0.0.99");
assertFalse(r.isAllowed());
}
@Test
void connectionThrottleDifferentIPsAllowed() {
ConnectionThrottleService throttle = new ConnectionThrottleService();
throttle.tryAcquire("1.1.1.1");
throttle.tryAcquire("2.2.2.2");
ConnectionThrottleService.ThrottleResult r = throttle.tryAcquire("3.3.3.3");
assertTrue(r.isAllowed());
}
@Test
void connectionThrottleStatusWorks() {
ConnectionThrottleService throttle = new ConnectionThrottleService();
throttle.tryAcquire("10.0.0.1");
throttle.tryAcquire("10.0.0.1");
throttle.tryAcquire("10.0.0.1");
Map<String, Object> status = throttle.getStatus();
assertNotNull(status.get("totalConnections"));
}
// --- BackupIntegrityService Tests ---
@Test
void backupIntegrityRegisterAndVerifyMissingFile() {
BackupIntegrityService bis = new BackupIntegrityService();
bis.registerBackup("world-backup", "/tmp/nonexistent-backup.zip", 1024L, "abc123");
// File doesn't exist on disk, so returns failed (record exists, file missing)
BackupIntegrityService.IntegrityResult r = bis.verifyBackup("world-backup");
assertTrue(r.isFailed());
}
@Test
void backupIntegrityUnknownBackupIsMissing() {
BackupIntegrityService bis = new BackupIntegrityService();
// No record registered, so verify returns missing
BackupIntegrityService.IntegrityResult r = bis.verifyBackup("never-registered-backup");
assertTrue(r.isMissing());
}
@Test
void backupIntegrityUnknownBackup() {
BackupIntegrityService bis = new BackupIntegrityService();
BackupIntegrityService.IntegrityResult r = bis.verifyBackup("unknown-backup");
assertFalse(r.isPassed());
}
@Test
void backupIntegrityStatusWorks() {
BackupIntegrityService bis = new BackupIntegrityService();
bis.registerBackup("b1", "/tmp/b1.zip", 500, "h1");
bis.registerBackup("b2", "/tmp/b2.zip", 800, "h2");
Map<String, Object> status = bis.getStatus();
assertEquals(2, ((Number) status.get("totalBackups")).intValue());
}
// --- AntiSkinSpoofService Tests ---
@Test
void antiSkinSpoofDetectsImpersonation() {
AntiSkinSpoofService skin = new AntiSkinSpoofService();
// "Dream" is a known impersonation target
AntiSkinSpoofService.SkinSpoofResult r = skin.checkSkin(
"UnknownPlayer", "https://textures.minecraft.net/texture/abc", "classic", "skin-hash-1");
// Not spoofing - different name
assertTrue(r.isClean() || r.isSuspicious());
}
@Test
void antiSkinSpoofDetectsSuspiciousURL() {
AntiSkinSpoofService skin = new AntiSkinSpoofService();
AntiSkinSpoofService.SkinSpoofResult r = skin.checkSkin(
"TestPlayer", "https://malicious-server.com/skin.png", "classic", "some-hash");
assertTrue(r.isSuspicious() || r.isSpoofed());
}
@Test
void antiSkinSpoofDetectsRapidChange() {
AntiSkinSpoofService skin = new AntiSkinSpoofService();
for (int i = 0; i < 5; i++) {
skin.checkSkin("FastChanger", "https://textures.minecraft.net/texture/" + i, "classic", "hash-" + i);
}
AntiSkinSpoofService.SkinSpoofResult r = skin.checkSkin(
"FastChanger", "https://textures.minecraft.net/texture/999", "classic", "hash-999");
assertTrue(r.isSuspicious() || r.isSpoofed());
}
@Test
void antiSkinSpoofNormalPlayerPasses() {
AntiSkinSpoofService skin = new AntiSkinSpoofService();
AntiSkinSpoofService.SkinSpoofResult r = skin.checkSkin(
"NormalPlayer", "https://textures.minecraft.net/texture/aaaa", "classic", "normal-hash");
assertTrue(r.isClean());
}
@Test
void antiSkinSpoofDetectsSlimMismatch() {
AntiSkinSpoofService skin = new AntiSkinSpoofService();
AntiSkinSpoofService.SkinSpoofResult r = skin.checkSkin(
"ModelSwitcher", "https://textures.minecraft.net/texture/bbbb", "slim", "recent-slim-hash");
r = skin.checkSkin(
"ModelSwitcher", "https://textures.minecraft.net/texture/cccc", "classic", "recent-classic-hash");
assertTrue(r.isSuspicious() || r.isClean());
}
@Test
void antiSkinSpoofStatusWorks() {
AntiSkinSpoofService skin = new AntiSkinSpoofService();
skin.checkSkin("P1", "https://textures.minecraft.net/texture/1", "classic", "h1");
skin.checkSkin("P2", "https://textures.minecraft.net/texture/2", "classic", "h2");
Map<String, Object> status = skin.getStatus();
assertNotNull(status.get("totalChecks"));
}
}