[PreProject] [Error] [Spring Security] : IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"

NtoZ·2023년 8월 13일
0

PreProject

목록 보기
4/12

기본 사항

  • 기존 SecurityConfiguration에 설정되어 있는 CustomFilterConfigurer
  • PasswordEncoder가 빈으로 등록되어 있는 상황

  • AccountDetailService에 구현되어 있는 loadUserByUsernameAccountDetails

문제 상황

  • 시큐리티 로그인 인증을 시도했으나 다음과 같은 예외 메시지가 출력됨

    IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"

    (에러메시지 후략)

  • JwtAuthenticationFilter.attemptAuthentication(JwtAuthenticationFilter.java:52) ~[main/:na]

    return authenticationManager.authenticate(authenticationToken); 에서 예외가 출력되는 것으로 보인다.

해결 과정

해결 방안 1

해결방법 1 : UserDetails 구현 클래스에서 Details를 생성할 때 비밀번호 인코딩?

  • 해결방법1 요약

    • CustomUserDetailsService implements UserDetailsService 에서 PasswordEncoder 객체를 의존성 주입받아 커스텀UserDetails를 생성할 때 인코딩된 비밀번호를 세팅하도록 함.
  • 현재 나의 class AccountDetailService implements UserDetailsService 코드 상태

  • 디버깅 :

  • 그러나 해결방법 1은 적절한 해결책이 아닌 것처럼 보인다. 이미 PasswordEncoder.encode로 암호화되어 DB에서 저장된 유저 정보로 새로운 UserDetails를 만드는 것인데 여기서 한 번 더 암호화 과정을 추가할 이유가 없다.

  • 그래도 한 번 시도해보자,

  • 역시나 마찬가지로 가입한 다음, 로그인 인증 요청을 보냈으나 똑같은 예외가 발생했다.

  • 다시 원상복구하자.


해결 방안 2

  • 암호화 방식 명시하기

    • 암호화 방식을 명시해서 return하는 방향으로 비밀번호를 만들어야 한다고 한다.
  • 그러나 DB에 정상적으로 암호화방식이 명시화되어 저장되었기 때문에 위와 같은 상황은 부합하지 않는다.


해결 방안 3

  • MemberDetails를 구현할 때 생성자로 초기화하는 값 확인하기

    • MemberDetails의 생성자에서는 Member엔티티 객체로부터 값을 받아서 this(MemberDetails)의 필드값으로 넘겨주어야 한다. 그에 따라 로그인 인증절차를 수행하는 것이다.
  • 기존 내 코드

    • AccountDetails에 account의 필드값 정보를 넘겨 인증절차를 수행해야 하는데,
      기존의 내 코드는 account를 다시 builder 패턴으로 재구성한 의미 없는 코드이다.
      핵심은 AccountDetails의 필드에 인자로 전달받은 account 객체의 정보를 전달하는 것임을 기억하자!
    private final class AccountDetails extends Account implements UserDetails {

        public AccountDetails(Account account) {
            account.builder()
                    .account_id(account.getAccount_id())
                    .email(account.getEmail())
                    .password(account.getPassword())
                    .roles(account.getRoles())
                    .build();
        }
  • builder()를 상속받아 작성해도 되지 않음.
    • 빌더로 객체를 구성해봐도 this(현재 생성된 AccountDetails의 객체)에 필드값이 반영되지 않는다.
      (필드 값이 받아와지지 않는모습)

    • Lombok 라이브러리로 생성되는 builder 패턴을 상속 받는 것의 문제인지, builder패턴 자체의 태생적인 한계인지는 알 수 없어서 자료를 찾아보았다.
    • 참고: Lombok @Builder 상속
    • 상속 관계에서 @Builder 적용
    private final class AccountDetails extends Account implements UserDetails {

        public AccountDetails(Account account) {
            AccountDetails.builder()
                    .account_id(account.getAccount_id())
                    .email(account.getEmail())
                    .password(account.getPassword())
                    .roles(account.getRoles())
                    .build();
        }
  • 지양하고 싶은 방법이었지만 Account 엔티티에 @Setter를 부여한 후,
    set으로 account_id, email, password, roles를 세팅해주었다.
    (@Builder와 @Setter)
private final class AccountDetails extends Account implements UserDetails {

        public AccountDetails(Account account) {

            setAccount_id(account.getAccount_id());
            setEmail(account.getEmail());
            setPassword(account.getPassword());
            setRoles(account.getRoles());
        }
  • 회원가입

  • 로그인

    응답헤더로 받은 Access 토큰과 Refresh 토큰


해결

  • 최종 해결책 : @Setter 대신 super()의 생성자를 이용하는 쪽으로 해결 보았다.

    class AccountDetailService implements UserDetailsService
    private final class AccountDetails
...(전략)
@AllArgsConstructor
    private final class AccountDetails extends Account implements UserDetails {

        public AccountDetails(Account account) {
            super(account.getAccountId(), account.getEmail(), account.getPassword(), account.getRoles(), account.getDisplayName());
        }
    }
...후략

소회

  • Spring Security 컴포넌트 동작 흐름을 이해하는 기본기가 필요하다.
    디버깅을 통해 부족한 부분을 찾아내고 결함을 파악하는 습관을 기르자.
profile
9에서 0으로, 백엔드 개발블로그

1개의 댓글

comment-user-thumbnail
2023년 8월 13일

유익한 자료 감사합니다.

답글 달기