springframework/시작하자SpringSecurity

39.FilterInvocationSecurityMetadataSource (1)

Jungsoomin :) 2020. 9. 30. 05:16

FilterInvocationMetadataSource

  1. SecurityMetadatasource 를 구현한 클래스가 FilterInvocationSecurityMetadataSource 다.
  2. UrlFilterInvocationSecurityMatadataSource 라는 이름으로 구현

어떤 기능을 구현하는가

 

  • 사용자가 접근하고자 하는 Url 자원 대한 권한 추출
  • AccessDecisionManager 에게 전달하여 인가처리 요청
  • DB 에서 자원, 권한 정보를 매핑하여 Map으로 관리
  • 사용자 요청에 매핑된 권한정보 확인

구현 흐름

  1. 사용자의 요청
  2. FilterInvocationMetadataSource에는 DB 에서 받은 권한, 자원정보를 가지고 Map객체에 저장
  3. 요청정보에 대한 권한정보를 추출
  4. 권한 목록 존재AccessDecisionManger 에게 심사요청 (decide(Authentication, FilterInvocation, List<ConfigAttribute>)

구현

FilterInvocationMetadataSource를 구현

  1. LinkedHashMap <RequestMatcher, List<ConfigAttribute>> 필드를 선언
  2. getAttributes() 는 매개값으로 FilterInvocation 이 필요하다.
  3. 매개값인 FilterInvocation으로 HttpServletRequest를 생성(사용자 요청 값)
  4. MapEntrySet 으로 반복 구문을 돌아 키인 RequestMatcher 를 가져와 요청정보가 일치하는지 확인
public class UrlFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {

    private LinkedHashMap<RequestMatcher, List<ConfigAttribute>> requestMap ;

    private SecurityResourceService securityResourceService;

    public UrlFilterInvocationSecurityMetadataSource(LinkedHashMap<RequestMatcher, List<ConfigAttribute>> resourcesMap, SecurityResourceService securityResourceService) {
        this.requestMap = resourcesMap;
        this.securityResourceService = securityResourceService;
    }

    @Override
    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {

        HttpServletRequest request = ((FilterInvocation) object).getRequest();

        if(requestMap != null){
            for(Map.Entry<RequestMatcher, List<ConfigAttribute>> entry : requestMap.entrySet()){
                RequestMatcher matcher = entry.getKey();
                if(matcher.matches(request)){
                    return entry.getValue();//권한정보
                }
            }
        }

        return null;
    }

    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        Set<ConfigAttribute> allAttributes = new HashSet<>();

        for (Map.Entry<RequestMatcher, List<ConfigAttribute>> entry : requestMap
                .entrySet()) {
            allAttributes.addAll(entry.getValue());
        }

        return allAttributes;
    }

    @Override
    public boolean supports(Class<?> clazz) {
        return FilterInvocation.class.isAssignableFrom(clazz);
    }

    public void reload(){

        LinkedHashMap<RequestMatcher, List<ConfigAttribute>> reloadedMap = securityResourceService.getResourceList();
        Iterator<Map.Entry<RequestMatcher, List<ConfigAttribute>>> iterator = reloadedMap.entrySet().iterator();

        requestMap.clear();

        while(iterator.hasNext()){
            Map.Entry<RequestMatcher, List<ConfigAttribute>> entry = iterator.next();
            requestMap.put(entry.getKey(), entry.getValue());
        }
    }
}

설정클래스로 이동

  • exceptionHandling() 메서드의 하위 API 에 

.addFilterAt(customFilterSecurityInterceptor(), FilterSecurityInterceptor.class) 로 필터 를 앞에 추가

  • FilterSecurityInterceptor 를 Bean으로 등록

필요한 객체들은 FilterInvocationSecurityMetadataSource, AccessDecisionManager 구현체, authenticationManager 이다.

@Bean
    public FilterSecurityInterceptor filterSecurityInterceptor() throws Exception {
        FilterSecurityInterceptor filterSecurityInterceptor = new FilterSecurityInterceptor();
        filterSecurityInterceptor.setSecurityMetadataSource(urlFilterInvocationSecurityMetadataSource());//구현체
        filterSecurityInterceptor.setAccessDecisionManager(affirmativeBased());//1개라도 허용이면 통과
        filterSecurityInterceptor.setAuthenticationManager(authenticationManagerBean());//메서드에서 추출
        return filterSecurityInterceptor;
    }

또한, AccessDecisionManager등록할 때 필요한 객체는 AccessDecisionVoter 이다.

private AccessDecisionManager affirmativeBased() {
        AffirmativeBased affirmativeBased = new AffirmativeBased(getAccessDecisionVoters());//getter 로 가져옴
        return affirmativeBased;
    }
    
    private List<AccessDecisionVoter<?>> getAccessDecisionVoters() {

        List<AccessDecisionVoter<? extends Object>> accessDecisionVoters = new ArrayList<>();
        accessDecisionVoters.add(roleVoter());

        return accessDecisionVoters;
    }
    
    @Bean
    public AccessDecisionVoter<? extends Object> roleVoter() {

        RoleHierarchyVoter roleHierarchyVoter = new RoleHierarchyVoter(roleHierarchy());
        return roleHierarchyVoter;
    }

그리고 구현한 FilterInvocationSecurityMetadataSource Bean 으로 등록되야한다.

@Bean
    public UrlFilterInvocationSecurityMetadataSource urlFilterInvocationSecurityMetadataSource() throws Exception {
        return new UrlFilterInvocationSecurityMetadataSource(urlResourcesMapFactoryBean().getObject(), securityResourceService);
    }

 

이후에 configure(HttpSecurity) 에 API 등록

.exceptionHandling()
                .authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"))
                .accessDeniedPage("/denied")
                .accessDeniedHandler(accessDeniedHandler())
        .and()
                .addFilterAt(customFilterSecurityInterceptor(), FilterSecurityInterceptor.class);

** FilterSecurityInterceptor 는 커스텀 FilterSecurityInterceptor 동작 후에는 작동하지 않고 chain 한다.

 

** FilterInvocationSecurityMetadataSource 에 선언한 Map 에는 AntRequestMatcher 와 ConfigAttribute 구현체SecurityConfig 클래스가 있다.