사용자의 정보가 수정이 될 때 SecurityContext의 인증 정보도 같이 업데이트해주도록 코드를 수정하고 있었다.
사용자 정보 수정 페이지에서 인증 정보를 불러와 set 해주는 부분에서는 문제가 발생하지 않았는데, 스레드를 생성해 여러 사용자의 정보를 수정하는 부분에서는 SecurityContext가 null인 것을 확인하였다.
찾아보니 Spring Security의 SecurityContextHolder의 전략(strategy)은 기본적으로 MODE_THREADLOCAL로 되어있다고 한다.
SecurityContextHolder에서 제공하는 전략은
1. MODE_THREADLOCAL: 동일한 thread 내에서만 SecurityContextHolder가 공유
2. MODE_INHERITABLETHREADLOCAL: 하위 thread 생성 시 SecurityContextHolder가 공유
3. MODE_GLOBAL: 애플리케이션 전체 범위에서 SecurityContextHolder가 공유
가 있다고 한다.
사실
SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
을 사용하여 하위 스레드에서 공유하도록 설정하면 되는 일이지만, 찾아보니 이슈가 존재하는 것 같았다.
또한, 다른 코드에서는 어떻게 사용되는지 모르기 때문에 함부로 수정할 수 없는 부분이었다.
찾아보니 Thread 생성 시 DelegatingSecurityContextRunnable을 이용하여 현재 SecurityContextHolder를 넘길 수 있는 방법이 있었다.
Runnable originalRunnable = new Runnable() {
public void run() {
// invoke secured service
}
};
SecurityContext context = SecurityContextHolder.getContext();
DelegatingSecurityContextRunnable wrappedRunnable =
new DelegatingSecurityContextRunnable(originalRunnable, context);
new Thread(wrappedRunnable).start();
위처럼 현재 스레드의 context를 가져와 DelegatingSecurityContextRunnable를 생성하여 스레드의 target으로 지정해주면 SecurityContextHolder를 쉽게 넘길 수 있다.
다음과 같이 context를 명시하지 않아도 되는 생성자도 제공하고 있다.
Runnable originalRunnable = new Runnable() {
public void run() {
// invoke secured service
}
};
DelegatingSecurityContextRunnable wrappedRunnable =
new DelegatingSecurityContextRunnable(originalRunnable);
new Thread(wrappedRunnable).start();
앞으로 생성된 스레드에서 SecurityContext를 사용할 일이 있을 때는 한 번 더 생각하도록 해야겠다.