Java_Spring Security

Minki CHO·2023년 1월 18일
0

CodeStates

목록 보기
20/43

애플리케이션의 서비스를 이용하기 위한 사용자 인증에 성공했다하더라도 체크해야 할 또 하나의 중요 보안 요소 : 권한 부여(Authortization, 인가)

Spring Security의 권한 부여 처리 흐름

Spring Security Filter Chain에 도달한 사용자의 인증 요청을 처리하기 위한 작업이 수행된 후 인증된 사용자임을 확인함
인증된 사용자는 애플리케이션에서 제공하는 리소스를 마음대로 이용할 수 있을까?
단순히 인증에만 성공했다고 해서 모든 리소스에 접근이 가능하다면 권한 부여/인가라느 중요한 보안 요소를 무시하는 것임

사용자 인증 요청이 정상적으로 처리되어 인증된 사용자임이 확인된 이후 Spring Security에서 인증된 사용자에게 어떤 과정을 거쳐 애플리케이션 리소스에 대한 접근 권한을 부여하는지 그 흐름을 이해해보자

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


:사용자가 로그인 인증에 성공한 후 Spring Security에서 인증된 사용자에게 어떻게 권한을 부여하는지 흐름을 알 수 있음

-Spring Security Filter Chain에서 URL을 통해 사용자의 액세스를 제한하는 권한 부여 Filter = AuthorizationFilter

1) Authentication 획득
:AuthorizationFilter는 먼저 SecurityContextHolder로부터 Authentication을 획득

2) Authentication과 HttpServletRequest 전달
:SecurityContextHolder로부터 획득한 Authentication과 HttpServletRequest를 AuthorizationManager에게 전달

AuthorizationManager
:권한 부여 처리를 총괄하는 매니저 역할을 하는 인터페이스

RequestMatcherDelegatingAuthorizationManager
:AuthorizationManager를 구현하는 구현체 중 하나
:RequestMatcher 평가식을 기반으로 해당 평가식에 매치되는 AuthorizationManager에게 권한 부여 처리를 위임하는 역할을 함<노이해중>
:RequestMatcherDelegatingAuthorizationManager가 직접 권한 부여 처리를 하는 것이 아니라 RequestMatcher를 총해 매치되는 AuthorizationManager 구현 클래스에게 위임만 함

3) 적절한 권한인가?
:RequestMatcherDelegatingAuthorizationManager 내부에서 매치되는 AuthorizationManager 구현 클래스가 있다면 해당 AuthorizationManager 구현 클래스가 사용자 권한을 체크함

4) 적절한 권한인가? Yes
:적절한 권한일 경우 다음 요청 프로세스를 이어감

5) 적절한 권한인가? No
:적절한 권한이 아닐 경우 AccessDeniedException이 throw되고 ExceptionTranslationFilter가 AccessDeniedException을 처리

? 컴포넌트란?
:여러 개의 프로그램 함수들을 모아 하나의 특정한 기능을 수행할 수 있도록 구성한 작은 기능적 단위
:프로그램의 한 부분을 의미하며 재사용이 가능한 최소 단위를 말함

Spring Security의 권한 부여 컴포넌트

1) 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) public AuthorizationFilter(AuthorizationManager<HttpServletRequest> authorizationManager) {...}
:AuthorizationFilter 객체가 생성될 때, AuthorizationManager를 DI 받음
:DI 받은 AuthorizationManager를 통해 권한 부여 처리를 진행

2) AuthorizationDecision decision = this.authorizationManager.check(this::getAuthentication, request);
:DI 받은 AuthorizationManager의 check() 메서드를 호출해 적절한 권한 부여 여부를 체크함
:AuthorizationManager의 check() 메서드는 AuthorizationManager 구현 클래스에 따라 권한 체크 로직이 다름
:URL 기반으로 권한 부여 처리를 하는 AuthorizationFilter는 AuthorizationManager의 구현 클래스로 RequestMatcherDelegatingAuthorizationManager를 사용

2) AuthorizationManager

:이름 그대로 권한 부여 처리를 총괄하는 매니저 역할을 하는 인터페이스

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

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

}

:AuthorizationManager 인터페이스는 check() 메서드 하나만 정의
:Supplier와 제너릭 타입의 객체를 파라미터로 가짐

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

1) 2)
:check() 메서드의 내부에서 루프를 돌면서 RequestMatcherEntry 정보를 얻은 후
:2)와 같이 RequestMatcher 객체를 얻음

3)
:MatcherResult.isMatch() == true 이면
AuthorizationManager 객체를 얻은 뒤 사용자의 권한을 체크함

여기서의 RequsetMatcher는 SecurityConfiguration에서 .antManters("/orders/** ").hasRole("ADMIN")와 같이 메서드 체인 정보를 기반으로 생성됨

profile
Developer

0개의 댓글