@AuthenticationPrincipal 커스텀해서 사용하기

박진형·2022년 9월 3일
1

이전에 프로젝트 진행 중 @AuthenticationPrincipal을 사용 중 @AuthenticationPrincipal을 커스텀해서 사용해야하는 문제가 있었고 어떻게 해결했는지를 공유합니다.

식별

@AuthenticationPrincipal로 Principal 객체 사용 시 NullPointerException 발생

분석 및 개선 방향

patch /api/users에 대한 permit all 테스트 용으로 설정해두고 해제하지 않고 Controller단에서는 인증된 인증객체의 Principal을 사용하고 있는 버그였습니다.

  • JwtAuthentication에 바인딩을 해야하지만 AnonymousAuthenticationToken 객체의 principal은 String타입의 “anonymousUser” 이므로 정상 바인딩할 수 없어 null로 남음
  • auth가 null 이므로 auth.id() 사용시 NPE 발생

우선은 permit all 설정을 하면 안되는 API end-point에 permit all 설정을 해줬지만, 실수를 하던 하지 않던 아래와 같이 Controller단에서 NULL체크를 통한 2차적인 검증이 필요 하다고 판단했습니다.

하지만 Principal 객체를 사용하는 모든 API end-point에 대하여 같은 NULL체크 구문을 작성하는 것은 번거롭다고 느껴 이를 개선할 필요가 있었습니다.

AuthenticationPrincipal은 사용시 SecurityContextHolder에서 Principal을 객체를 가져와 바인딩해주는식으로 동작할것이라고 예상했습니다. 그렇다면 바인딩이 된다면 바인딩 시 예외 발생도 가능할 것으로 예상하고 코드를 분석했습니다.

AuthenticationPrincipal 어노테이션AuthenticationPrincipalArgumentResolver를 사용하고 있었습니다. 이 Resolver는 다음과 같이 인증 정보가 없거나, errorOnInvalidType이 true일 시 타입이 맞지 않으면 ClassCastException을 발생시킵니다.

해결

처음 해결방안은 errorOnInvalidType 옵션을 true로 설정하는 것 입니다.

해당 옵션을 true로 설정하면 ClassCastException이 발생하므로 타입 검증을 할 수 있지만 ClassCastException은 너무 광범위한 Exception이라 “인증 되지 않았음"을 나타내기는 부족하다고 생각 되었습니다. 또한, 프로젝트에서는 ErrorCode로써 예외들을 관리하고 있기 때문에 커스텀 Exception을 사용해야 했습니다.

  • 커스텀 Exception을 사용하지 않을 시 ClassCastException을 인증 정보없음으로 단정지을 순 없으므로 어떤 문제 상황인지 정확한 식별이 불가능

두번째 해결방안은 ArgumentResolver를 커스텀하는 것 입니다.

이미 분석단계에서 예외를 발생시킬 수 있다는 것을 확인했고, 원하는 예외를 발생시킬 수도 있다고 판단했습니다. 그래서 다음과 같은 @AuthUser과 CustomAuthenticationPrincipalArgumentResolver를 작성했습니다.

인증정보가 null이거나 타입이 맞지 않을시 NotAuthenticationException을 발생시켜 아래와 같이 ControllerAdvice에서 구체적인 예외로 처리할 수 있게 되었습니다. 또한 @AuthenticationPrincipal 이라는 긴 어노테이션명 대신 @AuthUser라는 간결한 네이밍을 사용할 수 있었습니다.

  • 변경 전 (중복 코드, 긴 어노테이션 명, 불명확한 에러 식별)

  • 변경 후 (중복 코드 제거, 간결한 어노테이션 명, 명확한 에러 식별)

0개의 댓글