Spring

Spring Security 기본 8

RangA 2023. 6. 10. 02:17

Spring Security

Spring Security의 권한 처리 흐름

Spring Security의 컴포넌트로 보는 권한 부여(Authorization) 처리 흐름


  • Spring Security Filter Chain에서 URL을 통해 사용자의 액세스를 제한하는 권한 부여 Filter는 AuthorizationFilter
  • AuthorizationFilter는 먼저 (1)과 같이 SecurityContextHolder로부터 Authentication을 획득함
  • (2)와 같이 SecurityContextHolder로부터 획득한 Authentication과 HttpServletRequest를 AuthorizationManager에게 전달함
  • AuthorizationManager는 권한 부여 처리를 총괄하는 매니저 역할을 하는 인터페이스이고, RequestMatcherDelegatingAuthorizationManager는 AuthorizationManager를 구현하는 구현체 중 하나
    • RequestMatcherDelegatingAuthorizationManager는 RequestMatcher 평가식을 기반으로 해당 평가식에 매치되는 AuthorizationManager에게 권한 부여 처리를 위임하는 역할을 함
    • RequestMatcherDelegatingAuthorizationManager가 직접 권한 부여 처리를 하는 것이 아니라 RequestMatcher를 통해 매치되는 AuthorizationManager 구현 클래스에게 위임만 함
  • RequestMatcherDelegatingAuthorizationManager 내부에서 매치되는 AuthorizationManager 구현 클래스가 있다면 해당 AuthorizationManager 구현 클래스가 사용자의 권한을 체크함(3)
  • 적절한 권한이라면 (4)와 같이 다음 요청 프로세스를 계속 이어감
  • 만약 적절한 권한이 아니라면 (5)와 같이 AccessDeniedException이 throw되고 ExceptionTranslationFilter가 AccessDeniedException을 처리함

Spring Security의 권한 부여 컴포넌트

AuthorizationFilter

  • URL을 통해 사용자의 액세스를 제한하는 권한 부여 Filter이며, Spring Security 5.5 버전부터 FilterSecurityInterceptor를 대체함

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 받음
    • DI 받은 AuthorizationManager를 통해 권한 부여 처리를 진행함
  • (2)와 같이 DI 받은 AuthorizationManager의 check() 메서드를 호출해 적절한 권한 부여 여부를 체크함
    • AuthorizationManager의 check() 메서드는 AuthorizationManager 구현 클래스에 따라 권한 체크 로직이 다름
    • URL 기반으로 권한 부여 처리를 하는 AuthorizationFilter는 AuthorizationManager의 구현 클래스로 RequestMatcherDelegatingAuthorizationManager를 사용함

AuthorizationManager

  • 권한 부여 처리를 총괄하는 매니저 역할을 하는 인터페이스
  • AuthorizationManager 인터페이스는 check() 메서드 하나만 정의되어 있으며, Supplier와 제너릭 타입의 객체를 파라미터로 가짐

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

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

}

RequestMatcherDelegatingAuthorizationManager

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

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;
    }
}

  • check() 메서드의 내부에서 (1)과 같이 루프를 돌면서 RequestMatcherEntry 정보를 얻은 후에 (2)와 같이 RequestMatcher 객체를 얻음
  • (3)과 같이 MatchResult.isMatch()가 true이면 AuthorizationManager 객체를 얻은 뒤, 사용자의 권한을 체크함
    • 여기서의 RequestMatcher는 SecurityConfiguration에서 .antMatchers("/orders/**").hasRole("ADMIN") 와 같은 메서드 체인 정보를 기반으로 생성됨



접근 제어 표현식

  • Spring Security에서는 웹 및 메서드 보안을 위해 표현식(Spring EL)을 사용할 수 있음

  • hasRole(Stirng role)
    • 현재 보안 주체(principal)가 지정된 역할을 갖고 있는지 여부를 확인하고 가지고 있다면 true를 리턴함
    • hasRole(’admin’)처럼 파라미터로 넘긴 role이 ROLE_ 로 시작하지 않으면 기본적으로 추가함(DefaultWebSecurityExpressionHandler의 defaultRolePrefix를 수정하면 커스텀할 수 있음)
  • hasAnyRole(String… roles)
    • 현재 보안 주체가 지정한 역할 중 1개라도 가지고 있으면 true를 리턴함(문자열 리스트를 콤마로 구분해서 전달)
    • Ex) hasAnyRole(’admin’, ‘user’)
  • hasAuthority(String authority)
    • 현재 보안 주체가 지정한 권한을 갖고 있는지 여부를 확인하고 가지고 있다면 true를 리턴함
    • Ex) hasAuthority(’read’)
  • hasAnyAuthority(String… authorities)
    • 현재 보안 주체가 지정한 권한 중 하나라도 있으면 true를 리턴함
    • Ex) hasAnyAuthority(’read’, ‘write’)
  • principal
    • 현재 사용자를 나타내는 principal 객체에 직접 접근할 수 있음
  • authentication
    • SecurityContext로 조회할 수 있는 현재 Authentication 객체에 직접 접근할 수 있음
  • permitAll
    • 항상 true로 평가함
  • denyAll
    • 항상 false로 평가함
  • isAnonymous()
    • 현재 보안 주체가 익명 사용자면 true를 리턴함
  • isRememberMe()
    • 현재 보안 주체가 remember-me 사용자면 true를 리턴함
  • isAuthenticated()
    • 사용자가 익명이 아닌 경우 true를 리턴함
  • isFullyAuthenticated()
    • 사용자가 익명 사용자나 remember-me 사용자가 아니면 true를 리턴함
  • hasPermission(Object target, Object permission)
    • 사용자가 target에 해당 permission 권한이 있으면 true를 리턴
    • Ex) hasPermission(domainObject, ‘read’)
  • hasPermission(Object targetId, String targetType, Object permission)
    • 사용자가 target에 해당 permission 권한이 있으면 true를 리턴함
    • Ex) hasPermission(1, ‘com.example.domain.Message’, ‘read’)