이번 게시글에서는 Method Security를 이용한 인가와 관련된 검사에서 open-in-view와 영속성 컨텍스트가 어떠한 영향을 미치는지에 대해 작성하겠다.
Method Security를 이용한 인가와 관련된 검사를 횡단 관심사라고 생각하여 Method Security를 이용하여 검사하도록 구현하였다. 예시는 아래와 같다.
폴더 구조를 통해 볼 수 있듯이, Evaluator라는 추상 클래스를 상속받아 인가와 관련된 검사를 수행하면 된다.
public abstract class Evaluator {
public final boolean check(Long id) {
return hasRole(getRoleTypes()) || isEligible(id);
}
abstract protected List<RoleType> getRoleTypes();
abstract protected boolean isEligible(Long id);
private boolean hasRole(List<RoleType> roleTypes) {
return PrincipalHandler.extractMemberRoles().containsAll(roleTypes);
}
}
Evaluator 추상 클래스에서는 인가와 관련된 기본적인 검사를 제공한다.
@Component
@RequiredArgsConstructor
public class ApiKeyAccessEvaluator extends Evaluator {
private final ApiKeyRepository apiKeyRepository;
private static final List<RoleType> roleTypes = List.of(RoleType.ROLE_ADMIN);
@Override
protected List<RoleType> getRoleTypes() {
return roleTypes;
}
@Override
protected boolean isEligible(Long id) {
ApiKey apiKey = apiKeyRepository.findById(id).orElseThrow(ApiKeyNotFoundException::new);
return apiKey.getMember().getId().equals(PrincipalHandler.extractId());
}
}
Evaluator 추상 클래스를 상속한 인가와 관련된 검사를 수행하는 클래스에서는 실제 인가 검사를 수행한다.
@PreAuthorize("@apiKeyAccessEvaluator.check(#id)")
public ApiKeyDto read(@Param("id")Long id) {
ApiKey apiKey = apiKeyDomainService.getApiKey(id); // 영속성 컨텍스트에서 들고와 불필요한 Query 최소화
return new ApiKeyDto(apiKey.getId(), apiKey.getValue(), apiKey.getGrade().getGradeType(),
apiKey.getPaymentFailureBannedAt(), apiKey.getCardDeletionBannedAt(), apiKey.getCreatedAt());
}
비지니스 로직에서는 @PreAuthorize를 이용하여 인가 검사를 수행한다.
기본적인 구현은 끝났다. 생각해보아야 할 점이 몇 가지 있는데, Method Security를 이용하여 인가를 검증할 때는 영속성 컨텍스트에 대해 고려해야 한다. Method Security와 비즈니스 로직에서의 트랜잭션이 다를 수 있기 때문에 영속성 컨텍스트가 다를 수 있으며, 이로 인해 문제가 발생할 수 있다. Method Security를 이용한 인가 검증 시 발생할 문제를 매번 고려하기보다는 영속성 컨텍스트를 유지하는 것이 성능적, 구현 속도적인 측면에서 유리하다. 어떻게 영속성 컨텍스트를 유지할 수 있을까?
앞에서 언급한 영속성 컨텍스트 문제를 해결하기 위한 방법중에 하나가 open-in-view를 사용하는 것이다.
open-in-view를 이용하여 영속성 컨텍스트가 트랜잭션 범위를 넘어선 레이어까지 살아있도록 구현하는 것이다.
open-in-view를 사용하면 Method Security와 비즈니스 로직의 트랜잭션이 다르지만, 동일한 영속성 컨텍스트를 사용할 수 있어 문제를 고려할 필요가 없다. 하지만 open-in-view의 문제점인 데이터베이스 커넥션 문제 때문에 사용하지 않는 경우도 있다. 이를 해결하는 방법을 추천한다.
비즈니스 로직에서 처리하는 @PreAuthorize도 AOP이고, @Transactional도 AOP이다. 따라서 AOP의 순서를 제어하면 @Transactional이 먼저 실행되고, @PreAuthorize에서 인가 검사 후 비즈니스 로직이 실행된다. 이렇게 하면 open-in-view 없이도 같은 트랜잭션이기 때문에 동일한 영속성 컨텍스트를 사용할 수 있다.
@Configuration
@EnableTransactionManagement(order = 0)
public class TransactionConfig {
}
구현 방법은 간단하다. @Transactional의 순서를 0으로 지정하면 된다.
로그에서 볼 수 있듯이 비즈니스 로직 트랜잭션이 실행된 후, 처음 실행한 인가 검증을 위한 findById가 실행되고, 비즈니스 로직에서의 findById가 실행된다. findById의 경우 영속성 컨텍스트에서 1차적으로 조회하기 때문에 select 쿼리도 나가지 않은 모습이다. 중요한 점은 AOP의 순서를 조정하여 트랜잭션이 먼저 실행되도록 하여 Method Security와 비즈니스 로직이 동일한 트랜잭션과 영속성 컨텍스트를 사용할 수 있게 되었다는 것이다.