
Spring Security와 JWT를 통해 로그인 기능을 구현하고 테스트하는 과정에서
java.lang.IllegalArgumentException:There is no PasswordEncoder mapped for the id "null"라는 에러를 맞닥뜨렸다. 해당 에러가 왜 발생하였는지, 어떻게 해결하였는지 정리하기 위해 본 포스트를 작성하였다.
Postman을 통해 로그인 테스트를 하는 도중 500 Internal Server Error가 발생하였다.

IntelliJ를 확인해보니 log에 java.lang.IllegalArgumentException:There is no PasswordEncoder mapped for the id "null"가 찍혀있었다.

아직 멤버 추가 API를 개발하지 않아 DB에 멤버 데이터를 직접 넣은 것이 화근이었다.

17, 18번 데이터는 추후 멤버 추가 API를 개발한 후 해당 API를 통해 DB에 넣은 데이터이다.
자세한 원인은 Spring Security 5 이상부터 PasswordEncoder를 통해 password를 처리하는데 DB에 있는 password 데이터가 PasswordEncoder가 처리하는 형식으로 되어있지 않아서 발생한 것이다.

PasswordEncoderFactories.createDelegatingPasswordEncoder()로 생성한 PasswordEncoder는 BCryptPasswordEncoder가 사용되며 prefix인 {id}로 사용할 PasswordEncoder 종류가 정해진다. Default는 bcrypt이다. 즉, 로그인 과정에서 어떤 Encoder를 사용할지 DB에 저장된 password의 prefix {id}를 보고 결정한다.
허나 DB에 직접 넣은 19번 데이터는 prefix가 정해지지 않았고 로그인하는 과정 passwordEncoder.matches(rawPassword, encodePassword)에서 prefix가 없는 password에 mapping되는 Encoder가 없기 때문에 오류가 발생하였다.
크게 2가지가 있다.
Spring Security 5 이전에 사용되던 NoOpPasswordEncoder를 사용하는 방법

정상적으로 응답이 넘어온다. 하지만 이 방법은 deprecated 되었기 때문에 지양하는 방법이다. 실제로 해당 방법을 사용하면 IntelliJ에서 uses or overrides a deprecated API.라는 warning이 뜨는 것을 확인할 수 있다.
DB에 데이터를 저장할 때 prefix를 붙여주는 방법
prefix로 {noop}을 붙여서 저장해준다.

마찬가지로 정상적으로 응답이 넘어오는 것을 확인할 수 있다.
멤버를 추가할 때 password를 인코딩하여 저장하는 API를 작성하는 것이 가장 좋은 방법이지만 필자는 이 테스트를 진행하는 시점에 해당 API를 작성하지 않아 2번 방법으로 테스트를 진행하였다.
문득
PasswordEncoderFactories.createDelegatingPasswordEncoder()의 default prefix가 {bcrypt}라면 DB에 직접 저장할 때 {bcrypt}를 붙여서 저장하면 되지 않을까라는 의문점이 생겼다. 그래서 이 경우도 테스트 해보았다.
DB에 {bcrypt}를 붙여서 직접 넣어봤다.
Postman에서 로그인 요청을 보내봤는데 401 HTTP Status Code가 응답되었다.
IntelliJ를 확인해보니 Encoded password does not look like BCrypt라는 warning이 뜬 것을 확인할 수 있었다.
결론적으로 prefix는 bcrypt이기 때문에 BCryptPasswordEncoder를 사용하지만 password가 bcrypt 형식으로 인코딩되지 않았기 때문에 해당 오류가 뜨는 것이다.
https://ddasi-live.tistory.com/80