Spring/스프링 시큐리티 인 액션

[5장] 인증 구현

꾸준함. 2025. 5. 21. 23:18

주의

이 책은 Spring Security 5 버전을 기준으로 작성되었으므로, Spring Boot 3.X 버전에서는 일부 클래스가 더 이상 사용되지(deprecated) 않을 수 있습니다.

 

개요

  • 인증 논리를 담당하는 것은 AuthenticationProvider 계층이며 여기에서 요청을 허용할지 결정하는 조건과 명령을 발견할 수 있음
  • AuthenticationManager는 HTTP 필터 계층에서 요청을 수신하고 해당 책임을 AuthenticationProvider에 위임하는 구성 요소
  • 요청을 나타내는 방법을 명확히 이해하려면 Authentication 인터페이스부터 알아야 함
    • 그런 다음 인증이 성공한 후 요청 세부 사항이 처리하는 방법을 배울 수 있으며 SecurityContext 인터페이스와 스프링 시큐리티가 이를 관리하는 방법을 논의할 수 있음

 

 

1. AuthenticationProvider의 이해

  • 스프링 시큐리티에서는 AuthenticationProvider 계약으로 모든 맞춤형 인증 논리를 정의할 수 있음

 

인증 프로세스 중 요청 나타내기

  • Authentication은 인증이라는 이름이 의미하듯이 인증 프로세스의 필수 인터페이스
  • Authentication 인터페이스는 인증 요청 이벤트를 나타내며 애플리케이션에 접근을 요청한 엔티티의 세부 정보를 담으며 인증 요청 이벤트와 관련한 정보는 인증 프로세스 도중과 이후에 이용할 수 있음
  • 애플리케이션에 접근을 요청하는 사용자를 Principal이라고 지칭하며 자바 시큐리티 API의 Principal 인터페이스와 같은 개념
  • 스프링 시큐리티의 Authentication 계약은 주체만 나타내는 것이 아니라 인증 프로세스 완료 여부, 권한의 컬렉션 같은 정보를 추가로 가짐
    • 해당 계약이 자바 시큐리티 API의 Principal 계약을 확장하도록 설계됐다는 사실은 다른 프레임워크나 애플리케이션 구현과의 호환성이라는 측면에서 이점이 됨
    • 이 유연성 덕분에 다른 방식으로 인증을 구현한 애플리케이션을 스프링 시큐리티로 보다 쉽게 마이그레이션 할 수 있음

 

  • Authentication에서 숙지해야 할 메서드는 다음과 같음
    • isAuthenticated(): 인증 프로세스가 끝났으면 true를 반환하고 아직 진행 중이면 false를 반환
    • getCredentials(): 인증 프로세스에 이용된 암호나 비밀을 반환
    • getAuthorities(): 인증된 요청에 허가된 권한의 컬렉션을 반환

 

 

https://livebook.manning.com/concept/spring/authentication-request

 

맞춤형 인증 논리 구현

  • 스프링 시큐리티의 AuthenticationProvider는 인증 논리를 처리함
  • AuthenticationProvider 인터페이스의 기본 구현은 시스템의 사용자를 찾는 책임을 UserDetailsService에 위임하고 PasswordEncoder로 인증 프로세스에서 암호를 관리함

 

 

 

  • AuthenticationProvider 책임은 Authentication 계약과 강하게 결합돼 있음
  • authenticate() 메서드는 Authentication 객체를 매개 변수로 받고 Authentication 객체를 반환함
  • 인증 논리를 정의하려면 authenticate() 메서드를 구현해야 하는데, authentication 메서드를 구현하는 방법을 다음 세 항목으로 간단하게 요약 가능
    • 인증이 실패하면 메서드는 AuthenticationException 예외 발생
    • 메서드가 현재 AuthenticationProvider 구현에서 지원되지 않는 인증 객체를 받으면 null을 반환해야 하는데 이렇게 하면 HTTP 필터 수준에서 분리된 여러 Authentication 형식을 사용할 가능성이 생김
    • 메서드는 완전히 인증된 객체를 나타내는 Authentication 인스턴스를 반환해야 하고 해당 인스턴스에 대해 isAuthenticated() 메서드는 true를 반환하며, 여기에는 인증된 엔티티의 모든 필수 세부 정보가 포함됨

 

  • AuthenticationProvider 인터페이스의 두 번째 메서드는 supports(Class<?> authentication)
    • 해당 메서드는 현재 AuthenticationProvider가 Authentication 객체로 제공된 형식을 지원하면 true를 반환하도록 구현
    • 주의: 해당 메서드가 객체에 대해 true를 반환하더라도 authenticate() 메서드가 null을 반환해 요청을 거부할 수 있움

 

  • AuthenticationManager와 AuthenticationProvider가 함께 작동하는 방식은 보안성이 강화된 출입문에 복합 잠금장치를 설치한 것과 유사함
    • 해당 잠금장치는 카드나 전통적인 열쇠를 이용해 열 수 있다고 가정
    • 여기서 AuthenticationManager는 문을 열지 말지를 결정하는 잠금장치 자체에 해당하며, 이 결정 과정에서 각 인증 수단(카드, 열쇠)을 검증하는 역할을 AuthenticationProvider에게 위임
    • i.g. 사용자가 카드를 태그 하면 카드 인증을 담당하는 AuthenticationProvider가 해당 정보를 검증하고, 열쇠 인증을 담당하는 다른 AuthenticationProvider는 이 요청을 처리하지 않음
    • 이처럼 각 인증 공급자가 자신이 처리할 수 있는 인증 요청만을 수락하도록 판단하는 기준이 바로 supports() 메서드

 

https://livebook.manning.com/book/spring-security-in-action/chapter-5

 

맞춤형 인증 논리 적용

  • 맞춤형 인증 논리를 구현하는 과정은 다음과 같음
    • AuthenticationProvider 계약을 구현하는 클래스를 선언
    • 새 AuthenticationProvider가 어떤 종류의 Authentication 객체를 지원할지 결정
      • 정의하는 AuthenticationProvider가 지원하는 인증 유형을 나타내도록 supports(Class<?> c) 메서드를 재정의
      • authenticate(Authentication a) 메서드를 재정의해 인증 논리를 구현
    • 새 AuthenticationProvider 구현의 인스턴스를 스프링 시큐리티에 등록

 

https://assu10.github.io/dev/2023/11/25/springsecurity-authrorization-1/

 

2. SecurityContext

  • AuthenticationManager는 인증 프로세스를 성공적으로 완료한 뒤 요청이 유지되는 동안 Authentication 인스턴스를 저장함
    • Authentication 객체를 저장하는 인스턴스를 SecurityContext라고 함

 

https://livebook.manning.com/concept/spring/security-context

 

  • 스프링 시큐리티의 SecurityContext 인터페이스는 다음과 같음
    • SecurityContext의 주 책임은 Authentication 객체를 저장하는 것


 

  • SecurityContextHolder는 관리자 역할을 하는 객체로 SecurityContext를 관리하는 세 가지 전략을 제공함
    • MODE_THREADLOCAL: 각 쓰레드가 SecurityContext에 각자의 세부 정보를 저장할 수 있게 해 줌, request-per-thread 방식의 웹 애플리케이션에서는 각 요청이 개별 쓰레드를 가지므로 이는 일반적인 접근법
    • MODE_INHERITABLETHREADLOCAL: MODE_THREADLOCAL과 비슷하지만 비동기 메서드의 경우 SecurityContext를 다음 쓰레드로 복사하도록 스프링 시큐리티에 지시함, 해당 방식으로 @Asysnc 메서드를 실행하는 새 쓰레드가 SecurityContext를 상속할 수 있음
    • MODE_GLOBAL: 애플리케이션의 모든 쓰레드가 같은 SecurityContext 인스턴스를 보게 함

 

  • 개발자가 스프링에 알려지지 않은 쓰레드를 정의할 경우 SecurityContext의 세부 정보를 명시적으로 새 쓰레드로 복사해야 함
    • 스프링 시큐리티는 스프링의 컨텍스트에 없는 객체를 자동으로 관리할 수 없지만, 이를 위한 몇 가지 유용한 유틸리티 클래스를 제공함

 

SecurityContext를 위한 보유 전략 이용

  • SecurityContext를 관리하는 첫 번째 전략은 MODE_THREADLOCAL 전략이며 스프링 시큐리티가 SecurityContext를 관리하는 데 이용하는 기본 전략
    • 해당 전략에서 스프링 시큐리티는 ThreadLocal을 이용해 컨텍스트를 관리함
    • ThreadLocal은 JDK에 있는 구현으로 해당 구현은 데이터의 컬렉션처럼 작동하지만 애플리케이션의 각 쓰레드가 컬렉션에 저장된 데이터만 볼 수 있도록 보장함
    • 해당 방식으로 각 요청은 자신의 SecurityContext에 접근하며 쓰레드는 다른 쓰레드의 ThreadLocal에 접근할 수 없음
    • 웹 애플리케이션에서 이는 각 요청이 자신의 SecurityContexxt만 볼 수 있다는 의미이고 백엔드 웹 애플리케이션의 일반적인 작동 방식

 

  • 아래 그림처럼 비동기 메서드가 호출되어 새로운 쓰레드가 생성되면 해당 쓰레드도 자체적인 SecurityContext를 가지며, 상위 쓰레드인 요청의 원래 쓰레드의 세부 정보가 새로운 쓰레드의 SecurityContext에 복사되지 않음
  • 인증 프로세스가 끝난 후 필요할 때마다 정적 getContext() 메서드로 보유자에게 SecurityContext를 요청하기만 하면 됨

 

 

  • 스프링은 Authentication을 메서드 매개 변수에 곧바로 주입할 수 있으므로 엔드포인트 수준에서는 더 편하게 컨텍스트에서 인증을 얻을 수 있음
    • SecurityContextHolder 클래스를 매번 명시적으로 참조할 필요가 없음


 

비동기 호출을 위한 보유 전략 이용

  • MODE_THREADLOCAL은 각 쓰레드의 SecurityContext를 격리할 수 있게 해 주고 SecurityContext를 더 자연스럽고 이해하기 쉽게 만들어주지만 해당 전략이 적용되지 않은 상황도 있음
    • 요청당 여러 쓰레드가 사용될 때는 상황이 더 복잡해짐
    • 엔드포인트가 비동기가 되면 메서드를 실행하는 쓰레드와 요청을 수행하는 쓰레드가 다른 쓰레드가 됨
    • MODE_ INHERITABLETHREADLOCAL 전략을 설정하면 스프링이 SecurityContext를 올바르게 다음 쓰레드로 전파하며 Authentication도 더는 null이 아니게 됨


https://assu10.github.io/dev/2023/11/26/springsecurity-authrorization-2/

 

독립형 애플리케이션을 위한 보유 전략 이용

  • SecurityContext가 애플리케이션의 모든 쓰레드에 공유되는 전략을 원할 경우 MODE_GLOBAL을 이용하면 됨
    • 해당 전략은 일반적인 애플리케이션의 그림에는 맞지 않기 때문에 웹 서버에는 이용되지 않음
    • 백엔드 웹 애플리케이션은 수신하는 요청을 독립적으로 관리하므로 모든 요청에 대해 하나의 컨텍스트를 이용하기보다 요청 별로 보안 컨텍스트를 분리하는 것이 더 합리적이지만 독립형 애플리케이션에는 공유하는 것이 좋은 전략일 수 있음

 

https://assu10.github.io/dev/2023/11/26/springsecurity-authrorization-2/

 

  • MODE_INHERITABLETHREADLOCAL을 활성화할 때와 마찬가지로 SecurityContextHodler.setStrategyName() 메서드를 이용하거나 시스템 속성 spring.security.straategy를 설정해 전략을 변경할 수 있음
    • SecurityContext는 thread-safe하지 않음
    • 해당 전략을 설정할 경우 애플리케이션의 모든 쓰레드가 SecurityContext 객체에 접근할 수 있으므로 race condition은 개발자가 해결해야 함


 

DelegatingSecurityContextRunnable로 SecurityContet 전달

  • 기본적으로 프레임워크는 요청의 쓰레드에 SecurityContext를 제공하고 해당 쓰레드만 이 SecurityContext에 접근하도록 보장하지만 새로 생성된 쓰레드에 관해서는 별도의 작업을 하지 않으므로 SecurityContext를 관리하는 다른 모드를 명시적으로 설정해야 한다고 배웠음
    • 이러한 쓰레드는 프레임워크가 관리해주지 않아 개발자가 관리해야 하므로 종종 자체 관리 쓰레드라고 부름
    • 특정한 SecurityContextHolder 전략으로 자체 관리 쓰레드를 위한 해결책을 얻을 수 없으며 개발자가 SecurityContext 전파를 해결해야 함

 

  • 위 문제의 한 가지 해결책은 별도의 쓰레드에서 실행하고 싶은 작업을 DelegatingSecurityContextRunnable로 장식하는 것
    • DelegatingSecurityContextRunnable은 Runnable을 확장하여 반환 값이 없는 작업 실행 후 이용할 수 있음
    • 반환 값이 있는 작업에는 DelgatingSecurityContextCallable<T>에 해당하는 Callable<T>을 이용할 수 있음
    • 두 클래스 모두 다른 Runnable 또는 Callable과 마찬가지로 비동기적으로 실행되는 작업을 나타내며, 작업을 실행하는 쓰레드를 위해 현재 SecurityContext를 복사해줌


https://fizalihsan.github.io/technology/spring-security.html

 

DelegatingSecurityContextExecutorService로 SecurityContext 전달

  • 프레임워크에 알리지 않고 코드에서 시작한 쓰레드를 다룰 때는 SecurityContext에서 다음 쓰레드로의 전파를 관리해야 함
    • 스프링 시큐리티에는 DelegatingSecurityContextRunnable 및 DelegatingSecurityContextCallable과 같은 몇 가지 훌륭한 유틸리티 클래스를 통해 SecurityContext에 세부 정보를 복사하는 기법을 이용할 수 있으며 이들 클래스는 비동기적으로 실행하는 작업을 장식하고 구현이 새로 생성된 쓰레드의 SecurityContext에 접근할 수 있도록 SecurityContext에 세부 정보를 복하는 일도 함
    • 하지만 새 쓰레드로의 SeucirtyContext 전파를 해결하는 다른 옵션이 있는데, Task에서 처리하지 않고 쓰레드 풀에서 전파를 관리하는 방법도 있음

 

  • 작업을 장식하는 대신 특정 유형의 Executor를 이용할 수 있음
    • 아래 예제에서는 task가 단순한 Callable<T>로 남아 있지만, 여전히 쓰레드가 SecurityContext를 관리하는 것을 볼 수 있음
    • SecurityContext가 전파되는 이유는 DelegatingSecurityContextExecutorService 구현이 ExecutorService를 장식하기 때문


https://livebook.manning.com/book/spring-security-in-action/chapter-5/

 

  • 예약된 작업을 위해 SecurityContext 전파를 구현해야 할 경우 스프링 시큐리티에 있는 DelegatingSecurityContextScheduledExecutorService라는 데코레이터를 이용하면 됨
    • 해당 데코레이터의 작동 메커니즘은 DelegatingSecurityContextExecutorService와 유사하지만, ScheduledExecutorService를 표시하므로 예약된 작업을 대상으로 하는 점이 다름

 

  • 또한 스프링 시큐리티에는 유연성을 높이기 위한 DelegatingSecurityContextExecutor라는 더 추상적인 데코레이터 버전이 있음
    • 해당 클래스는 쓰레드 풀 계층 구조의 가장 추상적인 계약인 Executor를 직접 장식하며 애플리케이션을 디자인할 때 쓰레드 풀의 구현을 언어가 제공하는 선택 사항으로 대체할 수 있기를 원할 경우 이를 선택할 수 있음

 

클래스 설명
DelegatingSecurityContextExecutor Executor 인터페이스를 구현하여 Executor 객체를 장식하면 SecurityContext를 해당 풀에 의해 생성된 쓰레드로 전달하는 기능을 제공하도록 디자인 됨
DelegatingSecurityContextExecutorService ExecutorService 인터페이스를 구현하며 ExecutorService 객체를 장식하면 SecurityContext를 해당 풀에 의해 생성된 쓰레드로 전달하는 기능을 제공하도록 디자인 됨
DelegatingSecurityContextScheduledExecutorService ScheduledExecutorService 인터페이스를 구현하며 ScheduledExecutorService 객체를 장시갛면 SecurityContext를 해당 풀에 의해 생성된 쓰레드로 전달하는 기능을 제공하도록 디자인 됨
DelegatingSecurityContextRunnable Runnable 인터페이스를 구현하고 다른 쓰레드에서 실행되며 응답을 반환하지 않는 작업을 나타냄, 일반 Runnable의 기능에 더해 새 쓰레드에서 이용하기 위해 SecurityContext를 전파할 수 있음
DelegatingSecurityContextCallable Callable 인터페이스를 구현하고 다른 쓰레드에서 실행되며 최종적으로 응답을 반환하는 작업을 나타냄, 일반 Callable의 기능에 더해 새 쓰레드에서 이용하기 위해 SecurityContext를 전파할 수 있음

 

참고

스프링 시큐리티 인 액션

반응형

'Spring > 스프링 시큐리티 인 액션' 카테고리의 다른 글

[4장] 암호 처리  (0) 2025.05.19
[3장] 사용자 관리  (0) 2025.05.18
[2장] 스프링 시큐리티 기본 구성  (0) 2025.05.18