에러 : java.lang.NullPointerException: Cannot invoke "org.example.expert.domain.user.enums.UserRole.name()" because "userRole" is null
플러스 주차의 9. Spring Security 이 문제를 풀고 있었다.
결론부터 말하자면,
@Auth 어노테이션을 더 이상 쓰지 않고, @AuthenticationPrincipal 를 쓰려고 바꾸면서 import 문제가 발생했다.
가장 문제된 행동과 흐름 :
- @Auth 를 드래그로 감싸서 Refactor을 해서, 모든 클래스에 있는 이름을 AuthenticationPrincipal 으로 바꿨다.
- 이렇게 되면, security에 있는 어노테이션이 아니라, 기존에 ArgumentResolve 에서 사용하던 Auth 어노테이션을 이름만 바꿔서 쓰게 된 것이다. 그리고 import도 여전히 커스텀 어노테이션으로 보고 있다.
- 컨트롤러에서의 잘못된 import로 쓰고 있는 커스텀 어노테이션 AuthenticationPrincipal 때문에 기존에 쓰던 Auth처럼 동작하게 된다.
- 결론적으로 흐름을 보자면, 필터 → 컨트롤러의 AuthenticationPrincipal 어노테이션 → AuthenticationPrincipal 인터페이스 → 값이 주어지지 않은 상태로 AuthUser 생성자 실행 → null 값이 enum 타입에 들어가게 되면서 에러 발생
문제 추적
AuthUser에 제대로 된 값이 들어가지 않는다는 아래의 에러가 발생했다.

값이 왜 제대로 들어가지 않는지 알기 위해서, AuthUser의 생성자와 Filter에 로그를 찍어보기로 했다.
필터

AuthUser
@Getter
public class AuthUser {
private final Long id;
private final String email;
private final Collection<? extends GrantedAuthority> authorities;
public AuthUser(Long id, String email, UserRole userRole) {
this.id = id;
this.email = email;
System.out.println("::: userRole ::: == "+ userRole);
this.authorities = List.of(new SimpleGrantedAuthority(userRole.name()));
}
}
콘솔창
2024-10-09T01:40:48.617+09:00 INFO 21600 --- [spring-plus] [nio-8080-exec-2] o.e.expert.config.JwtSecurityFilter : :: AuthUser : 41 ::
2024-10-09T01:40:48.677+09:00 INFO 21600 --- [spring-plus] [nio-8080-exec-2] o.e.expert.config.JwtSecurityFilter : :: AuthUser : 49 ::
::: userRole ::: == ROLE_ADMIN
2024-10-09T01:40:53.455+09:00 INFO 21600 --- [spring-plus] [nio-8080-exec-2] o.e.expert.config.JwtSecurityFilter : :: AuthUser : 51 ::
::: userRole ::: == null
Disconnected from the target VM, address: '127.0.0.1:61687', transport: 'socket'
2024-10-09T01:42:13.785+09:00 ERROR 21600 --- [spring-plus] [nio-8080-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.example.expert.domain.common.dto.AuthUser]: Constructor threw exception] with root cause
java.lang.NullPointerException: Cannot invoke "org.example.expert.domain.user.enums.UserRole.name()" because "userRole" is null
at org.example.expert.domain.common.dto.AuthUser.<init>
현재 상황은, AuthUser 생성자를 필터의 52번째 줄 외에는, 그 어디에서도 사용하고 있지 않았다.
디버그를 찍어서 돌아가는 것을 봤는데, 알 수 없는 이유로 AuthUser 생성자가 필터를 돌면서 한번 실행되고, 필터가 다 끝난 뒤에 한번 더 실행이 되고 있었다. 하지만 필터가 다시 처음부터 도는 것도 아니었다.
다른 분들의 코드와 강의에 나온 내용들을 다시 확인해봤는데도, 도저히 틀린 부분을 찾아내는 것이 쉽지 않았다. 결국 소거법으로 정말 하나하나 다 따져가면서 문제가 없는 클래스들을 지워나가다 보니, 컨트롤러에서 쓰고 있던 어노테이션 문제라는 것을 알게 됐다.
이것은 전적으로 나의 실수인데, 이전에 쓰고 있던 @Auth 커스텀 어노테이션에서 AuthenticationPrincipal으로 바꿔 써야 하는 것 때문에, 모든 프로젝트의 이름을 일괄적으로 바꾸려고 했고, 나는 보통 그런 해결 방법을 Refactor를 써서 해결했다. 당연히 그렇게 했고, 이렇게 바꾸면 내가 시큐리티의 AuthenticationPrincipal 를 불러오는 것이 아니라, 커스텀 어노테이션을 이름만 바꿔서 똑같이 그대로 사용하는 것과 같았다. 아무튼 결과적으로는 여기의 문제를 파악하고, 시큐리티로 어노테이션을 불러오게 되면서, 해당 과제를 해결할 수 있게 됐다.

교훈
Import 실수를 조심하자
'트러블 슈팅' 카테고리의 다른 글
| 기간 검색에서 시작일 또는 종료일이 없을 경우 발생한 문제 개선 (0) | 2024.10.11 |
|---|---|
| fetchJoin와 BatchSize를 같이 사용한 경우의 경고문 (0) | 2024.10.11 |
| QueryDsl에서 List<Dto> 방식과 List<Entiry> 방식 (0) | 2024.10.11 |
| 테이블 이름이 예약어와 같은 경우의 에러 ( 테이블 이름 order ) (0) | 2024.09.21 |
| [깃 파일 권한 문제] warning: unable to access … Permission denied (1) | 2024.09.21 |