*GPT4o 가 답변한 문서입니다.

Spring WebFlux와 함께 사용하는 Spring Security, @EnableWebFluxSecurity, 그리고 ReactiveSecurityContextHolder는 리액티브(Non-blocking) 환경에서 보안을 처리하기 위한 핵심 구성 요소입니다. 이들 개념을 깊이 이해하려면, Spring MVC에서의 동작 방식과 비교하며 설명하는 것이 중요합니다.

1. Spring Security in WebFlux

Spring WebFlux에서의 Spring Security는 기존의 Spring MVC와는 달리 논블로킹(non-blocking) 방식으로 동작하는 보안 시스템입니다. WebFlux는 리액티브 스트림과 Publisher(예: Mono, Flux)를 사용하여 데이터 흐름을 처리하므로, 보안 처리 역시 비동기 방식으로 이루어집니다. 이러한 비동기 방식에 맞게 Spring Security는 리액티브 프로그래밍 모델을 기반으로 수정되었습니다.

주요 특징:

  • Non-blocking: Spring WebFlux는 리액티브 스트림을 사용하여 요청을 처리하고, Spring Security도 이에 맞추어 논블로킹 방식으로 인증 및 인가를 처리합니다.
  • Reactive Types: MonoFlux 같은 리액티브 타입을 활용해 인증 및 인가 정보를 처리합니다. 예를 들어, 인증 정보는 Mono<Authentication> 타입으로 반환됩니다.
  • WebFilter: WebFlux에서 사용하는 보안 필터는 Servlet 기반의 필터가 아닌 WebFilter입니다. Spring Security는 리액티브 요청/응답 흐름에서 이 WebFilter를 사용하여 보안 로직을 적용합니다.

2. @EnableWebFluxSecurity

@EnableWebFluxSecurity는 Spring WebFlux 환경에서 Spring Security 설정을 활성화하는 어노테이션입니다. Spring MVC에서 사용하는 @EnableWebSecurity와 비슷한 역할을 하지만, 리액티브 스트림 기반의 요청 처리를 지원하는 비동기 보안 체계를 설정하는 것이 다릅니다.

주요 역할:

  • WebFilter 등록: WebFlux 환경에서 보안 설정을 적용하기 위해 SecurityWebFilterChain을 등록합니다. 이를 통해 인증 및 인가 과정을 처리할 수 있는 필터 체인이 구성됩니다.
  • 리액티브 특성: @EnableWebFluxSecurity는 WebFlux의 리액티브 특성에 맞춰 논블로킹 방식으로 모든 보안 로직을 처리하도록 Spring Security를 설정합니다.

3. ReactiveSecurityContextHolder

ReactiveSecurityContextHolder는 Spring WebFlux에서 SecurityContext를 관리하는 리액티브 대응체입니다. 이는 기존 Spring MVC에서 사용하던 SecurityContextHolder의 리액티브 버전입니다.

  • SecurityContext 관리: Spring MVC에서는 SecurityContextHolder가 스레드 로컬(ThreadLocal)을 통해 인증 정보를 관리하지만, WebFlux에서는 논블로킹 환경에서 동작해야 하기 때문에 스레드 로컬을 사용할 수 없습니다. 대신 리액티브 체인에서 보안 컨텍스트를 전달하기 위해 ReactiveSecurityContextHolder를 사용합니다.
  • 리액티브 체인 전파: ReactiveSecurityContextHolder는 리액티브 파이프라인(즉, MonoFlux) 내에서 보안 컨텍스트를 전달합니다. 인증 정보는 Mono<SecurityContext>로 관리되며, 요청이 처리되는 동안 해당 컨텍스트를 참조할 수 있습니다.
  • Context Propagation: 리액티브 스트림은 요청과 응답 사이에 비동기적으로 전환될 수 있기 때문에, 인증 정보를 스트림에 적절히 전달해야 합니다. ReactiveSecurityContextHolder는 이를 처리하는 핵심 컴포넌트로 작동합니다.

4. Spring WebFlux Security와 기존 Spring MVC Security의 차이점

4.1 스레드 모델의 차이

  • Spring MVC: Spring MVC는 블로킹 I/O 기반의 동작 방식을 사용하며, 스레드 로컬을 사용하여 인증 정보를 관리합니다. SecurityContextHolderThreadLocal을 통해 스레드에 인증 정보를 저장하고 참조하는 방식으로 동작합니다. 요청마다 새로운 스레드가 할당되고, 해당 스레드가 완료될 때까지 블로킹되기 때문에, 인증 정보의 유지를 보장할 수 있습니다.
  • Spring WebFlux: WebFlux는 논블로킹 I/O를 사용하고, 이벤트 루프와 같은 리액티브 스레드 모델을 사용합니다. 스레드가 지속적으로 바뀌기 때문에 ThreadLocal을 사용할 수 없습니다. 대신, 보안 컨텍스트는 리액티브 스트림(Mono, Flux)을 통해 전달되어야 하며, ReactiveSecurityContextHolder는 이를 관리합니다.

4.2 필터 체인

  • Spring MVC: Spring MVC에서는 보안 필터로 FilterChainProxy가 사용되며, 요청이 들어올 때마다 동기적으로 필터 체인을 통과하여 인증 및 인가가 처리됩니다.
  • Spring WebFlux: WebFlux에서는 SecurityWebFilterChain이 사용되며, 비동기 방식으로 요청을 처리합니다. 이 필터 체인은 리액티브 방식으로 동작하며, MonoFlux 기반으로 인증 및 인가를 처리합니다.

4.3 SecurityContext의 전파

  • Spring MVC: 스레드 로컬을 통해 SecurityContextHolder에 저장된 인증 정보를 모든 계층에서 참조할 수 있습니다.
  • Spring WebFlux: SecurityContextHolder 대신 ReactiveSecurityContextHolder를 사용하여 리액티브 스트림의 컨텍스트 내에서 인증 정보를 전달합니다. 이 컨텍스트는 요청-응답 체인 전반에 걸쳐 비동기적으로 관리됩니다.

4.4 프로그램 모델의 차이

  • Spring MVC: 동기적 요청/응답 모델을 기반으로 작동합니다. 인증과 인가 과정도 이 모델에 맞추어 동기적으로 처리됩니다.
  • Spring WebFlux: 논블로킹/비동기 방식으로 동작하며, 모든 처리가 리액티브 방식으로 이루어집니다. 따라서 인증과 인가도 비동기적으로 처리되어야 하며, 리액티브 타입(Mono, Flux)을 기반으로 보안 로직을 작성해야 합니다.

5. Spring WebFlux Security의 동작 예시

WebFlux 환경에서 Spring Security가 어떻게 동작하는지 간단한 흐름을 설명하면:

  1. JWT 토큰 검증: JWT와 같은 토큰 기반 인증을 사용할 때, WebFilter에서 토큰을 추출하고 이를 검증한 후, Authentication 객체를 생성하여 ReactiveSecurityContextHolder에 저장합니다.
  2. ReactiveSecurityContextHolder 사용: 리액티브 체인을 통해 해당 요청에 대한 인증 정보를 SecurityContext에 저장하고, 이 정보는 이후 인가(Authorization) 단계에서 참조됩니다.
  3. 인가 처리: SecurityWebFilterChain에서 정의된 권한 체크(예: hasRole, hasAuthority)는 리액티브하게 인증 정보에서 필요한 권한을 확인합니다. 권한이 부여되지 않으면 적절한 에러가 반환됩니다.

MonoFluxSpring WebFlux와 같은 리액티브 프로그래밍에서 사용되는 리액티브 타입입니다. 이들은 데이터 스트림을 나타내며, 비동기적이고 논블로킹 방식으로 데이터를 처리합니다. ReactiveSecurityContextHolder는 이러한 리액티브 타입과 통합되어, SecurityContext(인증 정보 및 권한)를 리액티브 파이프라인 내에서 전파하는 역할을 합니다.

1. MonoFlux의 역할과 기능

  • Mono<T>: 0 또는 1개의 데이터를 비동기적으로 전달하는 리액티브 타입입니다. 주로 단일 값의 비동기 처리를 표현합니다.
  • Flux<T>: 0개 이상의 데이터를 비동기적으로 전달하는 스트림입니다. 주로 데이터 스트림이나 다중 값을 처리할 때 사용됩니다.

이 두 타입은 모두 Publisher의 구현체로서, 비동기 데이터 흐름을 나타냅니다. 즉, 하나 이상의 값을 비동기적으로 처리하고, 성공하거나 실패하는 시점에서 최종 상태를 나타냅니다.

2. SecurityContext의 역할과 기능

  • SecurityContext: 인증된 사용자의 Authentication 객체를 저장하여, 해당 사용자가 누구인지, 어떤 권한을 가지고 있는지 등 보안 정보를 담고 있습니다.
  • ReactiveSecurityContextHolderSecurityContext리액티브 파이프라인 내에서 관리하고 전달하는 역할을 합니다.

3. 리액티브 파이프라인에서 SecurityContext 전파

기존 Spring MVC에서는 SecurityContextThreadLocal을 통해 스레드 내에서 보안 컨텍스트를 전달했습니다. 하지만 리액티브 프로그래밍에서는 논블로킹으로 여러 스레드가 전환되기 때문에 ThreadLocal을 사용할 수 없습니다. 대신, 리액티브 타입인 MonoFlux 내에서 보안 컨텍스트를 함께 전달해야 합니다. 이를 위해 ReactiveSecurityContextHolder를 사용하여 리액티브 파이프라인 내에서 SecurityContext를 전파합니다.

4. MonoFlux에서 SecurityContext를 전달하는 방식

  • ReactiveSecurityContextHolderMonoFlux가 진행되는 동안 SecurityContext를 참조할 수 있도록 컨텍스트 전파를 돕습니다.
  • Mono 또는 Flux를 통해 데이터가 흐르는 동안, 해당 데이터를 처리하는 컨텍스트에 SecurityContext를 함께 담아 전달합니다.

전파 메커니즘

  • 리액티브 스트림을 처리하는 동안 SecurityContext리액티브 체인에 바인딩하여 필요할 때마다 참조할 수 있게 합니다.
  • 컨텍스트는 리액티브 체인 전파의 일부로 관리되며, 예를 들어 리액티브 요청이 처리되는 동안 특정 사용자에 대한 보안 컨텍스트를 참조하는 방식으로 동작합니다.
Mono.just(data)
    .contextWrite(ReactiveSecurityContextHolder.withSecurityContext(securityContextMono))
    .flatMap(data -> {
        // 여기서 Mono가 실행되는 동안 SecurityContext에 접근 가능
        return someReactiveService.process(data);
    })
    .contextWrite(ReactiveSecurityContextHolder.clearContext()); // 체인 종료 후 보안 컨텍스트 해제

5. SecurityContext와 Mono/Flux의 상호작용 예시

  1. ReactiveSecurityContextHolder.withSecurityContext():

    • 이 메서드는 리액티브 체인에 SecurityContext를 바인딩합니다. 이를 통해 리액티브 체인이 실행되는 동안 인증 정보를 계속해서 사용할 수 있습니다.
  2. 전파된 SecurityContext 사용:

    • 리액티브 체인의 중간에서 데이터가 처리될 때마다 ReactiveSecurityContextHolder.getContext()를 호출하여 현재 인증 정보를 가져올 수 있습니다.
    • 예를 들어, 데이터베이스 조회나 외부 API 호출 시 인증된 사용자의 정보를 참조할 수 있습니다.
Mono.just(data)
    .flatMap(data -> ReactiveSecurityContextHolder.getContext()
        .map(securityContext -> {
            Authentication authentication = securityContext.getAuthentication();
            // 인증 정보를 기반으로 작업 수행
            return processDataBasedOnUser(authentication.getName(), data);
        }))
    .flatMap(result -> anotherReactiveCall(result))
    .subscribe();

6. 구체적인 흐름

  • 리액티브 체인의 시작: 요청이 들어오면, 보안 컨텍스트(SecurityContext)는 사용자 인증 후 생성됩니다.
  • 체인 내 전파: 리액티브 체인(MonoFlux)이 데이터 흐름을 처리하는 동안, ReactiveSecurityContextHolder를 통해 이 SecurityContext를 지속적으로 참조할 수 있습니다.
  • 종료: 요청이 완료되면, SecurityContext는 해제되어 메모리에서 관리되지 않으며, 다른 요청이 처리될 때는 새로운 SecurityContext가 생성됩니다.

7. Mono와 Flux에서 SecurityContext가 필요한 이유

리액티브 애플리케이션은 비동기적으로 동작하기 때문에, 인증 및 인가를 위한 SecurityContext 정보가 각 요청 흐름 내에서 전파되어야 합니다. 이를 통해:

  • 리액티브 스트림 내에서 보안 로직을 안전하게 처리할 수 있습니다.
  • 데이터 흐름이 진행되는 동안 인증된 사용자의 정보를 유지하면서 다양한 서비스 호출을 할 수 있습니다.

결론

MonoFlux는 리액티브 프로그래밍에서 비동기적인 데이터 스트림을 처리하는 핵심 타입으로, SecurityContext는 이러한 리액티브 체인 내에서 인증 정보 및 권한 정보를 전달하기 위한 필수 요소입니다. ReactiveSecurityContextHolder는 이를 관리하는 컴포넌트로, MonoFlux의 실행 흐름에서 보안 컨텍스트를 전달하고 필요할 때마다 참조할 수 있도록 보장하는 역할을 합니다.