Spring Security 공식 문서 톺아보기(2)

Authentication(인증) 관련 Architecture

Overview

  • SecurityContextHolder - 스프링 시큐리티가 인증된 주체를 저장하는 객체
  • SecurityContext - 현재 인증된 유저에 대한 Authentication 객체를 저장. SecurityContextHolder로부터 얻을 수 있다.
  • Authentication - 유저가 인증을 위해 제공한 자격이나 SecurityContext에 저장된 현재 인증 사용자에 대한 객체로써 AuthenticationManager에 input이 될 수 있음.
  • GrantedAuthority - Authentication 객체의 주체(principal)에게 부여된 권한 객체(role, scope 등)
  • AuthenticationManager - Spring Security Filter들이 인증을 수행하는 방법을 정의한 API
  • ProviderManager - 가장 범용적이고 일반적인 AuthenticationManager의 구현체
  • AuthenticationProvider - 구체적인 인증을 수행. ProviderManager에 의해 호출됨
  • AuthenticationEntryPoint - 클라이언트의 요청 자격 증명을 위해 사용됨(로그인 페이지로 redirect, WWW-Authenticate 응답 헤더 등)
  • AbstractAuthenticationProcessingFilter - 인증을 위해 사용되는 기본 Filter

SecurityContextHolder(스프링 시큐리티 인증 모델의 핵심)

SecurityContextHolder누가 인증되었는지에 대한 디테일 정보를 저장한다.

스프링 시큐리티는 SecurityContextHolder의 정보가 어떻게 채워지는지는 관심이 없다. 단지 정보를 갖고있다면 현재 인증된 유저에 대한 정보로 간주하고 사용할 뿐이다.

즉, 사용자가 인증되었음을 나타내는 가장 간단한 방법은 아래와 같이 SecurityContextHolder를 직접 세팅하는 것이다.

SecurityContextHolder.createEmptyContext() 코드에 주의해야 한다. 새로운 SecurityContext 인스턴스를 생성하는것이 중요한데, 만일 SecurityContextHolder.getContext().setAuthentication(authentication) 의 경우 멀티 스레드 환경에서 예기치 않은 결과가 나올 수 있다.

만일 인증된 주체에 대한 정보가 필요하다면 SecurityContextHolder를 통해서 얻을 수 있게된다.

(중요) 기본적으로 SecurityContextHolder디테일 정보를 저장하기 위해 ThreadLocal을 사용하는데

이는 SecuritnContext가 메서드 인수로 명시적으로 전달되지 않더라도 SecurityContext는 항상 동일한 스레드의 메서드에서 사용할 수 있다. (ThreadLocal!!)

이처럼 ThreadLocal을 이용한 방법은 보안 주체(security principal)의 요청이 처리된 후 thread clear만 신경써주면 아주 안전하다.

스프링 시큐리티의 FilterChainProxySecurityContext항상 Cleared 되는 것을 보장한다.


SecurityContext

SecurityContextHolder로부터 얻을 수 있는 SecurityContextAuthentication 객체를 갖는다.


Authentication

Authentication 객체는 두 가지의 주요한 목적을 갖는다.

  • AuthenticationManager의 input으로 전달되는데, 유저가 인증을 위해 제공했던 자격 증명(credential)을 제공하기 위함
  • 현재 인증된 유저를 나타내기 위함. 현재의 Authentication 객체는 SecurityContext로부터 얻을 수 있다.

Authentication은 세 가지 정보를 갖는다.

  • principal - user의 식별자. 만약 username/password로 인증을 했다면 UserDetails의 인스턴스
  • credentials - 주로 password. 주로 유저가 인증을 거친 이후 삭제됨
  • authorities - GrantedAuthority 객체들로써, 유저에게 부여된 상위 개념들(role, scope 등)

GrantedAuthority

사용자에게 부여된 상위 수준의 권한으로써 role, scope 등이 그것들

GrantedAuthority 객체들은 Authentication.getAuthorities() 메서드를 이용해 얻을 수 있다. 이 메서드는 GrantedAuthority 객체들의 Collection을 제공한다.

GrantedAuthorityprincipal에 부여된 권한이다.

이러한 권한들은 보통 roles로 표현되는데 ROLE_ADMINISTRATOR, ROLE_HR_SUPERVISOR 등이 예시다.

이러한 role들은 이후 웹 권한 부여, 메서드 권한 부여, 도메인 객체 권한 부여를 위해 사용된다.

일반적으로 GrantedAuthority 객체들은 어플리케이션 전체에 대한 권한이다. 다시말해 특정 도메인 객체를 위한 권한으로 부여되지 않는다.

예를들어, 54번 Employee 인스턴스의 권한을 나타내기 위해서 GrantedAuthority를 사용할 수 없다. 만일 1천, 1만개 이상의 Employee가 있다고 하면 위와 같은 상황에서 메모리 초과에러가 날 것이다.

그러니 개별 인스턴스에 대한 권한이 아니라 프로젝트의 도메인 객체를 위한 권한을 사용하는 것이 좋다.


AuthenticationManager

AuthenticationManager는 스프링 시큐리티의 Filter들이 인증을 수행하기 위한 API를 정의한다.

AuthenticationManager를 호출한 Spring Security Filter는 반환된 AuthenticationSecurityContextHolder에 설정한다.

만일 Spring Security Filter와 통합하지 않는경우 SecurityContextHolder를 직접 설정할 수 있으며 AuthenticationManager를 사용할 필요가 없다.

AuthenticationManager의 구현체 중 가장 많이 사용되는 것이 ProviderManager다.


ProviderManager

ProviderManager는 가장 일반적으로 사용되는 AuthenticationManager의 구현체다.

이 객체의 주 역할은 AuthenticationProvider들에게 위임하는 것이다.

AuthenticationProvider인증 성공, 실패에 대한 결정을 할 수 있다.

만일 인증에대한 성공, 실패 여부를 결정할 수 없다면 다른(다운스트림) AuthenticationProvider가 결정하도록 전달할 수 있다.

그럼에도 설정된 모든 AuthenticationProvider가 인증 여부를 결정할 수 없으면 AuthenticationException을 던진다.

AuthenticationProvider는 특정 타입의 인증을 수행한다.

예를들어 어떤 AuthenticationProvider는 username/password 검증을 수행할 수 있고 다른 하나는 SAML assertion에 대한 인증을 수행할 수 있다.

이는 하나의 AuthenticationManager 빈만을 노출하지만, 각각의 AuthenticationProvider특정(개별적인) 타입의 인증을 수행하는 것을 가능케한다. (퍼사드 패턴처럼 보이는군!)

ProviderManager는 또한 선택적으로 Parent AuthenticationManager를 가질 수 있다.

만일 어떤 ProviderManager가 가진 AuthenticationProvider들이 모두 인증을 수행하지 못한다면 이 때 Parent가 지원해준다.

일반적으로 어떤 타입의 AuthenticationManager든 parent가 될 수 있지만 일반적으로 ProviderManager 타입인 경우가 대부분이다.

또한 다수의 ProviderManager 인스턴스들이 하나의 Parent Authentication을 공유할 수 있다.

이 경우는 SecurityFilterChain이 다수지만 동일한 인증이 필요한 경우 자주 사용되는 시나리오다.

기본적으로 ProviderManager는 요청에 따른 인증이 성공되어 반환된 Authentication에 존재하는 민감한 credential 정보를 지우는데, 이는 HttpSession scope 라이프사이클보다 오래 민감 정보가 유지되는 것을 방지한다.


AuthenticationProvider

다수의 AuthenticationProvider들은 ProviderManager 내에 주입될 수 있다. 각 AuthenticationProvider특정 타입의 인증을 수행한다.

예를들어 DaoAuthenticationProvider는 username/password 기반의 인증을 지원하고 JwtAuthenticationProvider는 JWT 토큰 기반의 인증을 지원한다.


AuthenticationEntryPoint를 이용하는 요청 자격 증명

AuthenticationEntiryPoint는 클라이언트의 자격 증명 요청에 대한 HTTP Response를 전송하기 위해 사용된다.

클라이언트가 서버의 어떤 리소스를 요청할 때 username/password와 같은 자격 증명을 포함할 때가 있다. 이러한 경우 스프링 시큐리티는 클라이언트에게 자격 증명을 요청하기 위한 HTTP Response를 전달하지 않아도 된다.

그러나 다른 경우 클라이언트는 접근 권한이 필요한 리소스에 대해서 인증되지 않은 요청을 보낼 때가 있다. 이러한 경우 AuthenticationEntiryPoint의 구현체가 클라이언트에게 (HTTP Response로)자격 증명 요청을 하기 위해 사용된다.

AuthenticationEntryPoint의 구현체는 주로 로그인 페이지 redirect를 수행하고 WWW-Authenticate 헤더 등으로 응답한다.


AbstractAuthenticationProcessingFilter

이름도 긴 AbstractAuthenticationProcessingFilter유저의 자격 증명에 대한 인증을 위한 기본 Filter로써 사용된다.

자격 증명(credential)이 인증되기 전까지는 스프링 시큐리티는 AuthenticationEntryPoint를 이용하여 클라이언트에게 자격 증명을 요청한다.

그 다음, AbstractAuthenticationProcessingFilter는 전달된 인증 요청에 대해서 인증할 수 있다.

  1. 유저가 자격증명(credentials)를 제출했을 때, AbstractAuthenticationProcessingFilter는 이후 인증 대상이 되는 Authentication 객체를 HttpServletRequest로부터 생성한다. 생성된 Authentication 객체의 타입은 AbstractAuthenticationProcessingFilter의 구체 타입에 의해 결정된다.

    예를들어 Authentication의 구체 타입인 UsernamePasswordAuthenticationTokenUsernamePasswordAuthenticationFilterHttpServletRequest에 제출된 username, password를 이용하여 생성한다.

  1. 다음으로 Authentication은 인증 프로세스 처리에 사용되기 위해 AuthenticationManager에 전달된다.
  2. 만일 인증이 실패한 경우
    • SecurityContextHolder가 clear된다.
    • RememberMeServices.loginFail이 호출된다.
    • AuthenticationFailureHandler가 호출된다.
  3. 만일 인증이 성공한 경우
    • SessionAthenticationStrategy가 새 로그인에 대한 알림을 받는다.
    • 인증된 AuthenticationSecurityContextHolder에 설정이 되고, 이후 SecurityContextPersistenceFilterSecurityContextHttpSession에 저장한다.
    • ApplicationEventPublisherInteractiveAuthenticationSuccessEvent를 발행한다.
    • AuthenticationSuccessHandler가 호출된다.