1+ package com .vimaltech .contactapi .security ;
2+
3+ import io .github .bucket4j .Bandwidth ;
4+ import io .github .bucket4j .Bucket ;
5+ import io .github .bucket4j .BucketConfiguration ;
6+ import io .github .bucket4j .distributed .proxy .ProxyManager ;
7+ import io .github .bucket4j .redis .lettuce .cas .LettuceBasedProxyManager ;
8+
9+ import io .lettuce .core .RedisClient ;
10+ import io .lettuce .core .RedisURI ;
11+
12+ import jakarta .servlet .FilterChain ;
13+ import jakarta .servlet .ServletException ;
14+ import jakarta .servlet .http .HttpServletRequest ;
15+ import jakarta .servlet .http .HttpServletResponse ;
16+
17+ import org .springframework .beans .factory .annotation .Value ;
18+ import org .springframework .context .annotation .Profile ;
19+ import org .springframework .stereotype .Component ;
20+ import org .springframework .web .filter .OncePerRequestFilter ;
21+
22+ import java .io .IOException ;
23+ import java .time .Duration ;
24+ import java .util .Optional ;
25+
26+ @ Component
27+ @ Profile ("prod" )
28+ public class RedisRateLimitFilter extends OncePerRequestFilter {
29+
30+ private final ProxyManager <byte []> proxyManager ;
31+
32+ public RedisRateLimitFilter (
33+ @ Value ("${spring.data.redis.host}" ) String host ,
34+ @ Value ("${spring.data.redis.port}" ) int port
35+ ) {
36+
37+ RedisURI redisURI = RedisURI .Builder .redis (host )
38+ .withPort (port )
39+ .build ();
40+
41+ RedisClient redisClient = RedisClient .create (redisURI );
42+
43+ this .proxyManager = LettuceBasedProxyManager
44+ .builderFor (redisClient )
45+ .build ();
46+ }
47+
48+ private BucketConfiguration configuration () {
49+ return BucketConfiguration .builder ()
50+ .addLimit (Bandwidth .simple (10 , Duration .ofMinutes (1 )))
51+ .build ();
52+ }
53+
54+ @ Override
55+ protected void doFilterInternal (HttpServletRequest request ,
56+ HttpServletResponse response ,
57+ FilterChain filterChain )
58+ throws ServletException , IOException {
59+
60+ if (!request .getRequestURI ().equals ("/api/v1/contact" )) {
61+ filterChain .doFilter (request , response );
62+ return ;
63+ }
64+
65+ String ip = Optional .ofNullable (request .getHeader ("X-Forwarded-For" ))
66+ .map (s -> s .split ("," )[0 ].trim ())
67+ .orElse (request .getRemoteAddr ());
68+
69+ Bucket bucket = proxyManager .builder ()
70+ .build (("ip:" + ip ).getBytes (), this ::configuration );
71+
72+ if (bucket .tryConsume (1 )) {
73+ filterChain .doFilter (request , response );
74+ } else {
75+ response .setStatus (429 );
76+ response .setContentType ("application/json" );
77+ response .getWriter ().write ("""
78+ {
79+ "success": false,
80+ "message": "Too many requests. Please try again later."
81+ }
82+ """ );
83+ }
84+ }
85+ }
0 commit comments