최근 프로젝트에서 JWT 토큰의 정보를 인증 객체에 저장 후 사용할 때 다음과 같이 SecurityContextHolder에서 인증 객체를 직접 가져오는 코드를 사용했었다.
fun getUserDetails(): CustomUserDetails {
val principal = SecurityContextHolder.getContext().authentication.principal
return if (principal is CustomUserDetails) principal else null
}
물론 직접 가져오는 방법 외 다음과 같이 메서드에 @AuthenticationPrincipal 어노테이션을 사용해 인증 객체를 가져오는 방법도 있다.
@PutMapping("/{todoId}")
fun updateTodo(
@AuthenticationPrincipal user: CustomUserDetails,
@PathVariable todoId: Long,
@RequestBody updateTodoRequest: UpdateTodoRequest
): ResponseEntity<TodoResponse> {
println("********** user: ${user.getUserId()}")
return ResponseEntity.status(HttpStatus.OK).body(todoService.updateTodo(todoId, updateTodoRequest))
}
인증 객체에 담긴 정보를 가져오다가 궁금한 점이 생겼다.
권한 없이 JWT의 토큰에 유저의 ID만 담는 경우 굳이 UserDetails 타입으로 인증 객체에 저장할 필요가 있을까 싶어 UsernamePasswordAuthenticationToken에 단순히 userId만 전달했다.
class JwtCheckFilter(
private val jwtUtil: JwtUtil
) : OncePerRequestFilter() {
// ...
override fun doFilterInternal(
request: HttpServletRequest,
response: HttpServletResponse,
filterChain: FilterChain,
) {
try {
// ...
// Access Token에 포함된 UserId를 가져와 인증 객체 생성
val authenticationToken = UsernamePasswordAuthenticationToken(userId, null)
SecurityContextHolder.getContext().authentication = authenticationToken
filterChain.doFilter(request, response)
} catch (e: Exception) {
// ...
}
}
}
디버그 모드로 확인해 보면 다음과 같이 principal에 유저의 ID값인 1만 저장됐음을 알 수 있다.
이제 값이 잘 전달되는지 확인하기 위해 다음 디버그 포인트로 이동하려는데 catch 구문으로 넘어가면서 예외 메시지를 보게 됐다.
Controller의 파라미터 문제이기 때문에 Controller까지 이동 후 예외가 발생할 것이라 생각했는데 Filter 부분에서 바로 예외가 발생한다는 점은 처음 알게 되었다.
NullPointException이 발생했다는 이야기는 Controller의 CustomUserDetails 타입의 파라미터의 값이 null이 된다는 말이다.
그러면 인증 객체에는 단순 정수값만 저장하고 사용할 수는 없는 건가 싶지만 타입을 Long으로 변경하면 다음처럼 user 파라미터에 유저의 ID값이 들어간 것을 볼 수 있다.
loadUserByUsername도 보통 이메일로 회원정보를 가져오는 로직을 사용하지만 다음처럼 토큰에 담긴 유저의 ID를 파라미터로 사용할 수도 있다.
loadUserByUsername의 파라미터 타입은 변경할 수 없기 때문에 일단 문자열로 받아와 .toLong()을 사용해 값을 변환해서 사용하면 된다.
@Service
class UserDetailServiceImpl(
private val userRepository: UserRepository
) : UserDetailsService {
override fun loadUserByUsername(userId: String): UserDetails {
val user = userRepository.findByIdOrNull(userId.toLong()) ?: throw UsernameNotFoundException(userId)
return CustomUserDetails(user)
}
}
'TIL(Today I Learned)' 카테고리의 다른 글
Windows에서 Docker 설치 (0) | 2024.06.27 |
---|---|
TIL - Entity의 Setter 사용을 지양하기 (0) | 2024.06.19 |
TIL - Spring Boot에서 Filter 내 발생하는 Exception 처리 (0) | 2024.06.14 |
TIL - 비밀번호 수정 요청 시 기존 비밀번호를 사용하지 못하게 하기 (0) | 2024.06.13 |
TIL - Spring Security Filter (0) | 2024.06.12 |