2023. 7. 27. 11:32ㆍJAVA & Kotiln/Spring-Boot
Spring에서 제공하는 스케쥴러를 사용해
일정시간마다 DB를 조회해 데이터를 전송하는 기능을 구현하던 중
SpringSecurity에서 NullpointException 예외가 발생했다.
설정 코드는 아래와 같다.

main 메소드가 있는곳에 어노테이션을 선언 해야 기본적으로 사용이 가능하다.
또는 @Configuration 어노테이션과 함께 @EnableScheduling을 선언해서 설정 파일을 만들어
사용이 가능하다. 예시는 아래와 같다.

1. 기본적으로 SchedulingConfigurer interface를 상속받아 작성한다.
2. override한 'configureTasks' 를 통해 taskRegistrar을 전달받고
3. 여기에 설정한 스케쥴러 설정을 적용하고 (== .setTaskScheduler() )
4. 최종적으로 Bean등록되어 스케쥴러를 필요한 위치에서 사용할 수 있다.(=> @Scheduled 어노테이션 사용)
'taskScheduler()' 메소드의 경우는 개별 설정 영역이다.
나는 간단하게 threadName과 poolsize 정도를 설정하려고 했다. (singleThread로 동작해야 해서 주석처리)
return에 있는 DelegatingSecurityContextTaskScheduler의 경우
org.springframework.security.scheduling 라이브러리에 포함 된 클래스다.
SpringSecurity와 같이 사용되는 프로젝트일 경우 해당 설정을 통해
스케쥴러를 Bean으로 등록하는걸 권고 하는 것을 아래 링크에서 확인하여 사용하였다.

cron 방식을 이용해 반복 단위시간을 설정하였고
내부에는 필요한 기능을 작성하면 된다.
------------------------------------------
아래는 위와 같이 설정하기까지 chatgpt에 처음 질문을 이용한 내역이다.
질문을 수정하면서 했는데 아래처럼 세세히 하니
꽤나 괜찮은 답변이 나와 참고에 도움이되었기에
기록을 남긴다. ( 심심하면 읽어보는걸루 >< )
질문 : 자바 Springboot aplication 에서 scheduler 를 이용해서 method 를 실행할 때 SecurityContextHolder 에 Authentication 의 NullpointException이 발생하는데 이를 DelegatingSecurityContextScheduledExecutorService 를 통해서 해결할 수 있는 방법과 예시 코드를 작성 해 줘
GPT said :
Spring Security의 `SecurityContextHolder`를 사용하여 스케줄러에서 실행되는 메서드에서 `Authentication` 객체에 접근할 때, `NullPointerException`이 발생하는 이유는 스케줄러는 보통 별도의 스레드에서 실행되기 때문입니다. Spring Security는 기본적으로 스레드 로컬(ThreadLocal)을 사용하여 `SecurityContext`를 관리하는데, 스레드가 변경되면 `SecurityContext`에 대한 정보가 유지되지 않기 때문에 발생합니다.
이를 해결하기 위해 `DelegatingSecurityContextScheduledExecutorService`를 사용할 수 있습니다. 이 클래스는 Spring Security `SecurityContext`를 스케줄러가 사용하는 스레드에 전달해주는 역할을 합니다.
아래는 `DelegatingSecurityContextScheduledExecutorService`를 사용하여 스케줄러에서 메서드를 실행하는 예시 코드입니다:
1. 먼저, 스프링 부트 애플리케이션에 Spring Security가 구성되어 있어야 합니다. 이를 위해 `SecurityConfig`와 같은 설정 파일이 필요합니다.
```java
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// Spring Security 설정 내용 구성 (예: 사용자 인증, 권한 등)
}
```
2. 스케줄러에서 실행할 작업을 수행하는 서비스 클래스를 생성합니다. 이 서비스 클래스에서는 `@Async` 어노테이션을 사용하여 비동기적으로 메서드가 실행되도록 합니다. `@Async` 어노테이션을 사용하면 Spring이 내부적으로 비동기적인 방식으로 실행되며, `SecurityContext`가 유지됩니다.
```java
@Service
public class MyScheduledService {
@Async
public void myScheduledMethod() {
// 여기에 스케줄러에서 실행할 작업을 구현합니다.
// SecurityContextHolder.getContext().getAuthentication()을 사용해도 NullPointerExcetion이 발생하지 않습니다.
}
}
```
3. 스케줄러를 설정하는 클래스를 생성합니다. `DelegatingSecurityContextScheduledExecutorService`를 사용하여 스케줄러를 구성합니다.
```java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.concurrent.DefaultScheduledExecutorFactory;
import org.springframework.scheduling.concurrent.ScheduledExecutorFactoryBean;
import org.springframework.scheduling.concurrent.ScheduledExecutorTask;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.security.task.DelegatingSecurityContextScheduledExecutorService;
import java.util.concurrent.ScheduledExecutorService;
@Configuration
@EnableAsync
public class SchedulerConfig implements SchedulingConfigurer {
private final MyScheduledService myScheduledService;
@Autowired
public SchedulerConfig(MyScheduledService myScheduledService) {
this.myScheduledService = myScheduledService;
}
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(scheduledExecutorService());
}
@Bean(destroyMethod = "destroy")
public ScheduledExecutorService scheduledExecutorService() {
ScheduledExecutorFactoryBean executorFactory = new ScheduledExecutorFactoryBean();
executorFactory.setScheduledExecutorTasks(new ScheduledExecutorTask[] {});
executorFactory.setThreadNamePrefix("my-scheduler-");
executorFactory.setWaitForTasksToCompleteOnShutdown(true);
executorFactory.setAwaitTerminationSeconds(60);
executorFactory.setThreadFactory(new DefaultScheduledExecutorFactory("my-scheduler-thread-factory"));
executorFactory.afterPropertiesSet();
return new DelegatingSecurityContextScheduledExecutorService(executorFactory.getObject());
}
}
```
4. 이제 `MyScheduledService` 클래스의 `myScheduledMethod()`가 스케줄러에서 실행될 때, `SecurityContextHolder.getContext().getAuthentication()`를 사용해도 `NullPointerException`이 발생하지 않을 것입니다. `DelegatingSecurityContextScheduledExecutorService`가 `SecurityContext`를 스케줄러가 사용하는 스레드로 전달하고 유지하기 때문입니다.
위의 예시 코드를 참고하여 스케줄러에서 `SecurityContextHolder`와 `Authentication` 객체에 접근하는 문제를 해결할 수 있습니다. 비슷한 방법으로 다른 스레드에서도 `SecurityContextHolder`를 사용할 수 있습니다.