1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.codelibs.fess.helper;
17
18 import java.util.Set;
19 import java.util.concurrent.TimeUnit;
20 import java.util.concurrent.atomic.AtomicLong;
21
22 import org.apache.logging.log4j.LogManager;
23 import org.apache.logging.log4j.Logger;
24 import org.codelibs.core.lang.StringUtil;
25 import org.codelibs.fess.mylasta.direction.FessConfig;
26 import org.codelibs.fess.util.ComponentUtil;
27
28 import com.google.common.cache.Cache;
29 import com.google.common.cache.CacheBuilder;
30
31 import jakarta.annotation.PostConstruct;
32 import jakarta.servlet.http.HttpServletRequest;
33
34
35
36
37
38
39 public class RateLimitHelper {
40
41 private static final Logger logger = LogManager.getLogger(RateLimitHelper.class);
42
43
44
45
46
47 protected Cache<String, AtomicLong> requestCounters;
48
49
50
51
52
53 protected Cache<String, Boolean> blockedIps;
54
55
56
57
58 public RateLimitHelper() {
59
60 }
61
62
63
64
65 @PostConstruct
66 public void init() {
67 if (logger.isDebugEnabled()) {
68 logger.debug("Initializing {}", this.getClass().getSimpleName());
69 }
70
71 final FessConfig fessConfig = ComponentUtil.getFessConfig();
72 final long windowMs = fessConfig.getRateLimitWindowMsAsInteger().longValue();
73 final long blockDurationMs = fessConfig.getRateLimitBlockDurationMsAsInteger().longValue();
74
75 requestCounters = CacheBuilder.newBuilder().expireAfterWrite(windowMs, TimeUnit.MILLISECONDS).maximumSize(10000).build();
76
77 blockedIps = CacheBuilder.newBuilder().expireAfterWrite(blockDurationMs, TimeUnit.MILLISECONDS).maximumSize(10000).build();
78
79 if (logger.isInfoEnabled()) {
80 logger.info("RateLimitHelper initialized: windowMs={}, blockDurationMs={}", windowMs, blockDurationMs);
81 }
82 }
83
84
85
86
87
88 public boolean isEnabled() {
89 return ComponentUtil.getFessConfig().isRateLimitEnabled();
90 }
91
92
93
94
95
96
97
98 public String getClientIp(final HttpServletRequest request) {
99 final String remoteAddr = request.getRemoteAddr();
100
101
102 if (isTrustedProxy(remoteAddr)) {
103 final String xForwardedFor = request.getHeader("X-Forwarded-For");
104 if (StringUtil.isNotBlank(xForwardedFor)) {
105 final String clientIp = xForwardedFor.split(",")[0].trim();
106 if (logger.isDebugEnabled()) {
107 logger.debug("Client IP from X-Forwarded-For: clientIp={}, remoteAddr={}", clientIp, remoteAddr);
108 }
109 return clientIp;
110 }
111 final String xRealIp = request.getHeader("X-Real-IP");
112 if (StringUtil.isNotBlank(xRealIp)) {
113 final String clientIp = xRealIp.trim();
114 if (logger.isDebugEnabled()) {
115 logger.debug("Client IP from X-Real-IP: clientIp={}, remoteAddr={}", clientIp, remoteAddr);
116 }
117 return clientIp;
118 }
119 }
120
121 if (logger.isDebugEnabled()) {
122 logger.debug("Client IP from remoteAddr: ip={}", remoteAddr);
123 }
124 return remoteAddr;
125 }
126
127
128
129
130
131
132 protected boolean isTrustedProxy(final String ip) {
133 final Set<String> trustedProxies = ComponentUtil.getFessConfig().getRateLimitTrustedProxiesAsSet();
134 final boolean trusted = trustedProxies.contains(ip);
135 if (logger.isDebugEnabled() && trusted) {
136 logger.debug("Trusted proxy detected: ip={}", ip);
137 }
138 return trusted;
139 }
140
141
142
143
144
145
146 public boolean isWhitelisted(final String ip) {
147 final Set<String> whitelist = ComponentUtil.getFessConfig().getRateLimitWhitelistIpsAsSet();
148 final boolean whitelisted = whitelist.contains(ip);
149 if (logger.isDebugEnabled() && whitelisted) {
150 logger.debug("Whitelisted IP: ip={}", ip);
151 }
152 return whitelisted;
153 }
154
155
156
157
158
159
160 public boolean isBlocked(final String ip) {
161 if (isWhitelisted(ip)) {
162 return false;
163 }
164
165
166 final Set<String> blockedIpSet = ComponentUtil.getFessConfig().getRateLimitBlockedIpsAsSet();
167 if (blockedIpSet.contains(ip)) {
168 if (logger.isDebugEnabled()) {
169 logger.debug("IP in static block list: ip={}", ip);
170 }
171 return true;
172 }
173
174
175 final Boolean blocked = blockedIps.getIfPresent(ip);
176 if (blocked != null) {
177 if (logger.isDebugEnabled()) {
178 logger.debug("IP dynamically blocked: ip={}", ip);
179 }
180 return true;
181 }
182
183 return false;
184 }
185
186
187
188
189
190
191 public boolean allowRequest(final String ip) {
192 if (isWhitelisted(ip)) {
193 return true;
194 }
195
196 final FessConfig fessConfig = ComponentUtil.getFessConfig();
197 final int maxRequests = fessConfig.getRateLimitRequestsPerWindowAsInteger();
198
199 AtomicLong counter = requestCounters.getIfPresent(ip);
200 if (counter == null) {
201 counter = new AtomicLong(0);
202 requestCounters.put(ip, counter);
203 }
204
205 final long count = counter.incrementAndGet();
206
207 if (logger.isDebugEnabled()) {
208 logger.debug("Request count: ip={}, count={}, max={}", ip, count, maxRequests);
209 }
210
211 if (count > maxRequests) {
212 blockedIps.put(ip, Boolean.TRUE);
213 logger.info("Rate limit exceeded, IP blocked: ip={}, requestCount={}", ip, count);
214 return false;
215 }
216
217 return true;
218 }
219
220
221
222
223
224 public int getRetryAfterSeconds() {
225 return ComponentUtil.getFessConfig().getRateLimitRetryAfterSecondsAsInteger();
226 }
227
228
229
230
231
232
233
234 public void blockIp(final String ip, final long durationMs) {
235 blockedIps.put(ip, Boolean.TRUE);
236 logger.info("IP manually blocked: ip={}", ip);
237 }
238
239
240
241
242
243 public void unblockIp(final String ip) {
244 blockedIps.invalidate(ip);
245 logger.info("IP unblocked: ip={}", ip);
246 }
247
248
249
250
251
252
253 public int getBlockedIpCount() {
254 blockedIps.cleanUp();
255 return (int) blockedIps.size();
256 }
257
258
259
260
261
262
263 public int getTrackedIpCount() {
264 requestCounters.cleanUp();
265 return (int) requestCounters.size();
266 }
267
268
269
270
271
272
273 public void cleanup() {
274 requestCounters.cleanUp();
275 blockedIps.cleanUp();
276 if (logger.isDebugEnabled()) {
277 logger.debug("Cache cleanup completed: trackedIps={}, blockedIps={}", requestCounters.size(), blockedIps.size());
278 }
279 }
280 }