Skip to content

Commit e399423

Browse files
feat(auth): 添加登录失败限制功能以增强安全性
新增登录尝试次数限制和账号锁定机制,防止暴力破解攻击。 在 AdminAuthService 中集成 LoginAttemptService,验证用户登录时检查 是否超过最大失败次数,若超出则锁定登录请求。支持通过配置文件 启用/禁用该功能,并设置最大尝试次数与锁定时间。 同时完善了白名单管理器的按名称删除玩家逻辑,优化日志记录格式, 并自动更新配置文件以包含新引入的配置项。
1 parent cb3c96f commit e399423

8 files changed

Lines changed: 737 additions & 8 deletions

File tree

CONFIG_UPDATE_GUIDE.md

Lines changed: 299 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,299 @@
1+
# 📝 配置文件自动更新说明
2+
3+
## ❓ 配置文件会自动更新吗?
4+
5+
**答案**: ⚠️ **部分自动,需要注意**
6+
7+
---
8+
9+
## 🔄 当前配置更新机制
10+
11+
### 1. 首次启动 (插件安装)
12+
13+
**完全自动**:
14+
```java
15+
// ConfigManager.java
16+
private void loadConfig() {
17+
plugin.saveDefaultConfig(); // 从 JAR 复制 config.yml 到插件目录
18+
plugin.reloadConfig();
19+
this.config = plugin.getConfig();
20+
}
21+
```
22+
23+
**效果**:
24+
- 如果 `plugins/ConvenientAccess/config.yml` 不存在
25+
- 自动从 JAR 包内的 `resources/config.yml` 复制
26+
- ✅ 包含所有最新配置项
27+
28+
---
29+
30+
### 2. 插件更新 (升级版本)
31+
32+
⚠️ **不会自动更新**:
33+
- Bukkit/Spigot 默认行为: `saveDefaultConfig()` 只在文件不存在时才复制
34+
- 如果 `config.yml` 已存在,**不会覆盖**
35+
- ❌ 新增的配置项**不会自动添加**
36+
37+
**问题场景**:
38+
```yaml
39+
# v0.4.0 的 config.yml (旧版本)
40+
api:
41+
auth:
42+
enabled: true
43+
admin-password: "xxx"
44+
api-token: "xxx"
45+
# ❌ 缺少新增的 login-attempt-limit 配置
46+
47+
# v0.5.0 的 config.yml (新版本) - 不会自动合并
48+
api:
49+
auth:
50+
enabled: true
51+
admin-password: "xxx"
52+
api-token: "xxx"
53+
login-attempt-limit: # ✅ 新增配置
54+
enabled: true
55+
max-attempts: 5
56+
lock-duration-minutes: 15
57+
```
58+
59+
---
60+
61+
## 🛠️ 解决方案
62+
63+
### 方案1: 手动添加新配置项 (推荐)
64+
65+
**用户需要手动编辑 `config.yml` 添加**:
66+
67+
```yaml
68+
api:
69+
auth:
70+
# ... 现有配置 ...
71+
72+
# 👇 手动添加这部分
73+
login-attempt-limit:
74+
enabled: true
75+
max-attempts: 5
76+
lock-duration-minutes: 15
77+
```
78+
79+
**优点**:
80+
- ✅ 保留现有配置
81+
- ✅ 不会丢失自定义设置
82+
83+
**缺点**:
84+
- ❌ 需要用户手动操作
85+
- ❌ 容易遗漏
86+
87+
---
88+
89+
### 方案2: 代码提供默认值 (已实现) ✅
90+
91+
**当前实现**:
92+
```java
93+
// 读取配置时提供默认值
94+
boolean attemptLimitEnabled = config.getBoolean("api.auth.login-attempt-limit.enabled", true);
95+
int maxAttempts = config.getInt("api.auth.login-attempt-limit.max-attempts", 5);
96+
int lockDuration = config.getInt("api.auth.login-attempt-limit.lock-duration-minutes", 15);
97+
```
98+
99+
**效果**:
100+
- ✅ 即使配置文件中没有该项,代码也能正常工作
101+
- ✅ 使用硬编码的默认值
102+
- ⚠️ 用户无法通过查看配置文件知道有这个功能
103+
104+
---
105+
106+
### 方案3: 自动合并配置 (建议实施) 🎯
107+
108+
**实现思路**:
109+
```java
110+
public class ConfigManager {
111+
private void loadConfig() {
112+
plugin.saveDefaultConfig();
113+
plugin.reloadConfig();
114+
this.config = plugin.getConfig();
115+
116+
// 🆕 自动添加缺失的配置项
117+
autoUpdateConfig();
118+
}
119+
120+
private void autoUpdateConfig() {
121+
boolean updated = false;
122+
123+
// 检查并添加 login-attempt-limit 配置
124+
if (!config.contains("api.auth.login-attempt-limit.enabled")) {
125+
config.set("api.auth.login-attempt-limit.enabled", true);
126+
updated = true;
127+
}
128+
if (!config.contains("api.auth.login-attempt-limit.max-attempts")) {
129+
config.set("api.auth.login-attempt-limit.max-attempts", 5);
130+
updated = true;
131+
}
132+
if (!config.contains("api.auth.login-attempt-limit.lock-duration-minutes")) {
133+
config.set("api.auth.login-attempt-limit.lock-duration-minutes", 15);
134+
updated = true;
135+
}
136+
137+
if (updated) {
138+
plugin.saveConfig();
139+
logger.info("配置文件已自动更新,添加了新的配置项");
140+
}
141+
}
142+
}
143+
```
144+
145+
**优点**:
146+
- ✅ 完全自动,无需用户干预
147+
- ✅ 保留现有配置
148+
- ✅ 自动添加新配置项
149+
150+
**缺点**:
151+
- ⚠️ 需要为每个新配置项编写检查代码
152+
- ⚠️ 版本升级时需要维护
153+
154+
---
155+
156+
### 方案4: 配置迁移系统 (最佳但复杂)
157+
158+
**版本化配置管理**:
159+
```java
160+
public class ConfigMigrator {
161+
private static final String CONFIG_VERSION_KEY = "config-version";
162+
private static final int CURRENT_VERSION = 2;
163+
164+
public void migrate(FileConfiguration config) {
165+
int version = config.getInt(CONFIG_VERSION_KEY, 1);
166+
167+
if (version < 2) {
168+
// 从版本1迁移到版本2
169+
migrateV1toV2(config);
170+
}
171+
172+
// 更新版本号
173+
config.set(CONFIG_VERSION_KEY, CURRENT_VERSION);
174+
}
175+
176+
private void migrateV1toV2(FileConfiguration config) {
177+
// 添加 login-attempt-limit 配置
178+
if (!config.contains("api.auth.login-attempt-limit")) {
179+
config.set("api.auth.login-attempt-limit.enabled", true);
180+
config.set("api.auth.login-attempt-limit.max-attempts", 5);
181+
config.set("api.auth.login-attempt-limit.lock-duration-minutes", 15);
182+
logger.info("配置已从 v1 迁移到 v2: 添加登录失败限制功能");
183+
}
184+
}
185+
}
186+
```
187+
188+
---
189+
190+
## 📋 当前状态
191+
192+
### 已有新配置项 (v0.5.0)
193+
194+
```yaml
195+
api:
196+
auth:
197+
login-attempt-limit:
198+
enabled: true
199+
max-attempts: 5
200+
lock-duration-minutes: 15
201+
```
202+
203+
### 当前行为
204+
205+
1. **首次安装**: ✅ 自动包含新配置
206+
2. **从旧版本升级**: ❌ 需要手动添加
207+
208+
### 代码行为
209+
210+
```java
211+
// 即使配置文件中没有,代码也会使用默认值
212+
boolean attemptLimitEnabled = config.getBoolean(
213+
"api.auth.login-attempt-limit.enabled",
214+
true // 👈 默认启用
215+
);
216+
```
217+
218+
---
219+
220+
## 🎯 推荐的升级步骤
221+
222+
### 对于用户
223+
224+
1. **备份现有配置**:
225+
```bash
226+
cp plugins/ConvenientAccess/config.yml plugins/ConvenientAccess/config.yml.backup
227+
```
228+
229+
2. **查看新配置示例**:
230+
- 解压 JAR 包查看 `resources/config.yml`
231+
- 或查看 GitHub/文档
232+
233+
3. **手动添加新配置项**:
234+
```yaml
235+
# 在 api.auth 部分添加
236+
login-attempt-limit:
237+
enabled: true
238+
max-attempts: 5
239+
lock-duration-minutes: 15
240+
```
241+
242+
4. **重载插件**:
243+
```bash
244+
/convenientaccess reload
245+
```
246+
247+
### 对于开发者 (TODO)
248+
249+
建议实施**方案3: 自动合并配置**:
250+
251+
1. 在 `ConfigManager.java` 添加 `autoUpdateConfig()` 方法
252+
2. 在 `loadConfig()` 中调用
253+
3. 检查并添加缺失的配置项
254+
4. 保存配置文件
255+
256+
---
257+
258+
## ⚠️ 注意事项
259+
260+
### 1. 配置文件格式
261+
- YAML 格式严格要求**缩进**
262+
- 使用**空格**缩进,不要用 Tab
263+
- 每级缩进 **2个空格**
264+
265+
### 2. 重载配置
266+
```bash
267+
# 重载配置命令
268+
/convenientaccess reload
269+
270+
# 或重启插件
271+
/reload confirm # 不推荐,可能导致内存泄漏
272+
```
273+
274+
### 3. 配置优先级
275+
1. 配置文件中的值 (最高优先级)
276+
2. 代码中的默认值 (配置缺失时使用)
277+
278+
---
279+
280+
## 📚 相关文件
281+
282+
- 📄 `/src/main/resources/config.yml` - 默认配置模板
283+
- 📄 `ConfigManager.java` - 配置管理器
284+
- 📄 `plugins/ConvenientAccess/config.yml` - 实际运行配置
285+
286+
---
287+
288+
## 🔄 未来改进计划
289+
290+
- [ ] 实现配置自动合并功能
291+
- [ ] 添加配置版本管理
292+
- [ ] 提供配置迁移向导
293+
- [ ] 生成配置变更日志
294+
- [ ] 支持配置热重载
295+
296+
---
297+
298+
**最后更新**: 2025年10月3日
299+
**适用版本**: v0.5.0+

src/main/java/com/xaoxiao/convenientaccess/ConvenientAccessPlugin.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,19 @@ public void onEnable() {
7575

7676
// 初始化管理员认证服务
7777
try {
78+
// 创建登录失败限制服务
79+
com.xaoxiao.convenientaccess.auth.LoginAttemptService loginAttemptService =
80+
new com.xaoxiao.convenientaccess.auth.LoginAttemptService(
81+
configManager.getLoginMaxAttempts(),
82+
configManager.getLoginLockDurationMinutes(),
83+
configManager.isLoginAttemptLimitEnabled()
84+
);
85+
7886
adminAuthService = new AdminAuthService(
7987
whitelistSystem.getDatabaseManager(),
8088
whitelistSystem.getRegistrationTokenManager(),
81-
configManager.getAdminPassword()
89+
configManager.getAdminPassword(),
90+
loginAttemptService
8291
);
8392
// 确保超级管理员账户存在
8493
adminAuthService.ensureSuperAdminExists();

src/main/java/com/xaoxiao/convenientaccess/api/WhitelistApiController.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -287,13 +287,13 @@ public void handleRemovePlayerByName(HttpServletRequest request, HttpServletResp
287287
WhitelistEntry player = playerOpt.get();
288288
final String playerUuid = player.getUuid();
289289

290-
// 如果有 UUID,使用 UUID 删除;否则也通过 name 删除 (WhitelistManager支持)
290+
// 如果有 UUID,使用 UUID 删除;否则通过 name 删除
291291
CompletableFuture<Boolean> removeFuture;
292-
if (playerUuid != null && !playerUuid.isEmpty()) {
292+
if (playerUuid != null && !playerUuid.isEmpty() && !playerUuid.equals("null")) {
293293
removeFuture = whitelistManager.removePlayer(playerUuid);
294294
} else {
295-
// UUID 为 null,仍然可以删除(按名称删除)
296-
removeFuture = whitelistManager.removePlayer(name);
295+
// UUID 为 null 或 "null" 字符串,使用名称删除
296+
removeFuture = whitelistManager.removePlayerByName(name);
297297
}
298298

299299
return removeFuture.thenApply(success -> {

0 commit comments

Comments
 (0)