CustomUserDetailService
를 구현하다가 유저가 없는 경우에 UsernameNotFoundException
을 던지게끔 처리했다. 그런데 계속 BadCredentialsException
이 발생하는 것이 아닌가.
디버깅을 해보니 DaoAuthenticationProvider
에서 UsernameNotFoundException
를 핸들링할 때 mitigateAgainstTimingAttack
이라는 메서드를 호출하고 있고, 그 후에 BadCredentialsException
으로 예외를 랩핑하는 식으로 처리하고 있었다. mitigateAgainstTimingAttack
메서드는 왜 실행하는 걸까?
DaoAuthenticationProvider.java
...
catch (UsernameNotFoundException ex) {
mitigateAgainstTimingAttack(authentication);
throw ex;
}
궁금하여 살펴보니, userNotFoundEncodedPassword
이라는 값을 기본적으로 사용하여 passwordEncoder를 통해 굳이 비밀번호를 검증하는 것이었다. 결과는 당연히 실패일텐데 말이다. (이미 없는 아이디라는 사실을 알기 때문이다.) userNotFoundEncodedPassword
에 주석을 통해 그 이유를 따라가 볼 수 있었는데, security 프로젝트에서 SEC-2056으로 보고된 이슈를 해결하기 위해 적용된 방식이었다. (with 부-채널 공격)
사실 Security가 제공하는 passwordEncoder 구현체들은 적응형 단방향 함수로써 고성능 하드웨어를 이용한 공격에 방어하기 위해서 인코딩시 약 1초 정도의 시간이 걸리도록 설정 및 사용하는 것을 권장하고 있다. 이는 의도적으로 고성능의 컴퓨팅 리소스를 이용하여 무차별적인 공격에 방어하기 위함이다. (참고) 그런데 유저를 찾을 수 없는 경우 비밀번호 검증을 하지 않는다면 공격자는 몇 ms 안에 유효하지 않은 아이디라는 결과를 확인할 수 있을 것이다. 즉 공격자가 무차별적인 공격을 통해서 유효한 아이디를 찾아낼 수 있게 된다.
따라서 시큐리티는 존재하지 않는 아이디로 로그인 요청이 들어오더라도 보안을 위해서 굳이 비밀번호 검증 작업을 수행하는 것이었다. 참고로 해당 이슈는 Spring Security 3.1.3+, 3.0.8+ 또는 2.0.8+로 업그레이드하여 해결할 수 있다.
마지막, 그럼 Exception은 왜 랩핑하는 걸까? 이유는 역시 보안때문이다. DaoAuthenticationProvider
를 설정할 때 아래 메서드를 통해 랩핑 여부를 결정할 수 있다.
AbstractUserDetailsAuthenticationProvider.java
...
/**
* By default the<code>AbstractUserDetailsAuthenticationProvider</code>throws a
*<code>BadCredentialsException</code>if a username is not found or the password is
* incorrect. Setting this property to<code>false</code>will cause
*<code>UsernameNotFoundException</code>s to be thrown instead for the former. Note
* this is considered less secure than throwing<code>BadCredentialsException</code>
* for both exceptions.
*@paramhideUserNotFoundExceptionsset to<code>false</code>if you wish
*<code>UsernameNotFoundException</code>s to be thrown instead of the non-specific
*<code>BadCredentialsException</code>(defaults to<code>true</code>)
*/
public void setHideUserNotFoundExceptions(boolean hideUserNotFoundExceptions) {
this.hideUserNotFoundExceptions = hideUserNotFoundExceptions;
}
'Spring' 카테고리의 다른 글
[알림 기능 구현] 웹소켓 vs SSE (0) | 2022.09.27 |
---|---|
스프링 로깅 기능 구현하기 (인터셉터, ThreadLocal 사용) (0) | 2022.09.13 |
테스트 코드 커버리지의 종류 (0) | 2022.08.16 |
단위 테스트 given-when-then 패턴과 BDDMockito (0) | 2022.08.08 |
Spring Security Form Login 사용과 동시성 세션 제어 (0) | 2022.07.17 |