✍️ 개발 기록

authenticate에서 발생하는 '자격 증명에 실패하였습니다' 문제 해결

ming412 2024. 6. 19. 16:10

프로젝트 중 로그인 구현을 맡았다. 로그인은 이메일과 비밀번호를 입력하는 방식으로 구현했다.

자격 증명에 실패하였습니다. 라는 에러가 발생했다.

 

 

디버깅을 해보니 이슈가 발생한 부분은 다음과 같았다.

Authentication authentication = authenticationManagerBuilder.getObject().authenticate(usernamePasswordAuthenticationToken);

 

UsernamePasswordAuthenticationToken으로 생성한 토큰을 넘겨 권한 인증을 받는 부분이다.

 

디버깅 모드에서 메서드 내부 동작 흐름을 따라가다보니 retrieveUser()에서 UserDetails를 사용하는 모습을 확인할 수 있었다.

 

커스텀으로 만든 userDetail은 다음과 같다.

package com.ssafy.naem.global.jwt;

import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import com.ssafy.naem.domain.member.entity.BasicLoginMember;
import com.ssafy.naem.domain.member.entity.Member;
import com.ssafy.naem.domain.member.repository.BasicLoginMemberRepository;
import com.ssafy.naem.domain.member.repository.MemberRepository;

import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class CustomUserDetailsService implements UserDetailsService {

	private final MemberRepository memberRepository;
	private final BasicLoginMemberRepository basicLoginMemberRepository;
	private final PasswordEncoder passwordEncoder;

	@Override
	public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
		return memberRepository.findByEmail(email)
			.map(this::createUserDetails)
			.orElseThrow(() -> new UsernameNotFoundException("해당하는 회원을 찾을 수 없습니다."));
	}

	// 해당하는 User 의 데이터가 존재한다면 UserDetails 객체로 만들어서 return
	private UserDetails createUserDetails(Member member) {
		BasicLoginMember basicLoginMember = basicLoginMemberRepository.findByMember(member)
			.orElseThrow(() -> new UsernameNotFoundException("해당하는 회원을 찾을 수 없습니다."));
		return User.builder()
			.username(member.getEmail())
			.password(passwordEncoder.encode(basicLoginMember.getEncPassword()))
			.build();
	}

}

 

authenticate() 메서드 내부에서 loadUserByUsername() 메서드를 사용하는데

createUserDetails() 메서드에서 새로운 유저를 만들 때

member라는 객체를 파라미터로 받을 때 내부의 password는 이미 암호화가 되어있었는데, 한번 더 암호화를 해버린 것이었다!

 

그 결과 authenticate() 메서드를 구현하는 AbstractUserDetailsAuthenticationProvider라는 추상 클래스에서

additionalAuthenticationChecks() 라는 메서드 안에서 오류가 발생했다.

 

if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
        this.logger.debug("Failed to authenticate since password does not match stored value");
        throw new BadCredentialsException(this.messages
                .getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
    }
}

 

BcryptEncoder를 통해 userDetail과 UsernamePasswordAuthenticationToken의 비밀번호가 매치하는지 확인하는 과정에서

에러가 발생한 것 같다.

 

그래서 customUserDetailsService 코드를 아래와 같이 변경해서 해결했다.

private UserDetails createUserDetails(Member member) {
    BasicLoginMember basicLoginMember = basicLoginMemberRepository.findByMember(member)
        .orElseThrow(() -> new UsernameNotFoundException("해당하는 회원을 찾을 수 없습니다."));
    return User.builder()
        .username(member.getEmail())
        .password(basicLoginMember.getEncPassword()) // 이 부분 변경 !!
        .build();
}

 

알게된 것

authenticationManagerBuilder의 getObject().authenticate 메서드는 내부적으로 UserDetailsService의 loadUserByUsername 메서드를 호출해 사용자를 찾는다는 것을 알게되었다.