본문 바로가기

Language/Spring Security

메서드 기반 Custom AuthorizationManager 구현

728x90
  • 사용자 정의 AuthorizationManager 를 생성함으로 메서드 보안을 구현 가능

설정 클래스 정의

@EnableMethodSecurity(prePostEnabled = false) // 시큐리티가 제공하는 클래스들을 비활성화 한다. 그렇지 않으면 중복해서 검사하게 된다
@Configuration
public class MethodSecurityConfig {
	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public Advisor preAuthorize() {
		return AuthorizationManagerBeforeMethodInterceptor.preAuthorize(new MyPreAuthorizationManager());
	}
	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public Advisor postAuthorize() {
		return AuthorizationManagerAfterMethodInterceptor.postAuthorize(new MyPostAuthorizationManager());
	}
}
  • @EnableMethodSecurity(prePostEnable=false) 설정 필요
    • 시큐리티가 제공하는 클래스들을 비활성화
    • true로 할 경우 @Pre/PostAuthorize, @Pre/PostFilter 총 4가지 클래스가 활성화되어 중복 검사를 하게 됨

 

사용자 정의 AuthorizationManager 구현

  • 사용자 정의 AuthorizationManager는 여러 개 추가할 수 있으며 그럴 경우 체인 형태로 연결되어 각각 권한 검사를 하게 됨
public class MyPreAuthorizationManager implements AuthorizationManager<MethodInvocation>{
	@Override
	public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocation invocation) {
		return new AuthorizationDecision(authentication.get().isAuthenticated());
	}
}
public class MyPostAuthorizationManager implements AuthorizationManager<MethodInvocationResult>{
	@Override
	public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocationResult result) {
		Authentication auth = authentication.get();
		
		if(auth instanceof AnonymousAuthenticationToken) return new AuthorizationDecision(false);
		
		Account account = (Account) result.getResult();
		
		boolean isGranted = account.getOwner().equals(authentication.get().getName());
		
		return new AuthorizationDecision(isGranted)
	}
}
  • PostAuthorize에서 AuthorizataionManager<>의 제네릭 타입은 MethodInvocationResult
    결과값을 가지고 권한 심사를 해야하기 때문
  • 인증 객체를 가지고와서 Anonymous(익명 사용자)라면 거부
  • Result로 Account 객체를 반환 받아 owner가 같은지 다른지 비교하여 인가 결정

인터셉터 순서 지정

public enum AuthorizationInterceptorsOrder {
	FIRST(Integer.MIN_VALUE),
	PRE_FILTER, // 100
	PRE_AUTHORIZE, // 200
	SECURED, // 300
	JSR250, // 400
	POST_AUTHORIZE, // 500
	POST_FILTER, // 600
	LAST(Integer.MAX_VALUE);
}
  • 메서드 보안 어노테이션에 대응하는 AOP 메소드 인터셉터들은 AOP 어드바이저 체인에서 특정 위치를 차지
  • 구체적으로 @PreFilter 메소드 인터셉터의 순서는 100, @PreAuthorize의 순서는 200 등으로 설정
  • 이것이 중요한 이유는 @EnableTransactionManagement와 같은 다른 AOP 기반 어노테이션들이 Integer.MAX_VALUE로 순서가 설정되어 있는데 기본적으로 이들은 어드바이저 체인의 끝에 위치
  • 만약 스프링 시큐리티보다 먼저 다른 어드바이스가 실행 되어야 할 경우, 예를 들어 @Transactional 과 @PostAuthorize 가 함께 어노테이션 된 메소드가 있을 때 @PostAuthorize가 실행될 때 트랜잭션이 여전히 열려있어서 AccessDeniedException이 발생하면 롤백이 일어나게 할 수 있음
    → @PostAuthorize가 우선 순위를 가지기 때문에 @Transaction이 실행되지 않아 롤백이 일어나지 않음
  • 따라서, 메소드 인가 어드바이스가 실행되기 전에 트랜잭션을 열기 위해서는 @EnableTransactionManagement의 순서 설정 필요
    • @EnableTransactionManagement(order = 0)
  • 위의 order = 0 설정은 트랜잭션 관리가 @PreFilter 이전에 실행되도록 하며 @Transactional 어노테이션이 적용된 메소드가 스프링 시큐리티의 @PostAuthorize 와 같은 보안 어노테이션보다 먼저 실행되어 트랜잭션이 열린 상태에서 보안 검사가 이루어지도록 설정 가능
    → 이러한 설정은 트랜잭션 관리와 보안 검사의 순서에 따른 의도하지 않은 사이드 이펙트를 방지 가능
  • AuthorizationInterceptorsOrder를 사용하여 인터셉터 간 순서를 지정 가능

 

728x90