프레임워크(Framework)/Spring

[Spring Security] 스프링 시큐리티 권한 부여 처리 흐름

잇트루 2023. 1. 2. 20:54
반응형

스프링 시큐리티의 권한 부여 처리 흐름

애플리케이션 서비스에서 사용자 인증에 성공한 이후에는 인가(권한 부여)가 필요하다.

스프링 시큐리티의 필터 체인을 통해 사용자 인증에 성공한 다음에는 애플리케이션에서 제공하는 리소스에 접근할 수 있도록 권한 부여 작업을 위한 필터 체인을 통과해야 한다.

 

다음은 사용자 인증이 정상적으로 처리된 이후 리소스에 대한 접근 권한을 부여하는 흐름에 대한 이미지이다.

스프링 시큐리티 필터 체인에서 AuthorizationFilter는 URL을 통해 인증된 사용자의 권한 부여하는 필터이다.

  • (1) AuthorizationFilter는 SecurityContextHolder로부터 Authentication 객체를 획득한다.

 

  • (2) HttpServletRequest와 획득한 Authentication을 AuthorizationManager에게 전달한다.
    • AuthorizationManager는 권한 부여를 총괄하는 인터페이스이다.
    • AuthorizationManager의 여러 구현체 중 RequestMatcherDelegatingAuthorizationManager는 RequestMatcher 평가식을 기반으로 권한 부여 처리를 위힘하는 역할을 한다.

 

  • (3) RequestMatcherDelegatingAuthorizationManager 내부에서 매치되는 AuthorizationManager 구현 클래스가 있다면, AuthorizationManager 구현 클래스가 사용자의 권한을 체크한다.

 

  • (4) 적절한 권한이면 요청 프로세스를 이어간다.

 

  • (5) 만약, 적절한 권한이 아닌 경우 AccessDeniedException을 던지게 된다.
    • ExceptionTranslationFilter가 AccessDeniedException을 처리한다.

 

 

AuthorizationFilter

AuthorizationFilter는 URL을 통해 사용자의 액세스를 제한하는 권한 부여 필터이다.

스프링 시큐리티 5.5 버전부터는 FilterSecurityInterceptor를 대체하고 있다.

 

AuthorizationFilter

public class AuthorizationFilter extends OncePerRequestFilter {

    private final AuthorizationManager<HttpServletRequest> authorizationManager;

    ...

    // (1)
    public AuthorizationFilter(AuthorizationManager<HttpServletRequest> authorizationManager) {
        Assert.notNull(authorizationManager, "authorizationManager cannot be null");
        this.authorizationManager = authorizationManager;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

        AuthorizationDecision decision = this.authorizationManager.check(this::getAuthentication, request); // (2)
        this.eventPublisher.publishAuthorizationEvent(this::getAuthentication, request, decision);
        if (decision != null && !decision.isGranted()) {
            throw new AccessDeniedException("Access Denied");
        }
        filterChain.doFilter(request, response);
    }

    ...

}
  • (1) AuthorizationFilter 객체가 생성될 때, AuthorizationManager를 의존관계 주입(DI) 받는다.
  • (2) 주입받은 AuthorizationManager의 check() 메서드를 호출하여 적절한 권한 여부를 체크한다.

 

 

AuthorizationManager

AuthorizationManager는 권한 부여 처리를 총괄하는 역할의 인터페이스이다.

@FunctionalInterface
public interface AuthorizationManager<T> {
    ...

    @Nullable
    AuthorizationDecision check(Supplier<Authentication> authentication, T object);

}
  • AuthorizationManager의 check() 메서드는 구현 클래스에 따라 처리 로직이 다르다.
  • URL을 기반으로 처리하는 AuthorizationFilter는 RequestMatcherDelegatingAuthorizationManager를 사용한다.

 

 

RequestMatcherDelegatingAuthorizationManager

RequestMatcherDelegatingAuthorizationManager는 AuthorizationManager의 구현 클래스 중 하나로, 직접 권한 부여를 수행하지 않고 RequestMatcher를 통해 매치되는 AuthorizationManager 구현 클래스에게 권한 부여 처리를 위임한다.

 

RequestMatcherDelegatingAuthorizationManager

public final class RequestMatcherDelegatingAuthorizationManager implements AuthorizationManager<HttpServletRequest> {
    ...

    @Override
    public AuthorizationDecision check(Supplier<Authentication> authentication, HttpServletRequest request) {
        if (this.logger.isTraceEnabled()) {
            this.logger.trace(LogMessage.format("Authorizing %s", request));
        }

    	// (1)
        for (RequestMatcherEntry<AuthorizationManager<RequestAuthorizationContext>> mapping : this.mappings) {

            RequestMatcher matcher = mapping.getRequestMatcher(); // (2)
            MatchResult matchResult = matcher.matcher(request);
            if (matchResult.isMatch()) {   // (3)
                AuthorizationManager<RequestAuthorizationContext> manager = mapping.getEntry();
                if (this.logger.isTraceEnabled()) {
                    this.logger.trace(LogMessage.format("Checking authorization on %s using %s", request, manager));
                }
                return manager.check(authentication,
                        new RequestAuthorizationContext(request, matchResult.getVariables()));
            }
        }
        this.logger.trace("Abstaining since did not find matching RequestMatcher");
        return null;
    }
}
  • (1) check() 메서드의 내부에서 반복문을 통해 RequestMatcherEntry 정보를 조회한다.
  • (2) 얻은 RequestMatcherEntry를 통해 RequestMatcher 객체를 얻는다.
  • (3) RequestMatcher.isMatch가 true이면, AuthorizationManager 객체를 얻은 뒤, 사용자의 권한을 체크한다.
반응형