본문 바로가기
Spring

[Spring] 스프링 시큐리티 알아보기

by jaee_ 2021. 12. 17.

프로젝트를 진행하다가 비밀번호 암호화를 위해 스프링 시큐리티를 적용하려고 합니다. 사실 공식 문서를 보고, 구글링을 통해 알아보았지만 완벽하게 이해가 된 것 같지는 않습니다. 그래도 글을 작성하면 조금이라도 이해가 더 될까 싶어 작성하게 되었습니다.

 

💡 스프링 시큐리티(Spring Security)란

스프링 시큐리티는 인증, 권한 부여 및 일반적인 공격에 대한 보호를 제공하는 프레임워크입니다. 즉, 인증과 인가를 담당하는 프레임워크입니다. 

 

그렇다면 인증과 인가는 무엇일까요? 보안 용어를 잠시 살펴보도록 하죠

 

  • 접근 주체(Principal) : 보호된 리소스에 접근하는 대상
  • 인증(Authentication) : 보호된 리소스에 접근한 대상에 대해 이 유저가 누구인지, 애플리케이션의 작업을 수행해도 되는 주체인지 확인하는 과정(ex. Form 기반 Login)
  • 인가(Authorize) : 해당 리소스에 대해 접근 가능한 권한을 가지고 있는지 확인하는 과정(After Authentication, 인증 이후)
  • 권한 : 어떠한 리소스에 대한 접근 제한, 모든 리소스는 접근 제어 권한이 걸려있다. 즉, 인가 과정에서 해당 리소스에 대한 제한된 최소한의 권한을 가졌는지 확인한다.

즉, 인증은 사용자가 누구인지 확인하는 과정, 인가는 사용자가 요청하는 요청을 실행할 수 있는 권한 여부를 확인하는 절차입니다. 


💡 스프링 시큐리티(Spring Security)의 동작 방식

스프링 시큐리티는 Servlet Filter를 기반으로 합니다. 

스프링은 서블릿 컨테이너의 라이프사이클과 스프링의 ApplicationContext 사이를 연결해주는 DelegatingFilterProxy  라는 필터 프록시를 제공합니다. 서블릿 컨테이너는 서블릿 표준에 따라 필터를 등록하기 때문에 스프링에서 정의한 Bean으로는 인식하지 못합니다. DelegatingFilterProxy  는 표준 서블릿 컨테이너에 따라 필터를 등록하지만, 그 필터로 구현된 스프링 빈에게 모든 일을 위임합니다. 

 

스프링 시큐리티의 servlet 지원은 FilterChainProxy 내에 포함되어 있습니다. 이는 스프링에서 제공하는 특수필터로 SecurityFilterChain을 통해 많은 필터 인스턴스에 위임할 수 있습니다. FilterChainProxy 는 빈이기 때문에 DelegatingFilterProxy로 래핑이 되어있습니다. 

 

그리고 SecurityFilterChain 안에는 많은 필터들이 순서대로 동작합니다. 이를 그림으로 표현한 것은 다음과 같습니다. 

 

위에서 스프링 시큐리티는 인증과 인가를 담당하는 프레임워크라고 했습니다. 따라서 인증을 관리하는 인터페이스(AuthenticationManager)와 인가를 담당하는 인터페이스(AccessDecisionManager)가 존재합니다.  

 

그리고 위의 사진에서 보이는 것처럼 인증을 관리하는 AuthenticationManagerUsernamePasswordAuthenticationFilter 가 접근결정관리자는 FilterSecurityInterceptor 가 수행합니다. 

 

자 이제 인증과 인가 과정이 어떻게 이루어졌는지 알아보도록 하겠습니다. 


🔍 UsernamePasswordAuthenticationFilter 

 

SecurityContextHolder 

UsernamePasswordAuthenticationFilter 를 알아보기에 앞서 인증관련해 알아두어야 할 것이 있습니다. 바로 SecurityContextHolder 라는 것입니다. 이는 스프링 시큐리티가 인증자의 세부정보를 저장하는 곳입니다. SecurityContextHolder 는 다음과 같이 구성되어있습니다. 

 

 

AuthenticationManager (인증관리자)

그리고 이전에 언급했듯이 인증을 관리하는 인터페이스인 AuthenticationManager에 관해서도 설명드리겠습니다. AuthenticationManager는 스프링 시큐리티 필터가 인증을 수행하는 방법을 정의하는 API 입니다. 이를 구현한 다음 반환되는 인증은 AuthenticationManager를 호출한 컨트롤러에 의해 SecurityContextHolder 에 설정됩니다.

 

SecurityContextHolder 를 직접 설정하는 방법도 있습니다. 만약 직접 설정한다면 굳이 AuthenticationManager를 통해 반환을 받아내지 않아도 되겠죠~? 직접 설정한 인증 세부정보가 존재하니까요! 즉, 무조건 AuthenticationManager을 구현해야한다는 것은 아닙니다. 

 

AuthenticationManager의 구현체는 많지만 일반적으로 ProviderManager를 많이 사용합니다. 

ProviderManager는 AuthenticationProvider로 구성된 AuthenticationProviders 목록에 위임하고 AuthenticationProvider에서 인증작업이 이루어집니다. 

 

UsernamePasswordAuthenticationFilter 

자, 이제 UsernamePasswordAuthenticationFilter 의 동작 방식을 알아보도록 하겠습니다. 다음은 공식문서에 나와있는 UsernamePasswordAuthenticationFilter 의 동작방식 사진입니다.

 

UsernamePasswordAuthenticationFilter 는 form Login 요청시 처리하는 필터입니다. (인증필터)

동작 방식은 다음과 같습니다.

  1. form 을 통해서 이름과 비밀번호가 제출이 되면 필터는 요청을 통해 들어온 사용자의 이름과 비밀번호를 인증할 수 있는 UsernamePasswordAuthenticationToken을 만듭니다. 
  2. 이렇게 만들어진 UsernamePasswordAuthenticationToken은 AuthenticationManager 에 전달됩니다. 
    • AuthenticationManager 는 실제 인증을 할 AuthenticationProvider에게 다시 전달한다. 
  3. 인증에 실패하면
    • SecurityContextHolder가 지워집니다.
    • RememberMeServices.loginFail이 호출됩니다. (구현되어있지않다면 작동하지 않습니다.)
    • AuthenticationFailureHandler가 호출됩니다. 
  4. 인증에 성공하면 
    • SessionAuthenticationStrategy에 새 로그인이 통보됩니다.
    • SecurityContextHolder에 인증이 설정됩니다.
    • RememberMeServices.loginSuccess가 호출됩니다. (구현되어있지않다면 작동하지 않습니다.)
    • ApplicationEventPublisher를 발행합니다.
    • AuthenticationSuccessHandler가 호출됩니다. 

🔍 FilterSecurityInterceptor

마지막에 위치한 필터로, 인증된 사용자에 대해 요청의 승인/거부 여부를 결정합니다. 

FilterSecurityInterceptor

동작방식

  1. FilterSecurityInterceptor 가 SecurityContextHolder에서 인증을 가져옵니다.
  2. FilterSecurityInterceptor HttpServletRequest, HttpServletResponse, FilterChain으로부터 FilterInvocation을 만듭니다.
  3. ConfigAttributes를 얻기 위해 SecurityMetadataSource에 FilterInvocation을 넘깁니다.
  4. Authentication, FilterInvocation, ConfigAttributes를 AccessDecisionManager로 넘깁니다.
    • 인증이 실패하면, AccessDeniedException을 발생시킨다. 이 경우엔 위에서 언급했던 ExceptionTranslationFilter가 AccessDeniedException를 처리합니다.
    • 인증 성공시, FilterSecurityInterceptor는 어플리케이션을 다음 동작으로 진행시킵니다.

 

동작방식을 봤지만, 이해가 되지않는 용어들이 있습니다. 이해가 안되는 용어에 대해서 조금 알아보도록 하겠습니다. 

 

AccessDecisionManager

AccessDecisionManager는 AbstractSecurityInterceptor에 의해 호출되며 최종 접근 제어 결정을 담당합니다. AccessDecisionManager 는 다음과 같은 총 세가지의 메소드가 있습니다. 

void decide(Authentication authentication, Object secureObject,
	Collection<ConfigAttribute> attrs) throws AccessDeniedException;

boolean supports(ConfigAttribute attribute);

boolean supports(Class clazz);

decide() 메소드는 권한 부여 결정을하기 위해 필요한 모든 관련 정보인 Authentication, ConfigAttribute을 전달받으며, 전달받은 parameter에 대한 access 제어를 결졍하는 역할을 합니다. 

 

supports()는 각 전달받은 파라미터를 처리 가능한지 판단합니다.

 

접근결정 유형

Spring Security는 투표권을 집계하는 AccessDecisionManager의 구현체는 다음과 같은 세 가지가 있습니다.

  • AffirmativeBased: 여러 Voter중에 하나라도 허용되면 허용된다. (기본 전략)
  • ConsensusBased: 다수결
  • UnanimousBased: 만장일치

위의 클래스는 모두  voter 를 통해 진행됩니다. 

 

Voter 

접근을 허가할지 거부할지 판단하는 인터페이스입니다.

AsscessDecisionVoter 인터페이스는 총 세 가지의 메소드로 구성되어있습니다. int를 리턴하며 이 값들을 이용하여 권한 부여 결정을 받을 수 있습니다. 승인 결정에 대한 의견이 없는 경우 ACCESS_ABSTAIN을 반환합니다. 의견이 있는 경우 ACCESS_DENIED 또는 ACCESS_GRANTED 중 하나를 반환해야 합니다.

public interface AccessDecisionVoter<S> {

	int ACCESS_GRANTED = 1;
	int ACCESS_ABSTAIN = 0;
	int ACCESS_DENIED = -1;

	boolean supports(ConfigAttribute attribute);
	boolean supports(Class<?> clazz);
	int vote(Authentication authentication, S object, Collection<ConfigAttribute> attributes);

}

 

Spring Security에는 AccessDecisionManager 투표를 기반으로 승인 여부를 결정합니다. 또한, Spring Security에는 투표를 기반으로 하는 여러 구현이 포함되어 있습니다.

 

다음은 AccessDecisionManager 의 구현체들과 구현체를 결정하는데 필요한  Voter, SecurityConfig에 의해 전달되는 ConfigAttribute의 관계입니다. 

Voting Decision Manager

이 부분에 대해서는 조금 더 학습한 후에 글을 보충하도록 하겠습니다. 

 


참고한 사이트

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

https://coding-start.tistory.com/153

댓글