1. 소개

이 예제에서는 @Async 를 사용하여 Spring Security principal전파에 초점을 맞출 것 입니다.

기본적으로 Spring Security 인증은 ThreadLocal에 바인딩됩니다. 따라서 실행 흐름이 @Async를 사용하여 새 스레드에서 실행될 때 인증된 컨텍스트가 되지 않습니다.

이는 이상적이지 않습니다. 수정하겠습니다.

2. 메이븐 의존성

Spring Security에서 비동기 통합을 사용하려면 pom.xml의존성다음 섹션을 포함해야 합니다 .

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-config</artifactId>
    <version>5.2.3.RELEASE</version>
</dependency>

최신 버전의 Spring Security 의존성은 여기 에서 찾을 수 있습니다 .

3. @Async를 사용한 스프링 Security 전파

먼저 간단한 예를 작성해 보겠습니다.

@RequestMapping(method = RequestMethod.GET, value = "/async")
@ResponseBody
public Object standardProcessing() throws Exception {
    log.info("Outside the @Async logic - before the async call: "
      + SecurityContextHolder.getContext().getAuthentication().getPrincipal());
    
    asyncService.asyncCall();
    
    log.info("Inside the @Async logic - after the async call: "
      + SecurityContextHolder.getContext().getAuthentication().getPrincipal());
    
    return SecurityContextHolder.getContext().getAuthentication().getPrincipal();
}

Spring SecurityContext 가 새 스레드로 전파 되었는지 확인하고 싶습니다 . 먼저 비동기 호출 전에 컨텍스트를 기록하고 다음으로 비동기 메서드를 실행하고 마지막으로 컨텍스트를 다시 기록합니다. asyncCall () 메소드는 다음 구현이 :

@Async
@Override
public void asyncCall() {
    log.info("Inside the @Async logic: "
      + SecurityContextHolder.getContext().getAuthentication().getPrincipal());
}

보시다시피 비동기 메서드의 새 스레드 내부에서 컨텍스트를 출력하는 것은 단 한 줄의 코드입니다.

4. 기본 구성

기본적으로 @Async 메서드 내부의 Security 컨텍스트 null 값을 갖습니다.

특히, 비동기 로직을 ​​실행하면 메인 프로그램에서 인증 객체 를 기록할 수 있지만 @Async 내부에 기록할 때는 null 이 됩니다. 다음은 로그 출력의 예입니다.

web - 2016-12-30 22:41:58,916 [http-nio-8081-exec-3] INFO
  o.baeldung.web.service.AsyncService -
  Outside the @Async logic - before the async call:
  org.springframework.security.core.userdetails.User@76507e51:
  Username: temporary; ...

web - 2016-12-30 22:41:58,921 [http-nio-8081-exec-3] INFO
  o.baeldung.web.service.AsyncService -
  Inside the @Async logic - after the async call:
  org.springframework.security.core.userdetails.User@76507e51:
  Username: temporary; ...

  web - 2016-12-30 22:41:58,926 [SimpleAsyncTaskExecutor-1] ERROR
  o.s.a.i.SimpleAsyncUncaughtExceptionHandler -
  Unexpected error occurred invoking async method
  'public void com.baeldung.web.service.AsyncServiceImpl.asyncCall()'.
  java.lang.NullPointerException: null

따라서 보시다시피 실행자 스레드 내에서 예상대로 NPE와 함께 호출이 실패합니다. 왜냐하면 Principal을 사용할 수 없기 때문입니다.

5. 비동기 Security 컨텍스트 구성

외부에서 액세스할 수 있는 것처럼 비동기 스레드 내부의 Security 주체에 액세스하려면 DelegatingSecurityContextAsyncTaskExecutor을 생성해야 합니다 .

@Bean 
public DelegatingSecurityContextAsyncTaskExecutor taskExecutor(ThreadPoolTaskExecutor delegate) { 
    return new DelegatingSecurityContextAsyncTaskExecutor(delegate); 
}

그렇게 함으로써 Spring은 @Async 호출 에서 현재 SecurityContext 를 사용할 것 입니다.

이제 애플리케이션을 다시 실행하고 로깅 정보를 확인하여 다음과 같은지 확인합니다.

web - 2016-12-30 22:45:18,013 [http-nio-8081-exec-3] INFO
  o.baeldung.web.service.AsyncService -
  Outside the @Async logic - before the async call:
  org.springframework.security.core.userdetails.User@76507e51:
  Username: temporary; ...

web - 2016-12-30 22:45:18,018 [http-nio-8081-exec-3] INFO
  o.baeldung.web.service.AsyncService -
  Inside the @Async logic - after the async call:
  org.springframework.security.core.userdetails.User@76507e51:
  Username: temporary; ...

web - 2016-12-30 22:45:18,019 [SimpleAsyncTaskExecutor-1] INFO
  o.baeldung.web.service.AsyncService -
  Inside the @Async logic:
  org.springframework.security.core.userdetails.User@76507e51:
  Username: temporary; ...

그리고 여기에 우리가 있습니다. 예상대로 비동기 실행기 스레드 내부에서 동일한 주체를 보고 있습니다.

6. 사용 사례

SecurityContext 가 다음과 같이 전파 되도록 하려는 몇 가지 흥미로운 사용 사례가 있습니다 .

  • 병렬로 실행할 수 있고 실행하는 데 상당한 시간이 걸릴 수 있는 여러 외부 요청을 만들고 싶습니다.
  • 로컬에서 수행해야 할 중요한 처리가 있고 외부 요청을 병렬로 실행할 수 있습니다.
  • 기타는 예를 들어 이메일을 보내는 것과 같은 화재 및 잊어 버린 시나리오를 나타냅니다.

7. 결론

이 빠른 사용방법(예제)에서는 전파된 SecurityContext 를 사용하여 비동기 요청을 보내기 위한 Spring 지원을 제시했습니다 . 프로그래밍 모델의 관점에서 볼 때 새로운 기능은 믿을 수 없을 정도로 단순해 보입니다.

이전에 여러 메서드 호출이 동기 방식으로 함께 연결된 경우 비동기 방식으로 변환하려면 결과를 동기화해야 할 수 있습니다.

이 예제는 Github 에서 Maven 프로젝트로도 사용할 수 있습니다 .

Security footer banner