Programming/Spring Boot

Spring Security 시큐리티 동작 원리 이해하기 - 1

Jan92 2021. 9. 9. 23:31

Spring Security

 

스프링 시큐리티 (Spring Security)는 스프링 기반 어플리케이션의 보안(인증과 권한, 인가)을 담당하는 스프링 하위 프레임워크입니다.

보안과 관련해서 체계적으로 많은 옵션들을 제공해주기 때문에 개발자의 입장에서는 하나하나 보안 관련 로직을 작성하지 않아도 된다는 장점이 있습니다.

 

알고 시작해야할 Spring Security 용어 및 기본 이론

 

  • 인증 Authentication : 해당 사용자가 본인이 맞는지를 확인하는 절차
  • 인가 Authorization : 인증된 사용자가 요청한 자원에 접근 가능한지를 결정하는 절차
  • 접근 주체 Principal : 보호받는 Resource에 접근하는 대상
  • 비밀번호 Credential : Resource에 접근하는 대상의 비밀번호
  • 권한 : 인증된 주체가 어플리케이션의 동작을 수행할 수 있도록 허락되어 있는지를 결정합니다.
              - 인증 과정을 통해 주체가 증명된 이후 권한을 부여할 수 있습니다.
              - 권한 부여에도 두 가지 영역이 존재하는데 웹 요청 권한과 메서드 호출 및 도메인 인스턴스에 대한 접근 권한 부여가 있습니다.

인증 (Authentication) -> 인증 성공 후 -> 인가 (Authorization)

Spring Security는 기본적으로 인증 절차를 거친 후에 인가 절차를 진행하게 되며, 인가 과정에서 해당 리소스에 대한 접근 권한이 있는지를 확인하게 됩니다. 이러한 인증과 인가를 위해 Principal을 아이디로, Credential을 비밀번호로 사용하는 인증 방식을 사용합니다.

 

 

* 기본적으로 인증 정보는 최종적으로 인메모리 세션 저장소인 SecurityContextHolder에 세션 - 쿠키 방식으로 저장됩니다.

 

 

 


 

 

동작 과정

 

Spring Security 동작 과정

 

전체적인 동작 과정이 담긴 이미지입니다. 처음부터 부분 부분 나눠서 보겠습니다.

 

 


 

 

request

 

HTTP 요청 수신

 

Security에는 일련의 필터 체인이 있습니다. 그래서 클라이언트(브라우저)로 부터 요청(Request)이 오면 인증 및 권한 부여 목적으로 일련의 필터를 거치게 됩니다.

 

일반적으로 ID, PASSWORD 기반의 인증이라고 할 경우 가장 먼저 Application Filters라는 필터 뭉치에 도달합니다. 그리고 그 필터들 중 Authentication Filters라는 필터 뭉치에 다시 도달합니다. 다음으로 username, password를 사용하는 form 기반 인증을 처리하는 필터인 UsernamePasswordAuthenticationFilter에 도착하게 됩니다.

 

 

* id, password가 아닌 OAuth2.0 인증이나 JWT를 이용한 인증을 하려고 할 때는 해당 필터가 아닌 다른 필터를 거치게 됩니다.

(ex : OAuth2ClientAuthenticationProcessingFilter)

 

 

HTTP 요청 수신

 

UsernamePasswoardAuthenticationFilter에 요청이 도착하면 해당 클래스의 attempAuthentication(request, response) 메서드가 동작합니다. 이 메서드는 request로부터 username, password를 가지고 와서 사용자 자격 증명을 기반으로 한 UsernamePasswordAuthenticationToken(Authentication)을 생성합니다.

 

이어서 생성된 UsernamePasswordAuthenticationToken(Authentication)을 가지고 AuthenticationManager (실질적으로는 구현체인 ProviderManager)에게 인증을 진행하도록 위임합니다.

 

 

* UsernamePasswordAuthenticationToken은 Authentication 인터페이스의 구현체입니다. 객체 간의 구조를 뜯어보면 UsernamePasswordAuthenticationToken이 AbstractAuthenticationToken을 extends, AbstractAuthenticationToken은 Authentication을 implements 하는 구조로 설계되어 있습니다.

 

* 모든 접근 주체는 Authentication을 생성합니다. 이것은 최종적으로 SecurityContext에 보관되고 사용됩니다.

 

 


 

 

AuthenticationManager, AuthenticationProvicers, ProviderManager

 

AuthenticationManager, AuthenticationProvicer(s), ProviderManager

 


 

public interface AuthenticationManager {
	Authentication authenticate(Authentication authentication) throws AuthenticationException;
}

- AuthenticationManager (Interface)

 

Authentication 객체를 받아 인증하고, 인증되었다면 인증된 Authentication 객체를 돌려주는 authenticate() 메서드를 구현하도록 하는 인터페이스입니다. 이 메서드를 통해 인증이 되면 isAuthenticated(boolean) 값을 true로 바꿔줍니다.

 


 

for (AuthenticationProvider provider : getProviders()) {
	if (!provider.supports(toTest)) {
		continue;
	}
	if (logger.isTraceEnabled()) {
		logger.trace(LogMessage.format("Authenticating request with %s (%d/%d)",
				provider.getClass().getSimpleName(), ++currentPosition, size));
	}
	try {
		result = provider.authenticate(authentication);
		if (result != null) {
			copyDetails(authentication, result);
			break;
		}
	}
	catch (AccountStatusException | InternalAuthenticationServiceException ex) {
		prepareException(ex, authentication);
		throw ex;
	}
	catch (AuthenticationException ex) {
		lastException = ex;
	}
}

- ProviderManager (Class)

 

AuthenticationManager의 구현체로 스프링에서 인증을 담당하는 클래스로 볼 수 있습니다. 스프링 시큐리티가 생성, 등록하고 관리하는 스프링 빈이기 때문에 직접 구현할 필요는 없습니다.

 

ProviderManager 클래스는 인증을 담당하고 있지만 실제로 직접 인증 과정을 진행하는 게 아니라 멤버 변수로 가지고 있는 AuthenticationProvider(s)에게 인증을 위임하고 그중에서 인증 처리가 가능한 AuthenticationProvider 객체가 인증 과정을 거쳐서 인증에 성공하면 요청에 대해 ProviderManager가 인증이 되었다고 알려주는 방식입니다.

인증이 되었다고 알려주는 건 AuthenticationManager 인터페이스의 authenticate() 메서드의 리턴 값인 Authentication 객체 안에 인증 값을 넣어주는 것으로 처리합니다.

 

* 위 코드는 authenticate() 메서드의 일부로 for문을 통해 provider를 하나씩 가지고 와서 supports() 메서드로 해당 provider가 인증 처리를 할 수 있는지 확인하고 result != null 때까지 반복하는 것을 볼 수 있습니다.

 


 

public interface AuthenticationProvider {

	Authentication authenticate(Authentication authentication) throws AuthenticationException;

	boolean supports(Class<?> authentication);
}

- AuthenticationProvider (Interface)

 

AUthenticationManager와 같은 authenticate() 메서드를 통해 인증 과정이 진행됩니다.

boolean supports(Class<?>) 메서드는 앞에서 필터를 통해 보내준 Authentication 객체를 이 AuthenticationProvider가 인증 처리가 가능한 클래스인지를 확인하는 메서드입니다.

 


 

지금까지의 흐름을 살짝 정리해보면

 

  1. ID, PASSWORD 기반의 인증 요청을 UserPasswordAuthenticationFilter에서 가로챕니다. 
  2. UsernamePasswordAuthenticationToken(Authentication) 객체를 AuthenticationManager에 넘깁니다.
    (실질적으로는 AuthenticationManager 인터페이스를 구현한 ProviderManager)

  3. ProviderManager는 여러 AuthenticationProvider를 순회하면서 UsernamePasswordAuthenticationToken을 처리해줄 AuthenticationProvider를 찾습니다.

 

이어서 진행되어야 할 과정은 AuthenticationProvider가 인터페이스이기 때문에 해당 인터페이스를 구현하는 클래스를 만들어서 인증 처리를 하는 로직이 구현되면 됩니다.

 

포스팅이 길어져 2부로 나눠서 진행하겠습니다.

 

 

이어지는 포스팅

 

Spring Security 시큐리티 동작 원리 이해하기 - 2

Spring Security 시큐리티 동작 원리 이해하기 - 1 스프링 시큐리티 (Spring Security)는 스프링 기반 어플리케이션의 보안(인증과 권한, 인가)을 담당하는 스프링 하위 프레임워크입니다. 보안과 관련해

wildeveloperetrain.tistory.com

 

참고자료

https://wildeveloperetrain.tistory.com/165

https://jeong-pro.tistory.com/205
https://mangkyu.tistory.com/76s
https://springbootdev.com/2017/08/23/spring-security-authentication-architecture/