문제점
- 초창기에는 Controller 단에서 사용자 인증에 대한 2차적인 검증이 필요하다고 느꼈습니다.
- 해당 과정이 필요한 이유는 ContextHolder에 있는 사용자의 Id를 추출하기 위해 사용했습니다.
- 하지만 Controller 코드에 매번 if(principal == null) {} 구문을 넣는 것이 비효율 적이라고 느꼈습니다.

해결

- 해당 중복된 코드를 개선하기 위해 ArgumentResolver와 커스텀한 어노테이션을 기반으로 바인딩 할 수 있도록 하여 해당 코드를 중복으로 작성하지 않아도 됬습니다.
추가 고려사항
@AuthenticationPrincipal에서 제공해주는 ClassCastException예외가 너무 추상적이기 떄문에 디버깅시 당황 스러울 수 있다고 판단했습니다. 그래서 커스텀한 Resolver를 적용하고 NotAuthenticationException이 발생하도록 구현했습니다.
이펙티브 자바 ITEM73에서 “추상화 수준에 맞는 예외를 던져라” 라는 가이드가 있습니다. 해당 서적에서는 “관련 없어 보이는 예외가 나오면 당황 스러울 수 있다. “ 라는 알게 된 내용을 적용해봤습니다.
과정
기존의 CastException을 해결하기 위해 고려했던 방안이 있었습니다.
기존의 errorOnInvalidType으로 null 체크를 해주면 되었지만 그렇게 하지 않았습니다.

시큐리티에서는 AuthenticationPrincipalArgumentResolver를 기본적으로 제공이 되고 있으며 저희가 원하는 중복코드인 null 체크를 제공해주고 CastExeption을 발생해주고 있습니다.

AuthenticationPrincipalArgumentResolver code
public final class AuthenticationPrincipalArgumentResolver implements HandlerMethodArgumentResolver { private ExpressionParser parser = new SpelExpressionParser(); private BeanResolver beanResolver; @Override public boolean supportsParameter(MethodParameter parameter) { return findMethodAnnotation(AuthenticationPrincipal.class, parameter) != null; } @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) { Authentication authentication = SecurityContextHolder.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); } // -> 여기에서 CastException을 자동 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; } /** * Sets the {@link BeanResolver} to be used on the expressions * @param beanResolver the {@link BeanResolver} to use */ public void setBeanResolver(BeanResolver beanResolver) { this.beanResolver = beanResolver; } /** * Obtains the specified {@link Annotation} on the specified {@link MethodParameter}. * @param annotationClass the class of the {@link Annotation} to find on the * {@link MethodParameter} * @param parameter the {@link MethodParameter} to search for an {@link Annotation} * @return the {@link Annotation} that was found or null. */ private <T extends Annotation> T findMethodAnnotation(Class<T> annotationClass, MethodParameter parameter) { T annotation = parameter.getParameterAnnotation(annotationClass); if (annotation != null) { return annotation; } Annotation[] annotationsToSearch = parameter.getParameterAnnotations(); for (Annotation toSearch : annotationsToSearch) { annotation = AnnotationUtils.findAnnotation(toSearch.annotationType(), annotationClass); if (annotation != null) { return annotation; } } return null; } }
하지만 해당 예외는 너무나도 추상적인 큰 범위를 갖고있으며 디버깅시 예외 자체에 담고 있는 의미가 모호하다고 생각했기 때문에 채택하지 않았습니다.
이펙티브 자바 ITEM73에서 “추상화 수준에 맞는 예외를 던져라” 라는 가이드가 있습니다. 해당 서적에서는
관련 없어 보이는 예외가 나오면 당황 스러울 수 있다.
라는 구문이 있습니다. 디버깅이 도움이 될 수 있도록 도모하기 위해서 따로 커스텀한 Resovler를 적용하였으며 NotAuthetnication이 발생하도록 구현했습니다.