개발자
프로젝트를 진행하던 중, 해당 펫이 api를 요청하는 유저가 소유하고 있는 펫이 정말 맞는지 확인하는 권한 검사 로직 등이 중복으로 발생해서 이를 비즈니스 로직에서 분리하고자 스프링 AOP 기술을 활용해보려고 했습니다. 동작은 잘 되지만.. Pet을 조회하는 쿼리가 중복해서 2번이 발생하더군요. 저는 OSIV가 꺼져있더라도, 비즈니스 레이어에서 같은 트랜잭션 내에서 동작하기에 AOP에서 조회한 엔터티도 같은 영속성 컨텍스트에서 관리될 거라고 생각하고 위처럼 작성했는데 어디가 문제인지 모르겠어서 도움을 부탁드립니다.. ㅠㅠ 혹시 가능하시다면 현업에서는 중복되는 권한 체크 로직을 어떻게 관리하시는지도 알려주시면 큰 배움이 될 것 같습니다!
1@Aspect
2@Component
3@RequiredArgsConstructor
4public class MyPetOrAdminAspect {
5
6 private final PetRepository petRepository;
7
8 @Pointcut("@annotation(com.maruhxn.todomon.core.global.auth.checker.IsMyPetOrAdmin)")
9 public void isMyPetOrAdminPointcut() {
10 }
11
12 @Before("isMyPetOrAdminPointcut() && args(memberId, petId,..)")
13 public void checkIsMyPetOrAdmin(Long memberId, Long petId) {
14 if (memberId == null | petId == null) return;
15
16 Optional<Pet> memberPet = petRepository.findOneByIdAndMember_Id(petId, memberId);
17
18 if (!hasAdminAuthority() && memberPet.isEmpty()) {
19 throw new ForbiddenException(ErrorCode.FORBIDDEN);
20 }
21 }
22
23 private boolean hasAdminAuthority() {
24 return getPrincipal().getAuthorities().contains(new SimpleGrantedAuthority(ROLE_ADMIN.name()));
25 }
26
27 private TodomonOAuth2User getPrincipal() {
28 Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
29 return (TodomonOAuth2User) authentication.getPrincipal();
30 }
31}
32
33
34
35// PetService.deletePet
36
37@Transactional
38 @IsMyPetOrAdmin
39 public void deletePet(Long memberId, Long petId) {
40 Pet findPet = petRepository.findOneByIdAndMember_Id(petId, memberId)
41 .orElseThrow(() -> new NotFoundException(ErrorCode.NOT_FOUND_PET));
42
43 Member findMember = memberRepository.findById(memberId)
44 .orElseThrow(() -> new NotFoundException(ErrorCode.NOT_FOUND_MEMBER));
45
46 findMember.getRepresentPet().ifPresent(representPet -> { // 삭제하려는 펫이 대표 펫이었을 경우, 대표펫을 null로 설정
47 if (representPet.getId().equals(petId)) {
48 findMember.setRepresentPet(null);
49 }
50 });
51
52 petRepository.delete(findPet);
53 }
답변 1
1. memberPet.isEmpty() 는 권한 검사라는 pointcut 역할과 관계가 없습니다. 2. Aspect에서 조회한 Pet 객체를 본 메소드에서도 재사용하고자 한다면 Before 대신 Around 를 이용해 argument로 넘기는 것도 생각해볼 수 있지 않을까요?
고지완
작성자
백엔드 취준 • 2024년 09월 16일
헉 감사합니다.. 아직 AOP를 프로젝트에 적용해본 적이 없어서 공부하느라 답변이 늦었네요! Before 대신 Around를 사용하니 영속성 컨텍스트가 잘 유지되었습니다! 다만, 권한 검사 애노테이션이 컨트롤러쪽이 아닌 서비스 레이어 쪽에 있어서 서비스까지 내려와야 권한이 필요함을 확인할 수 있다는게 조금 걸리네요..
지금 가입하면 모든 질문의 답변을 볼 수 있어요!
현직자들의 명쾌한 답변을 얻을 수 있어요.
이미 회원이신가요?
커리어리 AI 봇
GPT가 달아주는 답변입니다.
2024년 09월 13일
일단, 제안해주신 코드에서 보면 권한 검사 로직(AOP)가 진행되는 시점과 실제 비즈니스 로직이 동작하는 시점 사이에 트랜잭션의 경계가 존재합니다. `checkIsMyPetOrAdmin` 메소드와 `deletePet` 메소드 간에 별개로 트랜잭션이 처리되기 때문에 영속성 컨텍스트도 분리되어 있습니다. 이로 인해 두 개의 서로 다른 세션(각각의 트랜잭션에서 독립적으로 생성된)을 사용하게 되어 조회 쿼리가 중복 실행되는 문제가 발생하는 것입니다. AOP 관점에서 전체 작동 부분을 한 번에 트랜잭션으로 묶는 방식으로 접근해보세요. 이를 위해서 스프링의 `@Transactional` 애노테이션을 `@Aspect` 클래스에 적용하여 전체 AOP 작동 부분이 하나의 트랜잭션으로 처리될 수 있도록 구현할 수 있습니다. ```java @Transactional @Aspect @Component @RequiredArgsConstructor public class MyPetOrAdminAspect { ... } ``` 또한 권한 체크 로직을 상위 Service Layer 에서 처리하고, 하위 Business Logic 은 실제 비즈니스 로직만 담당하도록 하는 것이 이상적입니다. 권한 체크는 비즈니스 로직의 일부가 아니라 선행되어야 하는 보안 측면의 작업이므로 이를 분리하는 것이 좋습니다. 다만, 현업에서는 권한 체크 로직을 어떻게 관리할지는 대단히 다양한 방법들이 있습니다. 스프링 시큐리티를 활용하여 URL 레벨에서의 권한 관리를 하는 곳도 있고, 서비스 메소드 내에서 또는 AOP를 통해 검사를 실시하기도 합니다. 이런 부분은 결국 팀의 코드 컨벤션과 프로젝트의 요구 사항에 따라 달라집니다.
지금 가입하면 모든 질문의 답변을 볼 수 있어요!