스프링 시큐리티 - 인증 아키텍처

Jayong·2025년 9월 8일

스프링 시큐리티

목록 보기
4/4

개요

인증 아키텍처 공식 문서

앞서 살펴봤던 전체 개요에 이어서 '인증' 부분 관련 아키텍처를 살펴보려 한다.

인증 아키텍처와 관련된 필터 및 메서드들의 간단한 설명은 아래와 같다.


  • SecurityContextHolder : Spring Secuirty가 인증된 사용자의 세부 정보를 저장하는 곳
  • SecurityContext : SecurityContextHolder에서 획득되어 현재 인증된 사용자의 인증 정보를 포함한다.
  • Authentication : AuthenticationManager의 입력으로 사용되어 사용자가 인증울 위해 제공한 자격증명이나 SecurityContext에서 현재 사용자를 나타내는 역할을 한다.
  • GrantedAuthority : 인증된 주체(Principal)에게 부여된 권한
  • AuthenticationManager : Spring Security의 필터가 인증을 수행하는 방식을 정의하는 API
  • ProviderManager : AuthenticationManager의 가장 일반적인 구현체
  • AuthenticationProvider : ProviderManager가 특정 유형의 인증을 수행하기 위해 사용된다.
  • Request Credentials with AnthenticationEntryPoin : 클라이언트로부터 자격증명을 요청하는 데 사용된다. (페이지 리디렉션, 응답 전송 등)
  • AbstractAuthenticationProcessingFilter : 인증에 사용되는 기본 필터. 인증의 전체 흐름과 각 구성 요소가 어떻게 상호작용하는데 이해하는 데 도움이 된다.


SecurityContextHolder

SpringSecurity의 핵심이 되는 모델이며, SecurityContext를 포함한다.
SecurityContextHolder는 Spring Security가 인증된 사용자의 세부 정보를 저장하는 곳이다. Spring Security는 SecurityContextHolder가 어떻게 채워져있는지 상관없이 값이 포함되어 있는 경우에만 인증된 사용자로 사용된다.

즉, 사용자가 인증되었다는 것을 표시하는 가장 간단한 방법은 SpringContextHolder를 직접 설정하는 것이다.


설정방법

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

SecurityContextHolder.setContext(context);

  1. SecurityContext를 생성한다. 멀티 스레드 간 경합을 피하기 위해 SecurityContextHolder.getContext().setAuthentication(authentication)을 사용하는 대신 새로운 SecurityContext 인스턴스를 생성해야 한다.
  2. 새로운 Authentication객체를 생성한다.
    1. Spring Security는 SecurityContext에 설정된 Authentication 구현 유형에 대해 신경쓰지 않는다.
    2. 실제 개발 환경에서는 일반적으로 UsernamePasswordAuthenticationToken(UserDetails, password, authorities)를 사용한다.
  3. SecurityContextSecurityContextHolder에 설정한다. Spring Security는 이 정보를 권한 부여에 사용한다.

인증된 주체에 대한 정보를 얻기 위해선 SecurtiyContextHolder에 접근한다!


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을 사용하는 것은 현재 principal 요청이 처리된 후 스레드를 정리하는 경우 매우 안전하다.
    SpringSecurity의 FilterChainProxySecurityContext가 항상 정리되도록 보장한다.

일부 응용 프로그램은 스레드와의 특정 상호작용 방식 때문에 ThreadLocal을 사용하는 것이 적합하지 않을 수 있다.
예시 : Swing 클라이언트는 JVM 내의 모든 스레드가 동일한 보안 컨텍스트를 사용하도록 하는 것이 원할 할 수 있다.

이에 대해 시작 시 SecurityContextHolder에 전략을 구성하여 컨텍스트가 어떻게 저장되도록 할지 지정할 수 있다.

  • 독립 실행형 애플리케이션 : SecurityContextHolder.MODE_GLOBAL 전략을 사용한다.
  • 다른 애플리케이션의 경우 보안 스레드에서 생성된 스레드도 동일한 보안 신원을 상속하도록 하는게 좋을 방법 일 수 있으며, 이 경우 SecurityContextHolder.MODE_INHERITABLETHREADLOCAL를 사용한다.
  • 기본 설정인 SecurityContextHolder.MODE_THREADLOCAL를 변경하는 법에는 두가지 방법이 존재한다.
    1. 시스템 속성 설정
    2. SecurityContextHolder의 정적 메서드를 호출한다.

SecurityContext

  • SecurityContextSecurityContextHolder에서 얻을 수 있다.
  • SecurityContext 내부에는 Authentication객체가 포함된다.

Authentication

Authentication 인터페이스는 Spring Security 내에서 두 가지 주요 목적을 수행한다.

  1. AuthenticationManager에 사용자가 인증을 위해 제공한 자격 증명(입력한 정보)을 제공하는 입력으로 사용한다. 이 경우 isAuthenticated()false를 반환한다.
  2. 현재 인증된 사용자를 나타낸다. SecurityContext에서 얻을 수 있다.

Authentication에 포함된 요소들

  • principal : 사용자를 식별한다. username/password로 인증할 경우엔 UserDetails의 인스턴스가 사용된다.
  • credentials : 주로 비밀번호가 사용된다. 대부분의 경우 사용자가 인증된 이후 유출되지 않도록 삭제된다.
  • authorities : GrantedAuthority인스턴스는 사용자에게 부여된 고수준 권한을 나타낸다. (역할(role)과 범위(scope) 등이 예시이다.)

GrantedAuthority

GrantedAuthority 인스턴스는 사용자에게 부여된 고수준 권한이다. 예시로는 역할(role)과 범위(scope)가 존재

GrantedAuthority 인스턴스는 Authentication.getAuthorities() 메서드를 통해 얻을 수 있다.
GrantedAuthority 객체의 컬렉션을 제공하며, GrantedAuthority는 예상대로 주체(principal)에게 부여된 권한을 의미

Role

- `ROLE_ADMINISTRATOR`
- `ROLE_HR_SUPERVISOR`

역할은 웹 권한 부여, 메서드 권한 부여, 도메인 객체 권한 부여에 대해 나중에 구성된다.
사용자 이름/비밀번호 기반 인증을 사용할 때 GrantedAuthority 인스턴스는 일반적으로 UserDetailsService를 통해 조회 할 수 있다.

일반적으로 GrantedAuthority 객체는 애플리케이션 전체에 적용되는 권한으로, 특정 도메인 객체에 제한되지 않는다.


AuthenticationManager

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

인증 결과는 컨트롤러가 AuthenticationManager를 호출한 후 SecurityContextHolder에 설정한다.

Spring Security의 필터 인스턴스와 통합하지 않는 경우 SecurityContextHolder를 직접 설정할 수 있으며 이 경우엔 AuthenticationManager를 사용할 필요가 없다.

AuthenticationManager의 구현은 무엇이든 가능하지만, 가장 일반적인 구현은 ProviderManager이다.


ProviderManager

  • ProviderManagerAuthenticationManager의 가장 일반적으로 사용되는 구현체

ProviderManagerAuthenticationProvider 인스턴스의 목록에 위임한다.

AuthenticationProvider는 인증이 성공(successful) / 실패(fail) / 결정 실패 또는 결정을 내릴 수 없음을 표시하고 하위 AuthenticationProvider가 결정하도록 위임한다.

구성된 AuthenticationProvider 인스턴스 중 어느 것도 인증을 수행할 수 없는 경우, ProviderNotFoundException이 발생하며, 이 예외는 ProviderManager가 전달된 인증 유형을 지원하도록 구성되지 않았음을 나타내는 특수한 AuthenticationException을 반환함을 의미한다.

  • AuthenticationProvider는 특정 유형의 인증을 수행하는 로직을 알고있다.
    즉, 각각의 AuthenticationProvier 마다 인증하는 부분이 다르며, 자신의 영역이 아닌 부분에 대해서는 하위의 AuthenticationProvider에게 전달하여 인증을 진행하도록 한다.
    이를 통해, Spring Security는 단일 AuthenticationManger 빈(Bean)만 노출 할 수 있다는 장점이 있다.

ProviderManager는 또한 선택적 부모 AuthenticationProvider를 구성하도록 허용하여, 어떠한 AuthenticationProvider도 인증을 수행할 수 없는 경우에 부모 객체가 참조되게 된다.


또한, 여러 ProviderManager가 동일 부모 AuthenticationManager를 공유하는 것도 가능하다.
여러 SecurityFilterChain 인스턴스가 일부 인증 기능을 공유하지만(공통 부모 AuthenticationProvider), 동시에 서로 다른 인증 메커니즘을 가질 수 있는 경우에 유용한 방법이다.


기본적으로 ProviderManager는 성공적인 인증 요청으로 반한된 인증 객체에서 민감한 부분을 제거하려 하는데, 이는 HttpSession와 같은 곳에서 오랜시간 방치되는 것을 방지하기 위해서이다.

CredentialsContainer 인터페이스는 인증 과정에서 핵심적인 역할은 한다.
더 이상 필요하지 않는 자격 증명 정보를 제거하여, 민감한 데이터가 필요 이상으로 오래 보관되지 않도록 함으로써 보안을 강화한다.

민감한 부분의 제거는 사용자 객체의 캐시를 사용할 때 문제가 발생할 수 있다. 예를 들어, 무상태 애플리케이션의 성능을 개선하기 위해 캐시를 사용하는 경우, 인증(Authentication) 캐시 내의 객체(예: UserDetails)에 대한 참조가 포함되어 있었는데, 이 객체의 자격 증명이 제거되면, 캐시된 값을 사용하여 인증을 수행할 수 없다.


따라서, 캐시를 사용할 경우 이 점을 고려해야 한다. 해결책은 캐시 구현 또는 반환된 인증 객체를 생성하는 인증 제공자(AuthenticationProvider)에서 객체의 복사본을 먼저 만들거나 ProviderManagereraseCredentialsAfterAuthentication 속성을 비활성화 하는 방법을 이용할 수 있이다.


AuthenticationProvider

ProviderManager에 여러 개의 AuthenticationProvider 인스턴스를 주입할 수 있다.
AuthenticationProvider는 특정 유형의 인증을 수행한다. 예시로, DaoAuthenticationProvider는 사용자 이름/비밀번호 기반 인증을 지원하고, JwtAuthenticationProvider는 JWT 토큰 인증을 지원한다.


인증 정보 요청을 위한 AuthenticationEntryPoint

AuthenticationEntryPoint는 클라이언트로부터 인증 정보를 요청하는 HTTP 응답을 전송하기 위해 사용된다.

때로는 클라이언트가 자발적으로 인증 정보(예: 사용자 이름과 비밀번호)를 포함하여 리소스를 요청하는데, 이러한 경우 Spring Security는 클라이언트로부터 인증 정보를 요청하는 HTTP 응답을 제공할 필요가 없다. 왜냐하면 이미 인증 정보가 포함되어 있기 때문

다른 경우, 클라이언트가 권한이 없는 리소스에 대한 인증되지 않은 요청을 할 경우 AuthenticationEntryPoint의 구현체가 클라이언트로부터 자격 증명을 요청한다. AuthenticationEntryPoint 구현체는 로그인 페이지로 리디렉션하거나 WWW-Authenticate 헤더로 응답하거나 다른 조치를 취할 수 있다.


AbstractAuthenticationProcessingFilter

AbstractAuthenticationProcessingFilter는 사용자의 자격 증명을 인증하는 기본 필터로 사용한다.
1. 자격 증명을 인증하기 전에 Spring Security는 일반적으로 AuthenticationEntryPoint를 사용하여 자격 증명을 요청한다.
2. AbstractAuthenticationProcessingFilter는 제출된 모든 인증 요청을 인증할 수 있다.

  1. 사용자가 자격 증명을 제출하면 AbstractAuthenticationProcessingFilterHttpServletRequest에서 인증을 위해 Authentication 객체를 생성한다.
    (생성되는 Authentication의 유형은 AbstractAuthenticationProcessingFilter의 하위 클래스에 따라 다름)
    예를 들어, UsernamePasswordAuthenticationFilterHttpServletRequest에 제출된 사용자 이름과 비밀번호를 사용하여 UsernamePasswordAuthenticationToken을 생성
  2. AuthenticationAuthenticationMager로 전달되어 인증 진행
  3. 인증이 실패하면 실패로 처리
    • SecurityContextHolder 초기화 (인증 객체가 없으니)
    • RememberMeServices.loginFail 메서드 호출 -> remember me가 구성되지 않을 경우 아무 작업도 수행하지 않음
    • AuthenticationFailureHandler가 호출
  4. 인증이 성공할 경우 Success
    • SessionAuthenticationStrategy는 새로운 로그인 이벤트를 통지받는다.
    • AuthenticationSecurityContextHolder에 설정된다. (인증 객체 설정)
      향후에 요청시 자동으로 설정되도록 SecurityContext를 저장하려면 SecurityContextRepository#SaveContext를 명시적으로 호출해야 한다.
    • RememberMeServices.loginSuccess가 호출된다. remember me가 구성되지 않은 경우, 아무 작업도 수행하지 않는다.
    • ApplicationEventPublisherInteractiverAuthenticationSuccessEvent를 게시한다.
    • AuthenticationSucceeHandler가 호출된다.

마치며

스프링 시큐리티에서 인증 필터가 어떻게 수행되는지와 인증 객체가 어디서 관리되는지 등에 대해서 알아보았다. 여러 인터페이스, 객체들을 통해 보다 보안성이 뛰어난 인증 과정과 객체 관리 시스템을 이해하게 되었고, 인증 객체에서 정보를 얻는 방법도 알게 되었다.

profile
다양한 경험을 추구하는 개발자

0개의 댓글