로그인 확인 및 유저 정보 가져오기 AOP 적용

joona95·2024년 8월 22일

문제 상황

	@PostMapping
    public void post(@ApiIgnore final Authentication authentication) {

        if (authentication == null)
            throw new UserTokenNotExistException();

        User user = ((SecurityUser) authentication.getPrincipal()).getUser();

        ...
    }

로그인이 필요한 API 마다 위와 같은 코드가 중복적으로 적용이 돼있었다.

AOP 적용을 통해 어노테이션 적용만으로 중복 코드를 제거하고자 했다.

해결 방안

package com.recipe.app.src.common.aop;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LoginCheck {

}
  • 커스텀한 어노테이션을 생성

  • @Retention 으로 어노테이션이 언제까지 살아남을 것인지 정하고, @Target 으로 어노테이션이 어디에 적용될지를 정함

    • 로그인이 필요한 특정 API에 적용할 것이므로 @Target 은 메소드 범위로 적용
    • 스프링 AOP는 런타임 시 프록시를 동적으로 생성하므로 @Retention 을 런타임 시까지 유지
@Component
@Slf4j
@Aspect
public class LoginCheckAspect {

    @Around("@annotation(com.recipe.app.src.common.aop.LoginCheck)")
    public Object loginCheck(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        
        if (authentication == null || !(authentication.getPrincipal() instanceof SecurityUser)) {
            throw new UserTokenNotExistException();
        }
        
        User user = ((SecurityUser) authentication.getPrincipal()).getUser();

        log.info("Login User Id : " + user.getUserId());

        Object[] args = Arrays.stream(proceedingJoinPoint.getArgs())
                .map(arg -> {
                    if (arg instanceof User) {
                        return user;
                    }
                    return arg;
                })
                .toArray();

        return proceedingJoinPoint.proceed(args);
    }
}
  • @Aspect 어노테이션으로 부가기능을 제공하는 Aspect 클래스 작성

    • 포인트컷(Pointcut)과 어드바이스(Advice)로 구성된 어드바이저(Advisor)의 생성을 편리하게 해주는 기능을 가짐

      • 포인트컷: 어떤 메서드가 어드바이스를 적용받을지를 결정하는 일종의 필터
      • 어드바이스: 프록시가 적용할 부가기능 로직
    • Aspect 클래스를 Aspect 로 사용하려면 Bean 으로 등록해야하므로 @Component 작성 필요

  • @Around("@annotation(com.recipe.app.src.common.aop.LoginCheck)") 설정으로 커스텀한 어노테이션에 AOP 적용

  • Advice 정의하는 어노테이션으로는 @Before, @After, @Around, @AfterReturning, @AfterThrowing 존재

    • @Before: 타겟 호출 전에 공통 기능을 실행
    • @After: 예외 발생 여부에 상관없이 타겟 실행 후 공통 기능을 실행
    • @AfterReturning: 타겟이 예외 없이 실행된 이후에 공통 기능을 실행
    • @AfterThrowing: 타겟이 실행 도중 예외 발생한 경우에 공통 기능을 실행
    • @Around: 타겟 실행 전, 후 또는 예외 발생 시점에 공통 기능을 실행
  • @Around는 다른 포인트컷과 달리 ProceedingJointPoint라는 구현체를 사용하는데, 이 구현체를 통해 자바의 리플렉션 기능을 사용하여 타겟 메소드 호출 가능

    • Authentication 으로 로그인 여부를 확인하고, 로그인한 경우 얻은 User 클래스를 파라미터로 전달 가능하기 때문에 @Around 를 사용
 	@PostMapping
    @LoginCheck
    public void post(@ApiIgnore User user) {

        ...
    }
  • 적용할 컨트롤러 API 메소드에 커스텀하게 작성한 어노테이션 @LoginCheck 를 추가

    • @PostMapping 위가 아니라 적용할 특정 메소드의 바로 위에 어노테이션을 적용해줘야 함
  • AOP 적용을 통해 응답값으로 넘겨줄 User 클래스를 메서드의 매개변수로 받도록 설정

0개의 댓글