BackEnd/Spring Security

[Spring Security] 웹 기반 인가처리 과정

샤아이인 2022. 11. 1.

본 글은 Spring Security docs 와 여러 블로그 들을 참고하고, 공부하면서 요약하였습니다.

1. 인가처리 - 주요 아키텍처 이해

 

1 - 1) Spring Security의 인가처리

예를 들어 다음과 같이 권한 설정을 했다고 해보자!

http.antmatchers("/user").access("hasRole('USER')")

사용자(인증정보)가 "/user"자원(요청정보)에 접근하기 위해서는 "ROLE_USER" 권한(권한정보)이 필요한 상황이다.

 

이제 사용자 요청이 들어왔을때 어던 방식으로 초기화 되는지 알아보자!

1) 인증정보(Authentication)는 SecurityContext에서 얻습니다.

2) 사용자 요청정보로부터 FilterInvocation클래스를 생성해서 request 정보를 얻습니다.

3) 권한정보는 설정클래스에서 설정한 내용(ex: hasRole('USER'))을 파싱해서 얻습니다.

   → 내부적으로 스프링 시큐리티가 초기화 될 때 설정했던 내용들(ex: http.antmatchers("/user").access("hasRole('USER')")) 에         서 자원정보와 권한정보를 꺼내어 Map객체에 key(자원정보), value(권한정보) 형태로 저장합니다.

 

사용자가 자원요청을 할 경우 SecurityInterceptor가 요청을 받은 뒤 (인증, 요청, 권한) 정보들을 얻어서 AccessDecisionManager에게 전달하여 결정하게 됩니다.

 

1 - 2) 디버깅 해보기

권한 설정의 경우 파싱되어 ExpressionBasedFilterInvocationSecurityMetadataSource 의 requestToExpressionAttributesMap에 저장되게 됩니다.

 

각각을 살펴보면 다음과 같이 key : value의 형태로 저장되어 있습니다!

 

1 - 2 - 1) FilterInvocation 생성

FilterInvocation 에는 사용자의 자원 요청에 대한 정보가 들어 있습니다!

사용자의 요청이 오면, FilterSecurityInterceptor의 doFilter에서 FilterInvocation을 생성합니다.

이렇게 생성된 FilterInvocation을 invoke() 메서드를 호출하면서 인자로 전달합니다.

 

이후 invoke 메서드 내부에서 beforeInvocation()을 호출하면서 다시 인자로 전달하게 됩니다!

 

1 - 2 - 2) beforeInvocation()

이제 큰 틀에서 FilterSecurityInterceptor의 부모인 AbstractSecurityInterceptor의 beforeInvocation()을 살펴봅시다.

protected InterceptorStatusToken beforeInvocation(Object object) {
    // object타입의 인자로 FilterInvocation 를 전달받음
    
    // 권한 정보가 mapping되어 있는 map찾아오기
    Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object);

    // 인증 객체 생성
    Authentication authenticated = authenticateIfRequired();

    // Attempt authorization
    attemptAuthorization(object, attributes, authenticated);

    return new InterceptorStatusToken(SecurityContextHolder.getContext(), false, attributes, object);
}

위에서 디버깅으로 살펴봤듯, FilterInvocation에는 필요한 사용자의 자원 요청 정보가 모였습니다.

이후 obtainSecurityMetadataSource()를 통해 권한 정보가 mapping된 attributes를 찾아옵니다.

또한 인증객체를 생성하고, 마지막으로 attemptAuthorization()을 통해서 accessDecisionManager(인가 처리 결정 관리자)를 통해 결정됩니다!

 

다음 코드를 살펴보시죠!

private void attemptAuthorization(Object object, Collection<ConfigAttribute> attributes,
        Authentication authenticated) {
    try {
        this.accessDecisionManager.decide(authenticated, object, attributes);
    }
    // 생략...
}

 

1 - 2 - 3) obtainSecurityMetadataSource().getAttributes()

FilterSecurityInterceptor의 부모인 AbstractSecurityInterceptor의 beforeInvocation() 에서 attrubutes를 가져오게 됩니다.

이 attrubutes가 바로 위에서 초기화 될때 저장한 requestToExpressionAttributesMap 입니다.

따라서 Map객체에 key(자원정보), value(권한정보) 형태로 저장되어 있습니다.

 

위 코드에서 this는 다음과 같이 securityMetadataSource 입니다!

 

또한 getAttributes()는 DefaultFilterInvocationSecurityMetadataSource 에서 오버라이딩 되고 있습니다.

여기서 권한목록을 FilterSecurityInterceptor에게 반환하게 되는 것 입니다!

 

1 - 2) 권한 추출의 2가지 방법

- getAttributes(Object object)를 override해서 실질적인 구현을 한다.

- Method방식의 권한 정보 추출은 이미 구현되있는 클래스를 활용하여 Annotation방식으로 인가처리 설정을 해주도록 한다.

댓글