[Spring][Security] AOP를 통한 메서드 시큐리티 구현
들어가며
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을 어떤 식으로 나누는 것이 가장 효율적인지 공부해 봐야겠다.
댓글남기기