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:
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user