아래와 같이 어노테이션을 선언했다.
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("hasRole('ROLE_USER')")
public @interface PreAccountAuthorize {
boolean checkBanInfo() default false; //제재 여부 체크
}
해당 타겟이 annotation type이며 실행 시 annotation이 사라지지 않기 위해 RetentionPolicy는 runtime 으로 지정되어 있다.
해당 어노테이션을 사용하는 부분이 class와 method 라는 선언을 하였다.
기존 인증 체크에 추가적인 조건 체크를 더하기 위해 기존 인증 체크를 추가하였다.
@PreAuthorize("hasRole('ROLE_USER')")
사실 @PreAuthorize를 선언하면 해당 어노테이션에 위 선언들이 있기 때문에 아래와 같이 써도 무방하다.
@PreAuthorize("hasRole('ROLE_USER')")
public @interface PreAccountAuthorize {
boolean checkBanInfo() default false; //제재 여부 체크
}
@Slf4j
@Aspect
@Component
public class PreAccountAuthorizeAspect {
@Setter private BanInfoService banInfoService;
@Before("@annotation(preAccountAuthorize)")
public void methodBefore(JoinPoint joinPoint, PreAccountAuthorize preAccountAuthorize) {
checkBefore(joinPoint, preAccountAuthorize);
}
@Before("@target(preAccountAuthorize) && bean(*Controller)")
public void classBefore(JoinPoint joinPoint, PreAccountAuthorize preAccountAuthorize) {
checkBefore(joinPoint, preAccountAuthorize);
}
public void checkBefore(JoinPoint joinPoint, PreAccountAuthorize preAccountAuthorize) {
if (banInfoService == null) {
throw new BlueskyException("banInfoService is not set");
}
checkBanInfo(preAccountAuthorize);
}
private void checkBanInfo(PreAccountAuthorize preAccountAuthorize) {
log.debug("checkBanInfo");
if (!preAccountAuthorize.checkBanInfo()) {
return;
}
List banInfo = banInfoService.checkBanInfo();
log.debug("checkBanInfo banInfo : {}", banInfo);
if (!banInfo.isEmpty()) {
throw new BlueskyException("security.BANNED_ACCOUNT");
}
}
}
@target 을 이용한 class annotation은 전체 bean을 전부 훝어서 해당 어노테이션을 체크하는 듯 하다.
이 경우 spring security 를 사용하면 GlobalMethodSecurityConfiguration 관련해서 에러가 발생한다.
aop 관련 동작을 전체 bean 대상으로 체크하다 security쪽 bean의 non-visible class를 대상으로 cglib 동작이 수행되면서 에러가 발생하는데 이를 막기 위해 controller로 제한하였다.
관련한 에러는 다음과 같다.
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration': Unsatisfied dependency expressed through method 'setObjectPostProcessor' parameter 0: Error creating bean with name 'objectPostProcessor' defined in class path resource [org/springframework/security/config/annotation/configuration/ObjectPostProcessorConfiguration.class]: Initialization of bean failed; nested exception is org.springframework.aop.framework.AopConfigException: Could not generate CGLIB subclass of class [class org.springframework.security.config.annotation.configuration.AutowireBeanFactoryObjectPostProcessor]: Common causes of this problem include using a final class or a non-visible class; nested exception is java.lang.IllegalArgumentException: Cannot subclass final class org.springframework.security.config.annotation.configuration.AutowireBeanFactoryObjectPostProcessor; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'objectPostProcessor' defined in class path resource [org/springframework/security/config/annotation/configuration/ObjectPostProcessorConfiguration.class]: Initialization of bean failed; nested exception is org.springframework.aop.framework.AopConfigException: Could not generate CGLIB subclass of class [class org.springframework.security.config.annotation.configuration.AutowireBeanFactoryObjectPostProcessor]: Common causes of this problem include using a final class or a non-visible class; nested exception is java.lang.IllegalArgumentException: Cannot subclass final class org.springframework.security.config.annotation.configuration.AutowireBeanFactoryObjectPostProcessor