-
Notifications
You must be signed in to change notification settings - Fork 0
@AuthUserId 커스텀 어노테이션 생성 도입기
Jeonghwa Heo edited this page Jun 4, 2023
·
3 revisions
- 비로그인 시
@AuthenticationPrincipal CustomUserDetails userDetails
는 인자에 null값이 들어온다. - 때문에
userDetails.getUserId()
호출 시 null Point Exception이 발생하므로 로그인 유무 판단을 위해 분기처리가 컨트롤러든 서비스든 필요해졌다. - 또한 Id값만 필요함에도 불구하고, 파라미터로 계속 UserDetails 객체 전체를 받아와야하는 비효율성 및 코드중복이 존재하게 된다.
- 아래 코드는 본인이 작성한 글일 경우 조회수를 올리지 않기 위해 작성한 내용이다.
@GetMapping("/post/{postId}")
public PostReadDto getPostByPostId(@PathVariable Long postId, @AuthenticationPrincipal CustomUserDetails userDetails) {
return boardService.getPostByPostId(postId, userDetails);
}
@Transactional
public PostReadDto getPostByPostId(Long postId, CustomUserDetails userDetails) {
Post post = findPostByPostId(postId);
updateViewCount(post, userDetails);
User user = findUserByUserId(post.getUserId());
return PostReadDto.toDto(post, user);
}
private void updateViewCount(Post post, CustomUserDetails userDetails) {
if (userDetails != null && !post.getUserId().equals(userDetails.getUserId())) {
post.updateViewCount();
postRepository.update(post);
}
}
- AuthenticationPrincipalArgumentResolver 클래스
private Object resolvePrincipal(MethodParameter parameter, Object principal) {
...
if (isInvalidType(parameter, principal)) {
if (annotation.errorOnInvalidType()) {
throw new ClassCastException(principal + " is not assignable to " + parameter.getParameterType());
}
return null;
}
return principal;
}
private boolean isInvalidType(MethodParameter parameter, Object principal) {
if (principal == null) {
return false;
}
Class<?> typeToCheck = parameter.getParameterType();
...
return !ClassUtils.isAssignable(typeToCheck, principal.getClass());
}
-
resolvePirincipal
메서드를 보면, 파라미터와 principal의 클래스 타입 비교 후 같지 않으면 null을 던진다.
- 비로그인 사용자의 경우
AnonymousAuthenticationFilter
를 통과하면서 String 타입의 "anonymousUser" 라는 값을 가진 인증객체를 생성하게된다. - AnonymousAuthenticationFilter 클래스
/**
* Creates a filter with a principal named "anonymousUser" and the single authority
* "ROLE_ANONYMOUS".
* @param key the key to identify tokens created by this filter
*/
public AnonymousAuthenticationFilter(String key) {
this(key, "anonymousUser", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"));
}
/**
* @param key key the key to identify tokens created by this filter
* @param principal the principal which will be used to represent anonymous users
* @param authorities the authority list for anonymous users
*/
public AnonymousAuthenticationFilter(String key, Object principal, List<GrantedAuthority> authorities) {
Assert.hasLength(key, "key cannot be null or empty");
Assert.notNull(principal, "Anonymous authentication principal must be set");
Assert.notNull(authorities, "Anonymous authorities must be set");
this.key = key;
this.principal = principal;
this.authorities = authorities;
}
- 비로그인 시 principal는 “anonymousUser” 값을 갖는다는 것을 알았다.
- 따라서 principal가 “anonymousUser”면 null값을 반환하고 아니라면 로그인한 상태이므로 아이디값만 얻어서 던지도록해보자.
- @AuthenticationPrincipal의 expression기능을 이용하면 이를 쉽게 구현할 수 있다.
- 참고로 어노테이션에 expression값이 있다면
SpelExpressionParser
를 사용해서 parser를 진행한 후 값을 return한다.
- 참고로 어노테이션에 expression값이 있다면
- AuthenticationPrincipalArgumentResolver 클래스
private ExpressionParser parser = new SpelExpressionParser();
...
private Object resolvePrincipal(MethodParameter parameter, Object principal) {
...
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);
}
...
return principal;
}
SpelExpressionParser 의 #this variable 및 삼항연산자(If-Then-Else) 기능을 사용함
참고 : https://docs.spring.io/spring-framework/docs/3.0.x/reference/expressions.html
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : #this.getUserId()")
public @interface AuthUserId {
}
@GetMapping("/post/{postId}")
public PostReadDto getPostByPostId(@PathVariable Long postId, @AuthUserId Long loginUserId) {
return boardService.getPostByPostId(postId, loginUserId);
}
참고 : Spring Security @AuthenticationPrincipal의 expression 이용하기