[#4] Spring에서 중복되는 로그인 체크 기능을 AOP로 분리하기
마이 페이지에서 회원의 정보를 확인하거나 회원의 접근권한이 필요한 메소드에서 로그인을 했는지 확인해야하는 로직이 중복되는 문제가 발생하였습니다. AOP 를 적용하기 전에는 이러한 로그인 했는지 확인하는 부가기능이 회원의 권한이 필요한 모든 메소드에서 반복되었습니다. (세션에 로그인 정보가 남아있지 않다면 예외를 던져주고 있다면 진행하는 로직)
예를들어 배달 앱에서 내 프로필 수정하려고 하거나 고객이 가게에서 주문을 요청하는 메소드에서 주문을 하려면 고객으로 로그인이 되어있어야 하기 때문에 로그인 체크 로직이 필요합니다. 이러한 불필요한 반복은 줄여야하고 비즈니스 로직과는 전혀 관련이 없기 때문에 분리해낼 수 있다면 가장 좋은 방법이 될 것입니다.
토비의 스프링을 책을 읽었던 경험으로 AOP를 적용하면 분리해낼 수 있다는 것이 기억나 책을 여러번 다시 읽고 검색을 통해 SPRING AOP를 적용했습니다. 기존 객체지향의 DI와 서비스 추상화 전략만으로는 이러한 코드를 완벽히 분리해내기는 힘들어 새로운 관점의 AOP방식이 있는 것입니다.
AOP는 어노테이션, 파라미터, 주소등 여러 곳에 적용 해 줄 수 있습니다. 이제부터 @LoginCheck 어노테이션을 메소드 위에 붙여주기만 하면 적용되도록 바꿔보겠습니다.
우선 다음과 같이 @LoginCheck 커스텀 어노테이션을 만들어줍니다. Method위에 적용할 것이기 때문에 Target에는 Method를 적용하고 SPRING AOP 는 Run Time에 Weaving을 해주기 때문에 Retention은 Runtime으로 지정했습니다.
(사실 프록시 객체는 런타임에 생성하지만 런타임에 그 프록시를 생성할 타겟 빈을 찾을때 클래스정보(바이트 코드)를 참고하기 때문에 런타임까지 해당 어노테이션을 유지할 필요는 없습니다. Retention을 클래스까지로해도 똑같이 AOP적용이 가능합니다)
그 다음 @Aspect 클래스를 다음과 같이 빈으로 등록해줘야 합니다.
그 다음 @Before 어노테이션(포인트컷)을 사용하여 커스텀 어노테이션 @LoginCheck가 달린 메소드 시작 전(Before)에 Advice가 적용되도록 포인트컷을 적용해줍니다. 여기서 Advice는 세션에 아이디가 있다면 로직을 수행하고 없다면 401 에러를 보내주는 것입니다.
@DeleteMapping
@LoginCheck
public ResponseEntity<Void> deleteUser(@CurrentUserId String userId) {
userService.deleteUser(userId);
loginService.logoutUser();
return RESPONSE_OK;
}
이제 SPRING AOP를 적용하여 컨트롤러 메소드 위에 @LoginCheck를 붙혀 메소드 실행전 로그인 했는지 체크하는 로직이 실행 되도록 만들었습니다. 비즈니스 로직과는 관련 없는 부가기능을 메소드 전에 체크하고 로그인이 안되어있으면 바로 Error를 보내주는 것입니다.
부록
@EnableAspectJAutoProxy: AOP를 RTW인 다이나믹 프록시 방식으로 사용가능하게 해줍니다.
@SpringbootAplication: 어노테이션 안에 @EnableAutoConfiguration이 있고 이 어노테이션은 스프링부트가 클래스패스에서 찾은 빈들을 설정하게 해줍니다. 따라서 스프링이 @Aspect를 보고 프록시방식으로 사용하게 해주기 때문에
@EnableAspectJAutoProxy가 생략가능합니다.
프로젝트 url
https://github.com/f-lab-edu/make-delivery
참고자료