[Spring][Security] AOP를 통한 메서드 시큐리티 구현

1 분 소요

들어가며

AOP를 통한 메서드 시큐리티를 구현하는 과정에서 느낀 점을 기록하기 위한 포스팅

Spring Security를 통한 구현

  • 이번 동아리 프로젝트에서 인터셉터와 JWT를 통한 세션 리스 인증 방법을 구현했기 때문에 Spring Security 의존성을 받지 않았다.
  • MethodSecurityInterceptor를 이용하기 위해 의존성을 주입받아봤는데 필터를 제거하는 방법을 모르겠다..
  • web.ingnoring()를 통해 필터를 거치지 않고 통과하게 할 수는 있지만 어쨌든 많은 필터와 관련된 빈을 컨텍스트에 로딩해야 되기 때문에 적절치 않다고 생각했다.
  • 그래서 결과적으로 그냥 직접 AOP 기술을 통한 구현을 해보기로 했다.

사용 코드

  • AccessCheckAdvice
@Aspect
public class AccessCheckAdvice {

    @Pointcut("execution(* depromeet.ohgzoo.iam.security..*.*(..))")
    private void test() {
    }

    @Before("test() && args(post, userId,..)")
    public void security(Post post, Long userId) {
        if (!post.getMember().getId().equals(userId)) {
            throw new AccessDeniedException();
        }
    }
}
  • @Login
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface Login {
}
  • @PostEntity
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface PostEntity {
}
  • LoginMemberArgumentResolver
public class LoginMemberArgumentResolver implements HandlerMethodArgumentResolver {

    private final JwtService jwtService;

    public LoginMemberArgumentResolver(JwtService jwtService) {
        this.jwtService = jwtService;
    }

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        boolean hasLoginAnnotation = parameter.hasParameterAnnotation(Login.class);
        boolean hasLongType = Long.class.isAssignableFrom(parameter.getParameterType());

        return hasLoginAnnotation && hasLongType;
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest,
                                  WebDataBinderFactory binderFactory) throws Exception {
        HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
        return Long.valueOf(jwtService.getSubject(request.getHeader(TokenName.AUTH_TOKEN)));
    }
}
  • PostArgumentResolver
public class PostArgumentResolver implements HandlerMethodArgumentResolver {

    private final PostRepository postRepository;

    public PostArgumentResolver(PostRepository postRepository) {
        this.postRepository = postRepository;
    }

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        boolean hasPostEntityAnnotation = parameter.hasParameterAnnotation(PostEntity.class);
        boolean hasPostType = Post.class.isAssignableFrom(parameter.getParameterType());

        return hasPostEntityAnnotation && hasPostType;
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest,
                                  WebDataBinderFactory binderFactory) throws Exception {
        HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
        Map<String, String> pathVariables = (Map<String, String>) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
        String postId = pathVariables.get("postid");
        Post post = postRepository.findById(Long.valueOf(postId)).get();

        return post;
    }
}

고찰

  • 팀원분의 피드백을 받아보았다. 우선 security 모듈에서 다른 모듈의 접근 권한 여부를 판단하기 위해서는 해당 모듈의 의존성을 주입받아야 한다. AOP 적용을 원하는 모듈의 경우 모두 의존성이 주입되어야 한다.
  • 따라서 security 모듈을 주입받는 App 모듈 같은 경우 원치 않는 의존성을 주입받게 되거나 의존성이 중복되어서 주입될 수 있다. 이는 MSA 구조에서 이상적이지 않은 프로젝트 구조라고 생각한다.
  • 위와 같은 방법으로 메서드 접근 권한 검사 기능을 security 모듈에 단일 책임 원칙을 지우는 것이 좋은지, 아니면 그냥 모듈 단위로 접근 권한 검사를 할 것인지, 아니면 App 단위로 접근 검사를 하는 것이 좋은지 솔직히 잘 모르겠다..
  • 어떻게 생각해 보면 MSA 구조의 내재적 한계라는 생각도 든다. 의존성의 layer가 잘 구분되어 있지 않고 하나의 골칫덩어리 의존성 묶음이 여러 개의 의존성을 달고 다닌다면 결국 MSA의 의미는 없어지는 거라는 생각이 든다.
  • MSA 구조는 진짜 쉽지 않은 것 같다. layer을 어떤 식으로 나누는 것이 가장 효율적인지 공부해 봐야겠다.

댓글남기기