Skip to content

Commit 93c30e3

Browse files
committed
feat(security): 实现IP访问频率控制和验证码验证机制
- 添加IP首次访问时间缓存和最近验证码通过时间缓存 - 实现2小时内首次访问需验证码的验证逻辑 - 实现每3次访问触发一次验证码的计数机制 - 集成极验验证码验证功能 - 验证通过后清理黑名单和访问计数记录 - 验证失败时将IP加入黑名单并跳转验证页面
1 parent 74f6443 commit 93c30e3

File tree

2 files changed

+49
-6
lines changed

2 files changed

+49
-6
lines changed

src/main/java/org/b3log/symphony/processor/CaptchaProcessor.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,8 +124,10 @@ public void validateCaptcha(final RequestContext context) {
124124
final String captcha = requestJSONObject.optString(CaptchaProcessor.CAPTCHA);
125125
final String ip = Requests.getRemoteAddr(context.getRequest());
126126
if (CaptchaProcessor.jiyan(captcha)) {
127+
// 验证通过:清理黑名单和访问计数,并记录最近一次通过验证码的时间
127128
AnonymousViewCheckMidware.ipBlacklistCache.invalidate(ip);
128129
AnonymousViewCheckMidware.ipVisitCountCache.invalidate(ip);
130+
AnonymousViewCheckMidware.ipLastCaptchaPassTimeCache.put(ip, System.currentTimeMillis());
129131
context.renderJSON(StatusCodes.SUCC);
130132
System.out.println(ip + " 验证成功");
131133
} else {

src/main/java/org/b3log/symphony/processor/middleware/AnonymousViewCheckMidware.java

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,22 @@ public class AnonymousViewCheckMidware {
103103
// String类型的白名单
104104
public static final Set<String> whiteList = new HashSet<>();
105105

106+
/**
107+
* IP 首次访问时间缓存(用于 2 小时内首次访问需要验证码)
108+
*/
109+
public static final Cache<String, Long> ipFirstVisitTimeCache = Caffeine.newBuilder()
110+
.expireAfterWrite(2, java.util.concurrent.TimeUnit.HOURS)
111+
.maximumSize(100000)
112+
.build();
113+
114+
/**
115+
* IP 最近一次通过验证码的时间(通过后重置计数逻辑时使用)
116+
*/
117+
public static final Cache<String, Long> ipLastCaptchaPassTimeCache = Caffeine.newBuilder()
118+
.expireAfterWrite(2, java.util.concurrent.TimeUnit.HOURS)
119+
.maximumSize(100000)
120+
.build();
121+
106122
private static Cookie getCookie(final Request request, final String name) {
107123
final Set<Cookie> cookies = request.getCookies();
108124
if (cookies.isEmpty()) {
@@ -144,22 +160,47 @@ public void handle(final RequestContext context) {
144160
} else {
145161
final String ua = Headers.getHeader(request, Common.USER_AGENT, "");
146162
if (!isSearchEngineBot(ua)) {
147-
// 计数逻辑
163+
// 初始化首次访问时间(用于 2 小时内首次访问逻辑)
164+
Long firstVisitTime = ipFirstVisitTimeCache.getIfPresent(ip);
165+
long now = System.currentTimeMillis();
166+
if (firstVisitTime == null) {
167+
firstVisitTime = now;
168+
ipFirstVisitTimeCache.put(ip, firstVisitTime);
169+
}
170+
171+
// 计数逻辑(包含两种策略:2 小时内首次访问一次验证码,其后每 3 次一次)
148172
Integer count = ipVisitCountCache.getIfPresent(ip);
149173
if (count == null) count = 0;
150174
count++;
151175
ipVisitCountCache.put(ip, count);
152176
System.out.println(ip + " 访问计数:" + count + " " + context.requestURI());
153-
if (count >= 300) {
177+
178+
if (count >= 20) {
154179
String result = Execs.exec(new String[]{"sh", "-c", "ipset add fishpi " + ip}, 1000 * 3);
155180
System.out.println(ip + " 已封禁");
156181
}
157-
if (count >= 5) {
158-
// 进入黑名单
182+
183+
// 判断是否需要进入验证码流程
184+
boolean needCaptcha = false;
185+
186+
// 2 小时内首次访问:第一次就需要验证码
187+
Long lastPassTime = ipLastCaptchaPassTimeCache.getIfPresent(ip);
188+
if (lastPassTime == null && (now - firstVisitTime) <= 2L * 60L * 60L * 1000L) {
189+
if (count == 1) {
190+
needCaptcha = true;
191+
}
192+
}
193+
194+
// 之后每访问 3 次需要一次验证码
195+
if (!needCaptcha && count % 3 == 0) {
196+
needCaptcha = true;
197+
}
198+
199+
if (needCaptcha) {
200+
// 进入黑名单并跳转验证码页面
159201
ipBlacklistCache.put(ip, true);
160-
// 跳转到验证码页面
161202
context.sendRedirect("/test");
162-
System.out.println(ip + " 进入黑名单");
203+
System.out.println(ip + " 触发验证码,进入黑名单");
163204
return;
164205
}
165206
}

0 commit comments

Comments
 (0)