Table of Content
Spring Boot Brute Force(랜덤 문자열 공격) 대응
10 회 이상 로그인 실패 발생시, 24시간동안 로그인 시도를 차단합니다.
목표
Brute Force(랜덤 문자열 공격) 에 대응합니다.
기초 프로젝트
Spring Boot OAuth2 Authorization Server 를 기초 프로젝트로 합니다.
build.gradle 수정
dependencies {
// ......
implementation 'com.google.guava:guava:20.0'
// ......
}
소스 추가
AuthenticationFailureListener.java
@RequiredArgsConstructor
@Component
public class AuthenticationFailureListener implements
ApplicationListener<AuthenticationFailureBadCredentialsEvent> {
private final HttpServletRequest request;
private final LoginAttemptService loginAttemptService;
@Override
public void onApplicationEvent(AuthenticationFailureBadCredentialsEvent e) {
final String xfHeader = request.getHeader("X-Forwarded-For");
if (xfHeader == null) {
loginAttemptService.loginFailed(request.getRemoteAddr());
} else {
loginAttemptService.loginFailed(xfHeader.split(",")[0]);
}
}
}
AuthenticationSuccessEventListener.java
@RequiredArgsConstructor
@Component
public class AuthenticationSuccessEventListener implements
ApplicationListener<AuthenticationSuccessEvent> {
private final HttpServletRequest request;
private final LoginAttemptService loginAttemptService;
@Override
public void onApplicationEvent(final AuthenticationSuccessEvent e) {
final String xfHeader = request.getHeader("X-Forwarded-For");
if (xfHeader == null) {
loginAttemptService.loginSucceeded(request.getRemoteAddr());
} else {
loginAttemptService.loginSucceeded(xfHeader.split(",")[0]);
}
}
}
LoginAttemptService.java
@Service
public class LoginAttemptService {
private final int MAX_ATTEMPT = 10;
private LoadingCache<String, Integer> attemptsCache;
public LoginAttemptService() {
super();
attemptsCache = CacheBuilder.newBuilder().
expireAfterWrite(1, TimeUnit.DAYS).build(new CacheLoader<String, Integer>() {
public Integer load(String key) {
return 0;
}
});
}
public void loginSucceeded(String key) {
attemptsCache.invalidate(key);
}
public void loginFailed(String key) {
int attempts = 0;
try {
attempts = attemptsCache.get(key);
} catch (ExecutionException e) {
attempts = 0;
}
attempts++;
attemptsCache.put(key, attempts);
}
public boolean isBlocked(String key) {
try {
return attemptsCache.get(key) >= MAX_ATTEMPT;
} catch (ExecutionException e) {
return false;
}
}
}
CustomUserDetailService.java
@RequiredArgsConstructor
@Service
public class CustomUserDetailService implements UserDetailsService {
private final UserRepository userRepository;
private final AccountStatusUserDetailsChecker detailsChecker = new AccountStatusUserDetailsChecker();
private final LoginAttemptService loginAttemptService;
private final HttpServletRequest request;
@Override
public UserDetails loadUserByUsername(String name) throws UsernameNotFoundException {
String ip = getClientIP();
if (loginAttemptService.isBlocked(ip)) {
throw new UsernameNotFoundException("IP blocked");
}
User user = userRepository.findByUid(name).orElseThrow(() -> new UsernameNotFoundException("user is not exists"));
detailsChecker.check(user);
return user;
}
private String getClientIP() {
String xfHeader = request.getHeader("X-Forwarded-For");
if (xfHeader == null){
return request.getRemoteAddr();
}
return xfHeader.split(",")[0];
}
}
Pingback: Spring Boot OAuth2 Authorization Server 구축 – 상구리의 기술 블로그