Spring Security 정복하기(3)

Spring Security Authentication
이번장에서는 스프링 시큐리티에서 인증과 관련된 요소들을 알아볼것이다. 여기서는 Servlet 인증에서 사용되는 스프링 시큐리티 주요 구성 요소들을 쭉 나열하여 설명하고 이 구성요소들이 시큐리티 아키텍처에서 어떻게 적용되는지 하나씩 알아보는 방식으로 살펴볼 것이다.

SecurityContextHolder

SecurityContextHolder : 인증된 사람에 대한 세부 정보를 저장하는 곳

SecurityContextHolder는 공식문서에서 ‘At the Heart of Spring Security’s authentication’이라고 표현할 만큼 인증에 있어서 핵심적인 것 같다. 구조를 한번살펴보자. 시큐리티콘텍스트홀더

  • 어떻게 SecurityContextHolder가 채워지는지에 대한건 신경쓰지 않는다.
  • 이 안에 값이 들어있다면, 현재 인증된 사용자로 사용된다.

가장 간단하게 인증된 유저를 나타내는 방법은 그냥 직접 SecurityContextHolder를 채우는 것이다.
아래는 공식문서 샘플 코드이다.

SecurityContext context = SecurityContextHolder.createEmptyContext();
Authentication authentication =
    new TestingAuthenticationToken("username", "password", "ROLE_USER");
context.setAuthentication(authentication);

SecurityContextHolder.setContext(context);
  • 위 사진처럼 빈 SecurityContext객체를 생성하고 그 안에 Authentication 객체를 만들어 넣는다.
  • 마지막으로 SecurityContextHolder에 SecurityContext를 설정한다.
  • 샘플코드에서는 간단한 TestingAuthenticationToken을 사용하지만 실제 운영 환경에서는 UsernamePasswordAuthenticationToken(userDetails, password, authorities) 같은 객체를 사용하는 것이 일반적이다.

기존에는 SecurityContextHolder.getContext().setAuthentication(authentication) 로 컨텍스트를 불러와 사용했지만 이는 여러 스레드의 race condition 문제가 발생할 수 있기 때문에 새로운 SecurityContext인스턴스를 생성하여 사용한다.

이렇게하여 인증된 사람의 정보를 SecurityContextHolder에 저장할 수 있다.
이후 인증된 Principal의 정보를 얻기 위해서는 SecurityContextHolder에 접근하면 된다.

SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
String username = authentication.getName();
Object principal = authentication.getPrincipal();
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
  • SecurityContextHolder는 ThreadLocal을 사용하여 세부정보를 저장하므로 SecurityContext가 해당 메서드에 인수로 명시적으로 전달되지 않더라도 항상 동일한 스레드의 메서드에서 사용할 수 있다.
  • 현재 요청이 처리된 후 스레드를 지우는 데 주의하면 이런식으로 ThreadLocal을 사용하는것이 안전하다.
  • FilterChainProxy가 SecurityContext가 항상 지워지도록 보장해준다.

ThreadLocal
쓰레드 로컬은 자바에서 각 스레드마다 독립된 변수 값을 저장할 수 있게 해주는 메커니즘이다.
즉, 동일한 ThreadLocal 객체를 여러 스레드가 사용해도 각 스레드는 서로 다른 값을 갖게된다.
ThreadLocal에 저장된 데이터는 해당 스레드가 재사용될 때까지 유지되는데 만약 그 스레드가 다른 요청을 처리한다면 이전 요청의 정보를 그대로 가지고 있어 보안 문제를 불러올 수 있다. 스레드를 지우는 데 주의하라는 말이 이 맥락인 것 같다.

SecurityContext

SecurityContext : SecurityContextHolder로부터 얻을 수 있으며 Authentication을 포함한다.

Authentication

Authentication은 인터페이스로 두가지 주요 목적이 있다.

  1. 사용자 인증을 위한 AuthenticationManager의 입력으로 사용
  2. 현재 인증된 사용자를 나타내며 SecurityContext에서 얻을 수 있다.

또한 Authentication은 3가지를 포함한다.

  • Principal : 접근 주체이다. 사용자의 이름과 암호로 인증할 때 이는 종종 UserDetails의 인스턴스이다.
  • Credentials : Password 개념이다. 사용자 인증이 완료된 후 지워져서 유출되지 않도록 사용된다.
  • authorities : 권한을 나타내는 부분으로 대표적으로 역할, 권한 등이 있다.

GrantedAuthority

GrantedAuthority : Principal에 부여된 권한

  • Authentication.getAuthorities()로 가져올 수 있다. 이 메소드는 GrantedAuthority 객체의 컬렉션을 반환한다.
  • 웹 권한부여, 메서드 권한 부여 및 도메인 객체 권한 부여를 위해 구성된다.(보통 ‘ROLE’로 권한 부여)
  • ID/Password 기반의 인증을 사용하는 경우 GrantedAuthority인스턴스는 일반적으로 UserDetailsService에서 불러온다.

AuthenticationManager

AuthenticationManager : 필터가 인증을 수행하는 방식을 정의하는 API

  • AuthenticationManager를 호출한 필터 인스턴스에 의해 Authentication이 SecurityContextHolder에 설정된다.
  • AuthenticationManager는 인터페이스로써 구현방식을 정할 수 있지만 일반적으로 ProviderManager가 사용된다.

ProviderManager

ProviderManager : 가장 흔히 사용되는 AuthenticationManager의 구현체

  • ProviderManager는 AuthenticationProvider리스트를 가지며 이에 인증을 위임한다.
  • AuthenticationProvider 인스턴스 중 어느 것도 인증할 수 없는 경우 ProviderNotFoundException이 발생한다. 이는 ProviderManager가 전달된 인증 유형을 지원하도록 구성되지 않았음을 나타내는 특수 AuthenticationException이다.

프로바이더매니저

  • 각 AuthenticationProvider는 인증을 수행하는 주체이며 여러개가 있을수 있어 여러 유형의 인증을 지원한다.

AuthenticationProvider

AuthenticationProvider : 특정 유형의 인증을 수행

  • DaoAuthenticationProvider : 사용자 이름/비밀번호 기반의 인증 지원
  • JwtAuthenticationProvider : JWT 토큰 인증 지원

AuthenticationEntryPoint

AuthenticationEntryPoint : 클라이언트에게 Credentials를 요청하는데 사용
(앞서 살펴본 AuthenticationException에 등장)

  • 클라이언트가 credential을 같이 보내면 관여 X
  • 만약 리소스에 인증되지 않으면 관여
    • 로그인 페이지로 리다이렉트, WWW-Authenticate 헤더로 응답 등 구현

AbstractAuthenticationProcessingFilter

AbstractAuthenticationProcessingFilter : 인증에 사용되는 기본 필터
아래는 인증수행 과정 다이어그램이다. 앱어프필

  1. 사용자가 자격 증명을 제출하면, AbstractAuthenticationProcessingFilter는 HttpServletRequest로부터 인증을 위한 Authentication 객체를 생성한다.
    • 생성되는 Authentication 객체의 유형은 필터의 하위클래스에 따라 다르다. 예를들어 UsernamePasswordAuthenticationFilter는 UsernamePasswordAuthenticationToken을 생성한다.
  2. 생성된 Authentication객체는 인증을 위해 AuthenticationManager로 전달된다.
  3. 인증에 실패할 경우
    • SecurityContextHolder가 지워지고
    • RememberMeServices.loginFail이 호출된다.
      (만약 Remember me가 설정되지 않았다면, 아무런 효과가 없다)
    • AuthenticationFailureHandler가 호출된다.
  4. 인증에 성공할 경우
    • SessionAuthenticationStrategy가 알림을 받고,
    • Authentication 객체가 SecurityContextHolder에 설정되며,
    • RememberMeServices.loginSuccess가 호출되고,
    • AuthenticationSuccessHandler가 실행된다.

정리

이번장에서는 스프링시큐리티 내부 인증에 대한 구성요소들을 쭉 알아봤다.
SecurityContextHolder가 인증된 사람의 세부정보를 저장하며 SecurityContext 내부 Authentication객체에서 가져올 수 있다.
인증은 AuthenticationManager가 진행하며 이는 인터페이스로 AuthenticationProvider 리스트를 가지고 있는 ProviderManager가 주요 구현체이다. 따라서 인증은 AuthenticationProvider를 쭉 지나며 진행된다.
마지막으로 이러한 인증 과정은 시큐리티 필터체인 내부 AbstractAuthenticationProcessingFilter에서 시작된다.

공식문서 다음부분에는 흔히사용되는 Username/Password 기반의 인증방식에서 사용되는 내용들이 나온다. 이부분과 인가방식까지 쭉쭉 이어나가보자.