스프링 시큐리티/실전프로젝트 - 인가 프로세스 DB 연동 서비스 계층 구현

ch07. AOP Method 기반 DB 연동 - ProtectPointcutPostProcessor

webmaster 2022. 1. 27. 11:44
728x90

ProtectPointcutPostProcessor

  • 메서드 방식의 인가처리를 위한 자원 및 권한 정보 설정 시 자원에 포인트 컷 표현식을 사용할 수 있도록 지원하는 클래스
  • 후처리기로서 스프링 초기화 과정에서 빈 들을 검사하여 빈이 가진 메소드 중에서 포인트 컷 표현식과 matching 되는 클래스 , 메서드,권한 정보를 MapBasedMethodSecurityMetadataSource에전달하여 인가처리가 되도록 제공되는 클래스
  • DB 저장 방식
    • Method 방식
      • io.security.service.OrderService.order : ROLE_USER
    • Pointcut 방식
      • execution(* io.security.service.*Service.*(..)) : ROLE_USER
  • 설정 클래스에서 빈 생성 시 접근 제한자가package 범위로 되어 있기 때문에 리플렉션을 이용해 생성한다
  • 동작과정

    • 동작과정
    • DB에서 정보를 읽어와 ProtectPointcutPostProcess로 읽어온다.
    • Bean 등록
    • MethodResourcesFactoryBean이 Method, pointcut 두 가지 모두 읽어 올 수 있도록 코드 추가
      • @Slf4j
        public class MethodResourcesFactoryBean implements FactoryBean<LinkedHashMap<String, List<ConfigAttribute>>> {
        
            private SecurityResourceService securityResourceService;
            private String resourceType;
            private LinkedHashMap<String, List<ConfigAttribute>> resourcesMap;
        
            public void setResourceType(String resourceType) {
                this.resourceType = resourceType;
            }
        
            public void setSecurityResourceService(SecurityResourceService securityResourceService) {
                this.securityResourceService = securityResourceService;
            }
        
        
        
            public void init() {
                if ("method".equals(resourceType)) {
                    resourcesMap = securityResourceService.getMethodResourceList();
                } else if ("pointcut".equals(resourceType)) {
                    resourcesMap = securityResourceService.getPointcutResourceList();
                } else {
                    log.error("resourceType must be 'method' or 'pointcut'");
                }
            }
        
            public LinkedHashMap<String, List<ConfigAttribute>> getObject() {
                if (resourcesMap == null) {
                    init();
                }
                return resourcesMap;
            }
        
            @SuppressWarnings("rawtypes")
            public Class<LinkedHashMap> getObjectType() {
                return LinkedHashMap.class;
            }
        
            public boolean isSingleton() {
                return true;
            }
        }
        
    • 이슈사항으로 인한 클래스 추가
      • @Slf4j
        public class ProtectPointcutPostProcessor implements BeanPostProcessor {
        
            private final Map<String, List<ConfigAttribute>> pointcutMap = new LinkedHashMap<String, List<ConfigAttribute>>();
            private final MapBasedMethodSecurityMetadataSource mapBasedMethodSecurityMetadataSource;
            private final Set<PointcutExpression> pointCutExpressions = new LinkedHashSet<>();
            private final PointcutParser parser;
            private final Set<String> processedBeans = new HashSet<>();
        
            public ProtectPointcutPostProcessor(MapBasedMethodSecurityMetadataSource mapBasedMethodSecurityMetadataSource) {
                Assert.notNull(mapBasedMethodSecurityMetadataSource, "MapBasedMethodSecurityMetadataSource to populate is required");
                this.mapBasedMethodSecurityMetadataSource = mapBasedMethodSecurityMetadataSource;
        
                Set<PointcutPrimitive> supportedPrimitives = new HashSet<>(3);
                supportedPrimitives.add(PointcutPrimitive.EXECUTION);
                supportedPrimitives.add(PointcutPrimitive.ARGS);
                supportedPrimitives.add(PointcutPrimitive.REFERENCE);
                parser = PointcutParser.getPointcutParserSupportingSpecifiedPrimitivesAndUsingContextClassloaderForResolution(supportedPrimitives);
            }
        
            public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
                return bean;
            }
        
            public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        
                if (processedBeans.contains(beanName)) {
                    return bean;
                }
        
                synchronized (processedBeans) {
                    if (processedBeans.contains(beanName)) {
                        return bean;
                    }
        
                    Method[] methods;
                    try {
                        methods = bean.getClass().getMethods();
                    } catch (Exception e) {
                        throw new IllegalStateException(e.getMessage());
                    }
        
                    for (Method method : methods) {
                        for (PointcutExpression expression : pointCutExpressions) {
                            if (attemptMatch(bean.getClass(), method, expression, beanName)) {
                                break;
                            }
                        }
                    }
        
                    processedBeans.add(beanName);
                }
        
                return bean;
            }
        
            /**
             * 설정클래스에서 람다 형식으로 선언된 빈이 존재할 경우 에러가 발생하여 스프링 빈과 동일한 클래스를 생성하여 약간 수정함
             * 아직 AspectJ 라이브러리에서 Fix 하지 못한 것으로 판단되지만 다른 원인이 존재하는지 계속 살펴보도록 함
             */
            private boolean attemptMatch(Class<?> targetClass, Method method, PointcutExpression expression, String beanName) {
        
                boolean matches;
                try {
                    matches = expression.matchesMethodExecution(method).alwaysMatches();
                    if (matches) {
                        List<ConfigAttribute> attr = pointcutMap.get(expression.getPointcutExpression());
        
                        if (log.isDebugEnabled()) {
                            log.debug("AspectJ pointcut expression '"
                                    + expression.getPointcutExpression() + "' matches target class '"
                                    + targetClass.getName() + "' (bean ID '" + beanName
                                    + "') for method '" + method
                                    + "'; registering security configuration attribute '" + attr
                                    + "'");
                        }
        
                        mapBasedMethodSecurityMetadataSource.addSecureMethod(targetClass, method, attr);
                    }
                    return matches;
        
                } catch (Exception e) {
                    matches = false;
                }
                return matches;
            }
        
            public void setPointcutMap(Map<String, List<ConfigAttribute>> map) {
                Assert.notEmpty(map, "configAttributes cannot be empty");
                for (String expression : map.keySet()) {
                    List<ConfigAttribute> value = map.get(expression);
                    addPointcut(expression, value);
                }
            }
        
            private void addPointcut(String pointcutExpression, List<ConfigAttribute> definition) {
                Assert.hasText(pointcutExpression, "An AspectJ pointcut expression is required");
                Assert.notNull(definition, "A List of ConfigAttributes is required");
                pointcutExpression = replaceBooleanOperators(pointcutExpression);
                pointcutMap.put(pointcutExpression, definition);
                pointCutExpressions.add(parser.parsePointcutExpression(pointcutExpression));
        
                if (log.isDebugEnabled()) {
                    log.debug("AspectJ pointcut expression '" + pointcutExpression
                            + "' registered for security configuration attribute '" + definition
                            + "'");
                }
            }
        
            private String replaceBooleanOperators(String pcExpr) {
                pcExpr = StringUtils.replace(pcExpr, " and ", " && ");
                pcExpr = StringUtils.replace(pcExpr, " or ", " || ");
                pcExpr = StringUtils.replace(pcExpr, " not ", " ! ");
                return pcExpr;
            }
        
        }
728x90