View Javadoc
1   /*
2    * Copyright 2012-2025 CodeLibs Project and the Others.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
13   * either express or implied. See the License for the specific language
14   * governing permissions and limitations under the License.
15   */
16  package org.codelibs.fess.filter;
17  
18  import java.io.IOException;
19  
20  import org.apache.logging.log4j.LogManager;
21  import org.apache.logging.log4j.Logger;
22  import org.codelibs.fess.helper.RateLimitHelper;
23  import org.codelibs.fess.util.ComponentUtil;
24  
25  import jakarta.servlet.Filter;
26  import jakarta.servlet.FilterChain;
27  import jakarta.servlet.ServletException;
28  import jakarta.servlet.ServletRequest;
29  import jakarta.servlet.ServletResponse;
30  import jakarta.servlet.http.HttpServletRequest;
31  import jakarta.servlet.http.HttpServletResponse;
32  
33  /**
34   * Filter for rate limiting to protect against bot attacks and excessive requests.
35   * Tracks request counts per IP address and blocks excessive requests based on configurable thresholds.
36   */
37  public class RateLimitFilter implements Filter {
38  
39      private static final Logger logger = LogManager.getLogger(RateLimitFilter.class);
40  
41      /**
42       * Creates a new instance of RateLimitFilter.
43       */
44      public RateLimitFilter() {
45          // Default constructor
46      }
47  
48      @Override
49      public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain)
50              throws IOException, ServletException {
51          if (!ComponentUtil.available()) {
52              chain.doFilter(request, response);
53              return;
54          }
55  
56          final RateLimitHelper rateLimitHelper = ComponentUtil.getRateLimitHelper();
57  
58          if (!rateLimitHelper.isEnabled()) {
59              chain.doFilter(request, response);
60              return;
61          }
62  
63          final HttpServletRequest httpRequest = (HttpServletRequest) request;
64          final HttpServletResponse httpResponse = (HttpServletResponse) response;
65          final String clientIp = rateLimitHelper.getClientIp(httpRequest);
66  
67          if (logger.isDebugEnabled()) {
68              logger.debug("Rate limit check: clientIp={}, path={}", clientIp, httpRequest.getRequestURI());
69          }
70  
71          // Check if IP is blocked
72          if (rateLimitHelper.isBlocked(clientIp)) {
73              if (logger.isDebugEnabled()) {
74                  logger.debug("Request blocked: clientIp={}", clientIp);
75              }
76              sendBlockedResponse(httpResponse);
77              return;
78          }
79  
80          // Check rate limit
81          if (!rateLimitHelper.allowRequest(clientIp)) {
82              if (logger.isDebugEnabled()) {
83                  logger.debug("Rate limit exceeded: clientIp={}", clientIp);
84              }
85              sendRateLimitResponse(httpResponse, rateLimitHelper.getRetryAfterSeconds());
86              return;
87          }
88  
89          chain.doFilter(request, response);
90      }
91  
92      /**
93       * Send a 403 Forbidden response for blocked IPs.
94       * @param response the HTTP response
95       * @throws IOException if an I/O error occurs
96       */
97      protected void sendBlockedResponse(final HttpServletResponse response) throws IOException {
98          response.setStatus(HttpServletResponse.SC_FORBIDDEN);
99          response.setContentType("application/json;charset=UTF-8");
100         response.getWriter().write("{\"error\":\"ip_blocked\",\"message\":\"Access denied.\"}");
101     }
102 
103     /**
104      * Send a 429 Too Many Requests response.
105      * @param response the HTTP response
106      * @param retryAfterSeconds the Retry-After header value
107      * @throws IOException if an I/O error occurs
108      */
109     protected void sendRateLimitResponse(final HttpServletResponse response, final int retryAfterSeconds) throws IOException {
110         response.setStatus(429); // Too Many Requests
111         response.setContentType("application/json;charset=UTF-8");
112         response.setHeader("Retry-After", String.valueOf(retryAfterSeconds));
113         response.getWriter()
114                 .write("{\"error\":\"rate_limit_exceeded\",\"message\":\"Too many requests. Please retry after " + retryAfterSeconds
115                         + " seconds.\",\"retry_after\":" + retryAfterSeconds + "}");
116     }
117 }