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.
This commit is contained in:
K
2025-07-03 02:47:19 +05:30
parent aaf5d2dbd8
commit dd52421392
3 changed files with 56 additions and 2 deletions
+5
View File
@@ -172,6 +172,11 @@
</excludes> </excludes>
</configuration> </configuration>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>3.1.8</version>
</dependency>
</plugin> </plugin>
</plugins> </plugins>
@@ -24,12 +24,26 @@ public class AuthController {
} }
@PostMapping("/login") @PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest request) { public ResponseEntity<?> login(@RequestBody LoginRequest request, HttpServletRequest servletRequest) {
authManager.authenticate(new UsernamePasswordAuthenticationToken(request.getEmail(), request.getPassword())); 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()) User user = userRepository.findByEmail(request.getEmail())
.orElseThrow(() -> new RuntimeException("User not found")); .orElseThrow(() -> new RuntimeException("User not found"));
rateLimiterService.resetAttempts(ip);
String token = jwtService.generateToken(user); String token = jwtService.generateToken(user);
return ResponseEntity.ok().body(token); return ResponseEntity.ok().body(token);
} }
@@ -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<String, Integer> 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);
}
}