정리
오랜만에 테스트를 짜던 중 회원가입 부분에서 계속 비밀번호 매칭이 안되는 버그를 발견했습니다..
결론부터 말하자면 비밀번호 비교는 passwordEncoder.matches(비밀번호, 암호화된 비밀번호) 를 이용해야합니다.
Security내부 로직을 뜯어봤을 때 위처럼 비교하는 코드를 봤기 때문에 알고는 있었지만 그런가 보다 하고 지나쳤었던 로직인데 다 이유가 있던거였....
테스트 과정 및 실패의 이유 분석
단순하게 생각해서 passwordEncoder로 암호화 시킨 비밀번호를 DB에 저장시킨것이기 때문에 똑같은 비밀번호를 암호화 시켜서 비교한다면 성공할거라고 생각했습니다. 아래처럼요..(member는 테스트 결과 값으로 다시 조회한 객체입니다.)
Assertions.assertThat(member.getPassword()).isEqualTo(passwordEncoder.encode("TestPassword1234@@"));
테스트 결과 값이 다르게 나왔고 이유를 분석해봤습니다.
- 실제 애플리케이션 환경과 테스트 환경에서 서로 다른 passwordEncoder가 주입되나?
- 환경이 다르면 다른 알고리즘을 택하는건가..?
아래와 같이 하나의 빈만 등록했고 같은 암호화 알고리즘을 사용하도록 특정했는데도 계속해서 에러가났습니다.
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
암호화 알고리즘의 동작 방식을 이해하지 못했던 저의 실수...
passwordEncoder는 암호화 과정에서 매번 값이 달라집니다. 이는 암호화 과정에서 salt값을 추가하여 해싱하기 때문입니다
BCryptPasswordEncoer 동작원리
실제 구현체의 코드를 보면 암호화 과정에서 random한 salt값을 이용해 암호화 하는 것을 볼 수 있습니다.
public String encode(CharSequence rawPassword) {
if (rawPassword == null) {
throw new IllegalArgumentException("rawPassword cannot be null");
} else {
String salt = this.getSalt();
return BCrypt.hashpw(rawPassword.toString(), salt);
}
}
private String getSalt() {
return this.random != null ? BCrypt.gensalt(this.version.getVersion(), this.strength, this.random) : BCrypt.gensalt(this.version.getVersion(), this.strength);
}
그래서 matches는 다음과 같이 동작합니다.
1. 암호화된 password에서 salt값을 추출
2. 원본 password에 salt값을 이용해 해싱
3. 둘을 비교