Spring Security의 Architecture

60jong·2023년 3월 31일
2

Spring

목록 보기
7/9
post-thumbnail
post-custom-banner

모두 Spring Security 공식 문서를 참고해 정리했습니다.

Architecture

Spring Security는 filterannotation을 사용해서 애플리케이션 보안을 손 쉽게 도와줍니다.

애플리케이션의 보안은 크게 두 개의 독립적인 문제로 요약됩니다.
Authentication : who are you?, 누구인지?
Authorization : what are you allowed to do?, 어느 권한을 가졌는지?
Spring Security는 이 둘을 분리해서 각자의 전략과 확장을 제공합니다.

먼저, Authentication과 Authorization을 분리해서 정리하겠습니다.

인증과 인가

Authentication

Authentication의 가장 중요한 전략으로는 interface AuthenticationManager입니다. (구현체로는 ProviderManager)

AuthenticationManager

AuthenticationManager.java

package org.springframework.security.authentication;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;

public interface AuthenticationManager {

	Authentication authenticate(Authentication authentication) throws AuthenticationException;

}

authenticate()이라는 인증을 진행하는 메서드 하나만을 가지고 있습니다.

여기서 AuthenticationProvider가 등장합니다. (용어가 굉장히 헷갈리니 제대로 봐야합니다..ㅜ)


AuthenticationProvider

AuthenticationProvider.java

package org.springframework.security.authentication;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;

public interface AuthenticationProvider {

	Authentication authenticate(Authentication authentication) throws AuthenticationException;

	boolean supports(Class<?> authentication);

}

두 interface의 메서드를 확인해보면 return type, parameter가 같은 authenticate()가 있습니다.

AuthenticationProvider에는 supports()라는 메서드가 있다는 게 둘의 차이점입니다. 이 메서드는 내가 이 객체를 authenticate()할 수 있는가? 를 return합니다.

사실상 파라미터인 Class<?> -> Class<? extends Authentication>인 겁니다.

AuthenticationManager와 AuthenticationProvider의 협업

두 객체는 협업을 통해 Authentication을 진행합니다. 엄밀히 말하자면 AuthenticationManager --delegate-->AuthenticationProvider이다.

아래 그림이 가장 이해하기 좋은 것 같습니다.

두 객체는 AuthenticationManager 1 : N AuthenticationProvider의 관계를 맺고 있습니다.

추가로 ProviderManager소스 코드도 보여드리겠습니다. (매우 길어 정말 필요한 부분만 남겼습니다.) 보기 힘들지만 편의를 위해 달아놓은 !!!!! # 부분만 따라오시면 됩니다.


ProviderManager.java

public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean {
	!!!!!! 1
	private List<AuthenticationProvider> providers = Collections.emptyList();
	!!!!!! 2
	private AuthenticationManager parent;

	public ProviderManager(AuthenticationProvider... providers) {
		this(Arrays.asList(providers), null);
	}

	public ProviderManager(List<AuthenticationProvider> providers) {
		this(providers, null);
	}

	public ProviderManager(List<AuthenticationProvider> providers, AuthenticationManager parent) {
		Assert.notNull(providers, "providers list cannot be null");
		this.providers = providers;
		this.parent = parent;
		checkState();
	}

	@Override
	public Authentication authenticate(Authentication authentication) throws AuthenticationException {

		int size = this.providers.size();
        !!!!! 3
		for (AuthenticationProvider provider : getProviders()) {
        	!!!!! 4
			if (!provider.supports(toTest)) {
				continue;
			}
			
			try {
            	!!!!! 5
				result = provider.authenticate(authentication);
				if (result != null) {
					copyDetails(authentication, result);
					break;
				}
			}
			catch (AuthenticationException ex) {
				lastException = ex;
			}
		}
		if (result == null && this.parent != null) {
        	!!!!! 6
			// Allow the parent to try.
			try {
				parentResult = this.parent.authenticate(authentcation);
				result = parentResult;
			}
			catch (ProviderNotFoundException ex) {
				
			}
			catch (AuthenticationException ex) {
				parentException = ex;
				lastException = ex;
			}
		}
	
  • !!!!! 1
    1:N의 관계로 AuthenticationProvider를 List로 가지고 있습니다.

  • !!!!! 2
    밑에서 설명하겠습니다.

  • !!!!! 3
    가지고 있는 provider들 중에

  • !!!!! 4
    자신의 Authentication 객체를 지원하는 provider를 찾아

  • !!!!! 5
    해당 provider가 authenticate()을 수행하도록 합니다.

  • !!!!! 6
    밑에서 설명

이렇게 두 객체는 협력하게 됩니다.

parent AuthenticationManager

ProviderManager!!!!! 2를 보면 똑같은 객체를 parent라는 이름으로 가지고 있습니다.

ProviderManager는 자신의 provider들 중에서 support하는 provider로 authenticate()을 진행합니다. 하지만 가진 provider가 모두 지원하지 못한다면 자신의 parent에게 authenticate를 의뢰합니다. - !!!!! 6

만약 인증 실패 / 부모가 없거나 / 부모도 지원하지 못한다면 -> AuthenticationException 예외를 발생하게 됩니다.

Authenticaion 정리

User의 Authentication을 진행할 때, 가장 중요한 전략으로 AuthenticaionManager를 이용하게 된다.

AuthenticaionManager는 자신에게 인증 요청이 들어온 Authentication객체를 인증하게 된다. 이때 인증 행위를 위임한 AuthenticationProvider들을 탐색해서 이 객체를 인증할 수 있는 provider가 있는지 찾게 된다.

찾게 됐다면 그 provider가 인증을 진행하도록 한다. 만약 찾지 못했다면, 자신의 parent Manager에게 의뢰하게 되고, parent는 위와 같이 똑같은 행위를 하게 된다.

결과적으로 인증에 성공했다면, 성공한 Authentication 객체를 반환하고, 실패한다면 AuthenticationException을 던지게 된다.

Authorization

인증이 성공적이면, 이제 authorization(인가)을 진행하게 된다. 인가의 주요한 전략은 AccessDecisionManager이다.

AccessDecisionManager

이는 세 구현체를 갖는데

  • AffirmativeBased
  • ConsensusBased
  • UnanimousBased
    이다.

(소스코드를 찾아보니 AccessDecisionManager를 구현한 AbstractAccess~~의 구현 클래스들이다.)

AccessDecisionManager는 인가를 AccessDecisionVoter들에게 위임하게 된다.

구조가 Authenticaion과 비슷하다.
AuthManager -> AuthProviders delegate --- Authentication
AccessDeManager -> AccessDeVoters delegate --- Authorization

하지만 인가에서는 parent라는 개념은 존재하지 않습니다.

AccessDecisionProvider

AccessDecisionVoter의 메서드를 보면
int vote(Authentication authentication, S object, Collection<ConfigAttribute> attributes); 가 있는데

중간에 파라미터인 Object와 ConfigAttribute는

  • Object : 보안된 대상으로, 접근하고자 하는 대상입니다. (웹 리소스 or Java Class의 Method가 일반적)
  • ConfigAttribute : Object에 접근하기 위해 필요한 권한의 수준을 나타내는 metadata이다. 주로 User의 Role인 ROLE_ADMIN 등

Authorization 정리

인증이 성공적이라면, 인가를 진행하는데,

AccessDecisionManager가 요청 받은 secured Object에 인가를 진행할 수 있는 AccessDecisionVoter를 자신이 가진 voter들 중에 찾게 된다. voter는 User가 secured Object에 접근할 수 있는지 ConfigAttribute를 참고해 판단하게 된다.

어느 voter라도 접근을 허용한다면 ACCESS_GRANTED, ACCESS_DENIED 혹은 ACCESS_ABSTAIN도 존재한다.

Web Security

Spring Security는 Servlet의 filter를 기반으로 만들어졌습니다.

Servlet의 filter를 간단하게 말하자면, 모든 request는 Servlet에 도착하기 전에 순서를 가진 filter들인 filter chian를 타게 됩니다.

Spring Security는 이 filter chain에 하나의 filter를 추가함으로써 구현됩니다. 이 filter의 타입은 FilterChainProxy입니다.

FilterChainProxy

FilterChainProxy는 Spring Container에서 Bean으로 관리되며, 기본적으로 생성돼 모든 request에 적용이 됩니다.
그리고 default 순서를 가집니다. SecurityProperties.DEFAULT_FILTER_ORDER

위에 볼드체로 말했듯, FilterChainProxy는 Spring Container의 입장에서 하나의 filter에 불과합니다. 하지만, 그 내부에는 많은 filter들이 존재하고 각기 다른 기능을 수행합니다.

DelegatingFilterProxy

또 다시 많은 용어들이 등장하는데, 사실 FilterChainProxyDelegatingFilterProxy의 위임을 받는 bean입니다.

Security Filter에는 FilterChainProxy를 가진 (Spring Container 내부에서는 springSecurityFilterChain라는 이름을 가진 bean입니다.) DelegatingFilterProxy가 등록되게 되고, filter chain 역할 위임을 받은 FilterChainProxy가 내부의 filter들을 통해 동작하게 됩니다.

SecurityFilterChain

공식 문서에서 자꾸 양파처럼 "~~는 사실 ~~내부에 있다."라는 식으로 이야기한다.

SecurityFilterChain이 등장하는데,

DelegatingFilterProxy는 내부에 FilterProxyChain bean을 가지고 있고, FilterChainProxy는 내부에 여러 SecurityChainProxy bean을 갖고 있다. 하나의SecurityChainProxy에는 여러 Security Filter가 존재한다.

이렇게 하는 이유는 DelegatingFilterProxy는 굳이 bean일 필요가 없다는 것과 연관있는 것 같습니다. FilterChainProxy는 하나의 filter에 불과하다고 했는데, 서블릿 컨테이너에 하나의 filter로 등록하기 위해(반드시 bean일 필요X) DelegatingFilterProxy가 존재하고, 내부에 FilterChainProxySecurityFilterChain은 bean으로 관리하게 됩니다.

이 그림이 더 정확한 구조를 나타내는 그림인 것 같습니다.

Multiple SecurityFilterChain

FilterChainProxy는 여러 SecurityFilterChain을 가진다고 했는데, FilterChainProxy가 어느 SecurityFilterChain을 사용할 지는 매치 해놓은 URI를 통해 결정하게 된다.

Security Filters

FilterProxyChain에 등록된 filter와 순서로는

ForceEagerSessionCreationFilter

ChannelProcessingFilter

WebAsyncManagerIntegrationFilter

SecurityContextPersistenceFilter

HeaderWriterFilter

CorsFilter

CsrfFilter

LogoutFilter

OAuth2AuthorizationRequestRedirectFilter

Saml2WebSsoAuthenticationRequestFilter

X509AuthenticationFilter

AbstractPreAuthenticatedProcessingFilter

CasAuthenticationFilter

OAuth2LoginAuthenticationFilter

Saml2WebSsoAuthenticationFilter

UsernamePasswordAuthenticationFilter

DefaultLoginPageGeneratingFilter

DefaultLogoutPageGeneratingFilter

ConcurrentSessionFilter

DigestAuthenticationFilter

BearerTokenAuthenticationFilter

BasicAuthenticationFilter

RequestCacheAwareFilter

SecurityContextHolderAwareRequestFilter

JaasApiIntegrationFilter

RememberMeAuthenticationFilter

AnonymousAuthenticationFilter

OAuth2AuthorizationCodeGrantFilter

SessionManagementFilter

ExceptionTranslationFilter

FilterSecurityInterceptor

SwitchUserFilter

이렇게 많습니다. 순서까지 전부 다 알고 있을 필요는 없다고 합니다.

Reference

https://docs.spring.io/spring-security/reference/servlet/architecture.html

https://docs.gigaspaces.com/security/introducing-spring-security.html

profile
울릉도에 별장 짓고 싶다
post-custom-banner

0개의 댓글