글 작성자: 개발섭

TL; DR

없다 같은것이기 때문에 크게 신경쓰지 말자 타입캐스팅도 자동으로 되기때문에 굳이 서비스 로직이아니라면 Parameter화시키는게 더 편하다.

왜 궁금했나?

@CreatedBy@LastModifiedBy를 사용하는 경우 Auditor를 구성하기위해 implements AuditorAware<User> 과 같은 Aware를 이용하게되는데, 개발시 스프링에서 제공하는 예시를 보더라도...

class SpringSecurityAuditorAware implements AuditorAware<User> {

  public User getCurrentAuditor() {

    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

    if (authentication == null || !authentication.isAuthenticated()) {
      return null;
    }

    return ((MyUserDetails) authentication.getPrincipal()).getUser();
  }
}

getPrincipal을 빼서 쓰는데 사실 @AuthenticationPrincipal를 사용하는 방식 자체가 대략 비슷해보이긴 했었다. 그 둘의 차이가 있는지 싶어서 @AuthenticationPrincipal의 원리를 파악해서 같은건지 아니면 다른 영역의 것인지 알아 보려고 했다.

원리를 파고 들어가보자

일단 어노테이션이기 때문에, 해당하는 어노테이션이 어떤식으로 쓰이는지를 먼저 확인해야한다. → @AuthenticationPrincipal를 먼저 찾아보자
![[스크린샷 2024-10-25 오후 2.56.17.png]]
를 확인해보면 AuthenticationPrincipalArgumentResolver를 확인해볼 수 있다. 결국 어노테이션 밖에서 사용하기 위해서는 Controller의 argument를 인식하는 Resolver가 필요하기때문에...
그 해당하는 Resolver를 파악해보자.

해당하는 코드의 105번째줄에 다음과 같은 응답이 있다.

@Override  
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,  
       NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {  
    //우리가 유의해서 봐야할 부분은 다음이다. 
    Authentication authentication = this.securityContextHolderStrategy.getContext().getAuthentication();  
    if (authentication == null) {  
       return null;  
    }
    //그리고 이걸 바로 가져다 쓰는 구조다  
    Object principal = authentication.getPrincipal();  
    AuthenticationPrincipal annotation = findMethodAnnotation(AuthenticationPrincipal.class, parameter);  
    String expressionToParse = annotation.expression();  
    if (StringUtils.hasLength(expressionToParse)) {  
       StandardEvaluationContext context = new StandardEvaluationContext();  
       context.setRootObject(principal);  
       context.setVariable("this", principal);  
       context.setBeanResolver(this.beanResolver);  
       Expression expression = this.parser.parseExpression(expressionToParse);  
       principal = expression.getValue(context);  
    }  
    if (principal != null && !ClassUtils.isAssignable(parameter.getParameterType(), principal.getClass())) {  
       if (annotation.errorOnInvalidType()) {  
          throw new ClassCastException(principal + " is not assignable to " + parameter.getParameterType());  
       }  
       return null;  
    }  
    return principal;  
}

코드에서도 보이지만, 실제로 getAuthentication()하는 부분과 완전히 동일하다. 혹시 다른부분이 있을까 했지만 딱히 그런 부분은 보이지 않았다. 물론, 이 아규먼트에서도 재밌는 부분은 있었다. 주석에 예시로 달려있는 부분들은 꽤 코드짤때 도움이 되보였다.

@AuthenticationPrincipal를 커스터마이징시 어노테이션 자체에 붙여서 써버리면 CurrentUser 아님을 확인하는 과정이 없어도 문제는 없어보였다
다음처럼 사용하면

  @Target({ ElementType. PARAMETER })
  @Retention(RetentionPolicy. RUNTIME)
  @AuthenticationPrincipal
  public @interface CurrentUser {
  }
/// (@CurrentUser CustomUser customUser) 이렇게 표현하기 좋다

이런형태로 쓰기 좋기 때문에 결론적으로는 해당하는 어노테이션을 만들어도 좋을거 같았다.

결론

완전 동일함을 확인했고, Holder를 뽑아서 사용하는 케이스가 아니라면 파라미터로 전달받아서 사용하는 게 좋아 보였다.

출처

  1. https://wildeveloperetrain.tistory.com/324 → 쓰려는 내용보다 더 깊게 들어갔다. 더 자세한 원리를 파고 싶다면 확인해보자
  2. https://codevang.tistory.com/273