스프링 시큐리티에는 @AuthenticationPrincipal이라는 어노테이션이 있습니다. 이 어노테이션은 Authentication.getPrincipal()을 실행하고 결과를 핸들러 메서드 인수로 넣어줍니다.

Annotation that is used to resolve Authentication.getPrincipal() to a method

getPrincipal의 반환 타입이 변경된다면, principal을 사용하는 곳의 타입을 변경해 주어야 합니다. 하지만 @AuthenticationPrincipal을 사용하면 컴파일러가 타입에 대해 경고해 주지 않습니다. 실제로 코드가 호출될 때만이 문제가 발생한다는 것을 발견할 수 있습니다. 예를 들어 다음과 같은 예제가 있다고 해보겠습니다.

fun handleProjectCreate(
    authentication: UserAuthentication,
    @RequestBody request: ProjectCreateRequest
): Project {
    val userId: Long = authentication.principal

    return projectCreateService.create(
        userId = userId,
        name = request.name,
        description = request.description,
        domain = request.domain
    )
}

@AuthenticationPrincipal은 위 설명과 같이 Authentication.getPrincipal()을 한 값을 함수 argument로 추가해 주어서 val userId = authentication.principal를 안 해도 돼서 더 편할 수 있습니다.

fun handleProjectCreate(
    @AuthenticationPrincipal userId: Long,
    @RequestBody request: ProjectCreateRequest
): Project {
    return projectCreateService.create(
        userId = userId,
        name = request.name,
        description = request.description,
        domain = request.domain
    )
}

하지만 만약 Authentication.getPrincipal이 다음과 같이 변경하여 반환 타입이 변경되면 어떻게 될까요?

class UserAuthentication(
    private val id: Long
) : AbstractAuthenticationToken(authorities()) {
    // ... 생략

    override fun getPrincipal(): User = user // 반환 타입이 Long에서 User로 바뀜
    
    // ... 생략
}

getPrincipal의 응답 타입이 변경되어, 더 이상 getPrincipal의 응답 타입이 Long이 아닙니다. 따라서 컴파일 타임에 타입에 대해서 에러가 발생해야 합니다. 하지만 실제로는 컴파일러는 아무런 경고도 해주지 않습니다. 이 에러는 실제로 API 호출하여 코드가 실행될 때 에러가 발생합니다.

java.lang.IllegalArgumentException: null
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
# ... 생략

만약 그래도 authentication: UserAuthentication를 사용하고 val userId = authentication.principal 이렇게 사용한다면 userId를 사용하는 곳에서 타입이 맞지 않는다고 컴파일러가 아주 빠르게 피드백을 줍니다

참고