앞에 이메일 인증 요청과 인증 코드 확인까지 완벽하게 성공한 줄 알았는데, 나중에 로그인 기능을 구현할 때 계속해서 인증이 막혔었다. 분명 코드도 잘 짜놨고 제대로 이해했다고 생각했는데 같은 오류가 반복돼서 멘붕이 왔었던 것 같다.
오류 1
BadCredentialException: 자격 증명에 실패하였습니다.
로그인에 필요한 기능들을 공부하고 API를 구현했다면 좋았겠지만... 구현 기한이 정해져 있기도 했고 공부할 시간이 부족해서 다른 사람들의 코드를 참고하면서 구현했다. 계속되는 오류에 코드를 갈아엎을까 고민했지만 로그인 구현은 문제가 없다고 생각해서 내가 기존에 작성한 코드에서 문제가 생기는 건 아닐지 확인하기로 했다.
org.springframework.security.authentication.BadCredentialsException: 자격 증명에 실패하였습니다.
이메일과 비밀번호(1234)를 간단하게 만들고 회원가입을 진행시킨 뒤 DB에 저장된 결과를 확인했는데, 일단 암호화는 제대로 진행되고 있었다. 그러다가 BCryptPasswordEncoder는 BCrypt 해시 함수를 이용한다는 게 떠올라서 아래 사이트에서 1234를 암호화시켜 봤는데 DB에 저장된 암호화된 비밀번호와 전혀 다르다는 걸 알게 됐다.
비밀번호를 암호화해 저장하는 코드를 살펴보니 MemberConverter에서 암호화한 비밀번호를 MemberService에서 또 암호화해 DB에 저장하고 있는 걸 발견했다. 그래서 입력한 비밀번호와 DB에 저장된 암호화된 비밀번호가 일치하는지 확인하는 부분에서 오류가 발생했던 것이었다.
public class MemberConverter {
private static final PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
public static Member toMember(MemberRequestDTO.MemberJoinDTO request) {
return Member.builder()
.monthBudget(request.getMonthBudget())
.name(request.getName())
.email(request.getEmail())
// Converter에서 비밀번호 암호화
.password(passwordEncoder.encode(request.getPassword()))
.build();
}
public static MemberResponseDTO.MemberJoinResultDTO toJoinResultDTO(Member member) {
return MemberResponseDTO.MemberJoinResultDTO.builder()
.memberId(member.getId())
.createdAt(LocalDateTime.now()).build();
}
}
public class MemberService {
...
private final PasswordEncoder encoder = new BCryptPasswordEncoder();
@Transactional
public Member joinMember(MemberRequestDTO.MemberJoinDTO request) throws Exception {
...
Member newMember = MemberConverter.toMember(request);
// Converter에서 암호화한 비밀번호를 Service에서 또 암호화
newMember.encodePassword(encoder.encode(newMember.getPassword()));
return memberRepository.save(newMember);
}
}
해결방안
MemberConverter에서 Member Entity를 만들 때 password를 build 하는 부분과 PasswordEncoder를 삭제하고, MemberService에서 rawPassword로 암호화를 진행하도록 수정했더니 로그인이 정상적으로 작동하는 걸 확인할 수 있었다.
@RequiredArgsConstructor
public class MemberConverter {
public static Member toMember(MemberRequestDTO.MemberJoinDTO request) {
return Member.builder()
.monthBudget(request.getMonthBudget())
.name(request.getName())
.email(request.getEmail())
.build();
}
public static MemberResponseDTO.MemberJoinResultDTO toJoinResultDTO(Member member) {
return MemberResponseDTO.MemberJoinResultDTO.builder()
.memberId(member.getId())
.createdAt(LocalDateTime.now()).build();
}
}
오류 2
Data truncation: Data too long for column
DB에 비밀번호를 저장할 때 BCrypt 해시 함수를 이용해 암호화된 비밀번호를 저장하는데, 이때 암호화된 결과는 256비트인 64자리의 문자열로 저장된다. 서비스 정책서만 보고 Member Entity의 password 필드의 길이를 15까지로 제한해 둔 상태여서 DB 저장 시 길이 제한을 넘어가기 때문에 발생한 오류였다.
com.mysql.jdbc.MysqlDataTruncation: Data truncation: Data too long for column 'password'
해결방안
Member Entitiy의 password 필드 길이 제한을 65까지 늘려 해결했다.
@Entity
@Getter
@Builder
@DynamicUpdate
@DynamicInsert
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Member extends BaseEntity {
...
@Column(nullable = false, length = 65)
private String password;
}
참고자료