1. 개요
이 빠른 예제에서는 Spring Security를 사용하여 무차별 대입 인증 시도 를 방지 하기위한 기본 솔루션을 구현합니다 .
간단히 말해서, 단일 IP 주소에서 발생한 실패한 시도 횟수를 기록합니다. 특정 IP가 설정된 요청 수를 초과하면 24 시간 동안 차단됩니다.
2. AuthenticationFailureEventListener
AuthenticationFailureEventListener 를 정의하여 시작해 보겠습니다. - AuthenticationFailureBadCredentialsEvent 이벤트 를 수신 하고 인증 실패를 알려줍니다.
@Component
public class AuthenticationFailureListener
implements ApplicationListener<AuthenticationFailureBadCredentialsEvent> {
@Autowired
private LoginAttemptService loginAttemptService;
public void onApplicationEvent(AuthenticationFailureBadCredentialsEvent e) {
WebAuthenticationDetails auth = (WebAuthenticationDetails)
e.getAuthentication().getDetails();
loginAttemptService.loginFailed(auth.getRemoteAddress());
}
}
인증이 실패 하면 실패한 시도가 시작된 IP 주소를 LoginAttemptService 에 알리는 방법에 유의하십시오 .
3. AuthenticationSuccessEventListener
또한 AuthenticationSuccessEventListener를 정의 해 보겠습니다. AuthenticationSuccessEvent 이벤트 를 수신하고 인증 성공을 알려줍니다.
@Component
public class AuthenticationSuccessEventListener
implements ApplicationListener<AuthenticationSuccessEvent> {
@Autowired
private LoginAttemptService loginAttemptService;
public void onApplicationEvent(AuthenticationSuccessEvent e) {
WebAuthenticationDetails auth = (WebAuthenticationDetails)
e.getAuthentication().getDetails();
loginAttemptService.loginSucceeded(auth.getRemoteAddress());
}
}
실패 리스너와 유사하게 인증 요청이 시작된 IP 주소를 LoginAttemptService 에 알리는 방법에 유의 하십시오.
4. LoginAttemptService
이제 -LoginAttemptService 구현에 대해 논의 해 보겠습니다 . 간단히 말해, IP 주소 당 잘못된 시도 횟수를 24 시간 동안 유지합니다.
@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;
}
}
}
방법 공지 사항 실패한 인증 시도가 해당 IP에 대한 시도의 수를 증가 하고, 성공적인 인증 재설정 카운터가.
이 시점부터는 인증 할 때 카운터 를 확인하기 만하면 됩니다 .
5. UserDetailsService
이제 사용자 정의 UserDetailsService 구현 에 추가 검사를 추가하겠습니다 . 우리가로드 할 때 된 UserDetails를 , 이 IP 주소가 차단되면 우리는 먼저 필요가 확인 :
@Service("userDetailsService")
@Transactional
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Autowired
private RoleRepository roleRepository;
@Autowired
private LoginAttemptService loginAttemptService;
@Autowired
private HttpServletRequest request;
@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
String ip = getClientIP();
if (loginAttemptService.isBlocked(ip)) {
throw new RuntimeException("blocked");
}
try {
User user = userRepository.findByEmail(email);
if (user == null) {
return new org.springframework.security.core.userdetails.User(
" ", " ", true, true, true, true,
getAuthorities(Arrays.asList(roleRepository.findByName("ROLE_USER"))));
}
return new org.springframework.security.core.userdetails.User(
user.getEmail(), user.getPassword(), user.isEnabled(), true, true, true,
getAuthorities(user.getRoles()));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
다음은 getClientIP () 메서드입니다.
private String getClientIP() {
String xfHeader = request.getHeader("X-Forwarded-For");
if (xfHeader == null){
return request.getRemoteAddr();
}
return xfHeader.split(",")[0];
}
클라이언트의 원래 IP 주소 를 식별 하기 위한 몇 가지 추가 로직이 있습니다. 대부분의 경우 필요하지 않지만 일부 네트워크 시나리오에서는 필요합니다.
이러한 드문 시나리오의 경우 X-Forwarded-For 헤더를 사용하여 원래 IP에 도달합니다. 이 헤더의 구문은 다음과 같습니다.
X-Forwarded-For: clientIpAddress, proxy1, proxy2
또한 Spring이 가지고있는 또 다른 매우 흥미로운 기능에 주목 하십시오. HTTP 요청이 필요하므로 단순히 연결하는 것입니다.
이제 멋지다. 이것이 작동하려면 web.xml 에 빠른 리스너를 추가 해야하며 작업이 훨씬 쉬워집니다.
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
</listener-class>
</listener>
그게 다 입니다. UserDetailsService 에서 요청에 액세스 할 수 있도록 web.xml 에이 새로운 RequestContextListener 를 정의했습니다 .
6. AuthenticationFailureHandler 수정
마지막으로 CustomAuthenticationFailureHandler 를 수정 하여 새 오류 메시지를 사용자 지정해 보겠습니다 .
사용자가 실제로 24 시간 동안 차단되는 상황을 처리하고 있으며 사용자에게 허용 된 잘못된 인증 시도가 허용 된 최대 값을 초과했기 때문에 IP가 차단되었음을 사용자에게 알립니다.
@Component
public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
@Autowired
private MessageSource messages;
@Override
public void onAuthenticationFailure(...) {
...
String errorMessage = messages.getMessage("message.badCredentials", null, locale);
if (exception.getMessage().equalsIgnoreCase("blocked")) {
errorMessage = messages.getMessage("auth.message.blocked", null, locale);
}
...
}
}
7. 결론
이것은 무차별 암호 대입 시도 를 처리 하는 좋은 첫 번째 단계 이지만 개선의 여지가 있음 을 이해하는 것이 중요합니다 . 프로덕션 등급의 무차별 대입 방지 전략에는 IP가 차단하는 요소 이상이 포함될 수 있습니다.
이 예제 의 전체 구현 은 github 프로젝트 에서 찾을 수 있습니다. 이것은 Eclipse 기반 프로젝트이므로 그대로 가져 와서 실행할 수 있어야합니다.
- https://docs.spring.io/spring-framework/docs/current/reference/html
- https://www.baeldung.com/spring-security-block-brute-force-authentication-attempts
'Java' 카테고리의 다른 글
java.util.concurrent 개요 (0) | 2021.03.31 |
---|---|
자바에서 두 List의 교차점 (0) | 2021.03.30 |
스프링 부트 마이크로 서비스의 12 단계 방법론 (0) | 2021.03.30 |
SpringData JPA 사양을 사용한 REST 쿼리 언어 (0) | 2021.03.30 |
SpringData JPA 및 Querydsl을 사용한 REST 쿼리 언어 (0) | 2021.03.30 |