Spring Boot에서 성능 최적화를 위해 @Async를 사용하여 비동기 처리를 도입했습니다. 그런데 평소엔 잘 작동하던 SecurityContextHolder.getContext().getAuthentication()이 @Async가 붙은 메서드 안에서만 실행하면 null을 반환하는 문제가 생겼습니다.
이유는 Spring Security의 기본 동작 방식인 ThreadLocal 때문입니다.
@Async를 사용하면 Spring은 기존 스레드가 아닌 새로운 작업 스레드(Task Thread)를 할당합니다.단순히 ThreadPoolTaskExecutor를 설정하는 것만으로는 부족합니다. Spring Security가 제공하는 DelegatingSecurityContextAsyncTaskExecutor로 실행기를 감싸주어야 합니다.
이 방식은 새로운 스레드를 생성할 때, 현재 부모 스레드의 SecurityContext를 복사해서 넘겨주는 역할을 합니다.
특정 도메인에 종속되지 않도록 공통 설정(Common Config) 영역에 작성하는 것이 좋습니다.
@Configuration
@EnableAsync
public class AsyncConfig extends AsyncConfigurerSupport {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(500);
executor.setThreadNamePrefix("Async-Exp-");
executor.initialize();
// 핵심: 비동기 스레드 생성 시 SecurityContext를 전파하도록 위임
return new DelegatingSecurityContextAsyncTaskExecutor(executor);
}
}
만약 멀티 모듈 프로젝트를 운영 중이라면, 이 AsyncConfig가 특정 모듈(예: Batch 전용 모듈)에만 위치해 있지 않은지 확인해야 합니다.
API 모듈이나 다른 서비스 모듈에서도 비동기 보안 정보가 필요하다면, 모든 모듈이 참조할 수 있는 Common 모듈이나 Shared Library 계층으로 설정을 옮겨야 전역적으로 이 혜택을 볼 수 있습니다.
비동기 처리는 성능상 이점이 크지만, 스레드 간 데이터 공유라는 복병이 늘 숨어있습니다. Spring Security를 사용하는 환경에서 @Async를 쓴다면, 반드시 SecurityContext 전파 설정을 확인해야 합니다