애플리케이션의 서비스를 이용하기 위한 사용자 인증에 성공했다하더라도 체크해야 할 또 하나의 중요 보안 요소 : 권한 부여(Authortization, 인가)
Spring Security Filter Chain에 도달한 사용자의 인증 요청을 처리하기 위한 작업이 수행된 후 인증된 사용자임을 확인함
인증된 사용자는 애플리케이션에서 제공하는 리소스를 마음대로 이용할 수 있을까?
단순히 인증에만 성공했다고 해서 모든 리소스에 접근이 가능하다면 권한 부여/인가라느 중요한 보안 요소를 무시하는 것임
사용자 인증 요청이 정상적으로 처리되어 인증된 사용자임이 확인된 이후 Spring Security에서 인증된 사용자에게 어떤 과정을 거쳐 애플리케이션 리소스에 대한 접근 권한을 부여하는지 그 흐름을 이해해보자
:사용자가 로그인 인증에 성공한 후 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을 처리
? 컴포넌트란?
:여러 개의 프로그램 함수들을 모아 하나의 특정한 기능을 수행할 수 있도록 구성한 작은 기능적 단위
:프로그램의 한 부분을 의미하며 재사용이 가능한 최소 단위를 말함
: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를 사용
:이름 그대로 권한 부여 처리를 총괄하는 매니저 역할을 하는 인터페이스
@FunctionalInterface
public interface AuthorizationManager<T> {
...
...
@Nullable
AuthorizationDecision check(Supplier<Authentication> authentication, T object);
}
:AuthorizationManager 인터페이스는 check() 메서드 하나만 정의
:Supplier와 제너릭 타입의 객체를 파라미터로 가짐
: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")와 같이 메서드 체인 정보를 기반으로 생성됨