From dd5242139249904170de971866edd00a2a09b78d Mon Sep 17 00:00:00 2001 From: Kshitij <160704796+kshitij-ka@users.noreply.github.com> Date: Thu, 3 Jul 2025 02:47:19 +0530 Subject: [PATCH] Add brute-force protection with rate limiting on login - Caffeine cache used to allow max 5 login attempts per minute. - Login endpoint blocks IPs exceeding rate, returns 429 status. - Failed attempts are reset after successful login or after 1 minute. --- pom.xml | 5 +++ .../controller/AuthController.java | 18 ++++++++-- .../security/RateLimiterService.java | 35 +++++++++++++++++++ 3 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/skycrate/backend/skycrateBackend/security/RateLimiterService.java diff --git a/pom.xml b/pom.xml index e61d69e..c664f71 100644 --- a/pom.xml +++ b/pom.xml @@ -172,6 +172,11 @@ + + com.github.ben-manes.caffeine + caffeine + 3.1.8 + diff --git a/src/main/java/com/skycrate/backend/skycrateBackend/controller/AuthController.java b/src/main/java/com/skycrate/backend/skycrateBackend/controller/AuthController.java index b823bd4..55d017b 100644 --- a/src/main/java/com/skycrate/backend/skycrateBackend/controller/AuthController.java +++ b/src/main/java/com/skycrate/backend/skycrateBackend/controller/AuthController.java @@ -24,12 +24,26 @@ public class AuthController { } @PostMapping("/login") - public ResponseEntity login(@RequestBody LoginRequest request) { - authManager.authenticate(new UsernamePasswordAuthenticationToken(request.getEmail(), request.getPassword())); + public ResponseEntity login(@RequestBody LoginRequest request, HttpServletRequest servletRequest) { + String ip = servletRequest.getRemoteAddr(); // or use request.getEmail() as key + + if (rateLimiterService.isBlocked(ip)) { + return ResponseEntity.status(429).body("Too many login attempts. Please try again later."); + } + + try { + authManager.authenticate( + new UsernamePasswordAuthenticationToken(request.getEmail(), request.getPassword()) + ); + } catch (Exception ex) { + rateLimiterService.recordFailedAttempt(ip); + return ResponseEntity.status(401).body("Invalid credentials."); + } User user = userRepository.findByEmail(request.getEmail()) .orElseThrow(() -> new RuntimeException("User not found")); + rateLimiterService.resetAttempts(ip); String token = jwtService.generateToken(user); return ResponseEntity.ok().body(token); } diff --git a/src/main/java/com/skycrate/backend/skycrateBackend/security/RateLimiterService.java b/src/main/java/com/skycrate/backend/skycrateBackend/security/RateLimiterService.java new file mode 100644 index 0000000..7aad7ee --- /dev/null +++ b/src/main/java/com/skycrate/backend/skycrateBackend/security/RateLimiterService.java @@ -0,0 +1,35 @@ +package com.skycrate.backend.skycrateBackend.security; + +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +import org.springframework.stereotype.Service; + +import java.util.concurrent.TimeUnit; + +@Service +public class RateLimiterService { + + private final Cache attemptsCache; + + private static final int MAX_ATTEMPTS = 5; + + public RateLimiterService() { + this.attemptsCache = Caffeine.newBuilder() + .expireAfterWrite(1, TimeUnit.MINUTES) + .build(); + } + + public boolean isBlocked(String key) { + Integer attempts = attemptsCache.getIfPresent(key); + return attempts != null && attempts >= MAX_ATTEMPTS; + } + + public void recordFailedAttempt(String key) { + int attempts = attemptsCache.getIfPresent(key) == null ? 0 : attemptsCache.getIfPresent(key); + attemptsCache.put(key, attempts + 1); + } + + public void resetAttempts(String key) { + attemptsCache.invalidate(key); + } +} \ No newline at end of file