티스토리 뷰

1. 개요

DelegatingSecurityContextAsyncTaskExecutor는 Spring Security에서 비동기 작업(@Async) 실행 시, 현재 SecurityContext를 새로운 스레드에서도 유지할 수 있도록 도와주는 AsyncTaskExecutor 구현체입니다.

Spring Security의 SecurityContextHolder는 기본적으로 ThreadLocal을 사용하여 인증 정보를 관리합니다.
즉, 현재 실행 중인 스레드에서는 SecurityContext가 유지되지만, 새로운 스레드에서 실행될 경우 SecurityContext가 공유되지 않습니다.
이를 해결하기 위해 DelegatingSecurityContextAsyncTaskExecutor가 사용됩니다.

 

 

 

2. 왜 필요한가?

Spring에서 @Async를 사용하면 새로운 스레드에서 실행되므로 기존의 SecurityContext가 전파되지 않습니다.
즉, 인증 정보가 없는 상태에서 비동기 작업이 실행될 수 있습니다.

 

2.1. 문제 발생 예시

@Async
public void asyncMethod() {
    Authentication auth = SecurityContextHolder.getContext().getAuthentication();
    System.out.println("비동기 스레드에서의 인증 정보: " + auth);
}
  • 만약 asyncMethod()가 실행될 때 SecurityContext가 유지되지 않는다면 auth 값이 null이 될 수 있습니다.
  • 기존의 ThreadLocal 기반 SecurityContext는 비동기 스레드로 전파되지 않기 때문입니다.

 

 

 

3. DelegatingSecurityContextAsyncTaskExecutor 사용 방법

3.1. DelegatingSecurityContextAsyncTaskExecutor 적용 예시

import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.security.concurrent.DelegatingSecurityContextAsyncTaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AsyncConfig {

    @Bean
    public AsyncTaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(20);
        executor.setQueueCapacity(50);
        executor.initialize();
        
        return new DelegatingSecurityContextAsyncTaskExecutor(executor);
    }
}

 

3.2. @Async 메서드에서 사용

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.Authentication;

@Service
public class AsyncService {
    
    @Async
    public void asyncMethod() {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        System.out.println("비동기 스레드에서의 인증 정보: " + auth);
    }
}

이제 asyncMethod()가 실행될 때, 기존 SecurityContext가 유지되므로 비동기 스레드에서도 인증 정보가 유지됩니다.

 

 

 

4. 내부 동작 방식

  1. DelegatingSecurityContextAsyncTaskExecutor는 내부적으로 SecurityContext를 저장한 후, 새로운 스레드에서 실행될 때 해당 SecurityContext를 적용합니다.
  2. 기존 Executor를 감싸서 사용하며, Runnable 또는 Callable을 실행할 때 SecurityContext를 전파합니다.

 

4.1. 내부 구현 (간략화된 코드)

@Override
public void execute(Runnable task) {
    super.execute(wrap(task));
}

private Runnable wrap(Runnable task) {
    return new DelegatingSecurityContextRunnable(task, SecurityContextHolder.getContext());
}
  • wrap() 메서드는 현재 스레드의 SecurityContext를 저장하고, 새로운 스레드에서 이를 설정한 후 실행하도록 보장합니다.

 

 

 

5. DelegatingSecurityContextAsyncTaskExecutor를 사용하지 않는 경우

5.1. 직접 SecurityContext를 전파하는 방법

만약 DelegatingSecurityContextAsyncTaskExecutor를 사용하지 않는다면,
비동기 작업 실행 시 SecurityContext를 직접 전달해야 합니다.
예를 들어 Runnable 또는 Callable을 직접 감싸는 방법이 있습니다.

import org.springframework.security.concurrent.DelegatingSecurityContextRunnable;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContext;

public class AsyncExample {

    public void executeAsync(Runnable task) {
        SecurityContext context = SecurityContextHolder.getContext();
        new Thread(new DelegatingSecurityContextRunnable(task, context)).start();
    }
}
  • 하지만 이 방법은 매번 직접 SecurityContext를 전달해야 하므로 유지보수가 어렵습니다.
  • 따라서 Spring Security가 제공하는 DelegatingSecurityContextAsyncTaskExecutor를 사용하는 것이 훨씬 간편합니다.

 

 

 

6. 주요 특징 및 장점

특징 설명

SecurityContext 전파 비동기 스레드에서도 인증 정보가 유지됨
Spring @Async 지원 @Async를 사용할 때 자동으로 SecurityContext를 유지할 수 있음
기존 Executor 감싸기 기존 ThreadPoolTaskExecutor를 감싸서 사용 가능
데드락 방지 SecurityContext를 명확하게 전달하므로, 데드락이나 인증 오류를 방지

 

 

 

7. 고려해야 할 점

7.1. 모든 Executor를 DelegatingSecurityContextAsyncTaskExecutor로 변경할 경우

DelegatingSecurityContextAsyncTaskExecutor를 모든 Executor에서 사용하면 불필요한 성능 저하가 발생할 수 있습니다.
따라서 SecurityContext가 필요한 Executor에만 적용하는 것이 좋습니다.

예를 들어, 다음과 같이 특정 Executor만 감싸는 것이 좋은 방법입니다.

@Bean
public AsyncTaskExecutor securityTaskExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(10);
    executor.setMaxPoolSize(20);
    executor.setQueueCapacity(50);
    executor.initialize();
    
    return new DelegatingSecurityContextAsyncTaskExecutor(executor);
}

이제 @Qualifier("securityTaskExecutor")를 통해 필요한 곳에서만 사용할 수 있습니다.

@Autowired
@Qualifier("securityTaskExecutor")
private AsyncTaskExecutor securityExecutor;

 

 

 

8. 결론

  • Spring Security의 SecurityContext는 기본적으로 ThreadLocal을 사용하기 때문에, 비동기 스레드에서는 인증 정보가 유지되지 않습니다.
  • 이를 해결하기 위해 DelegatingSecurityContextAsyncTaskExecutor를 사용하면 자동으로 SecurityContext를 전파할 수 있습니다.
  • @Async를 사용할 때 인증 정보가 유지되지 않는 문제가 있다면, 기존 Executor를 감싸는 방식으로 적용하는 것이 가장 효율적인 해결 방법입니다.
  • 다만, 모든 Executor에서 적용하는 것은 성능 저하를 초래할 수 있으므로 필요한 곳에서만 사용하는 것이 바람직합니다.
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2025/03   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31
글 보관함