Spring Security Context Propagation with @Async – 使用@Async的Spring安全上下文传播

最后修改: 2016年 12月 31日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.介绍

In this tutorial, we are going to focus on the propagation of the Spring Security principal with @Async.

在本教程中,我们将重点讨论@Async传播Spring安全本体。

By default, the Spring Security Authentication is bound to a ThreadLocal – so, when the execution flow runs in a new thread with @Async, that’s not going to be an authenticated context.

默认情况下,Spring安全认证被绑定到ThreadLocal–因此,当执行流程在一个带有@Async的新线程中运行时,这不会是一个认证的上下文。

That’s not ideal – let’s fix it.

这并不理想–让我们来解决这个问题。

2. Maven Dependencies

2.Maven的依赖性

In order to use the async integration in Spring Security, we need to include the following section in the dependencies of our pom.xml:

为了在Spring Security中使用异步集成,我们需要在pom.xmldependencies中包含以下部分。

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

The latest version of Spring Security dependencies can be found here.

最新版本的 Spring Security 依赖项可以在这里找到。

3. Spring Security Propagation With @Async

3.使用@Async的Spring安全传播

Let’s first write a simple example:

让我们先写一个简单的例子。

@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();
}

We want to check if the Spring SecurityContext is propagated to the new thread. First, we log the context before the async call, next we run asynchronous method and finally we log the context again. The asyncCall() method has the following implementation:

我们想检查Spring的SecurityContext是否被传播到了新的线程。首先,我们在异步调用前记录上下文,接下来我们运行异步方法,最后我们再次记录上下文。asyncCall()方法有如下实现。

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

As we can see, it’s only one line of code that will output the context inside the new thread of asynchronous method.

我们可以看到,这只是一行代码,它将在异步方法的新线程中输出上下文。

4. The Default Configuration

4.默认配置

By default, the security context inside the @Async method will have a null value.

默认情况下, @Async方法内的安全上下文将有一个null值。

In particular, if we’ll run the async logic, we’ll be able to log the Authentication object in the main program, but when we’ll log it inside the @Async, it’s going to be null. This is an example logs output:

特别是,如果我们运行异步逻辑,我们将能够在主程序中记录Authentication对象,但当我们在@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

So, as you can see, inside the executor thread, our call fails with a NPE, as expected – because the Principal isn’t available there.

所以,正如你所看到的,在执行者线程中,我们的调用失败了,出现了NPE,正如预期的那样–因为那里的Principle是不存在的。

5. Async Security Context Configuration

5.异步安全上下文配置

If we want to have access to the principal inside the async thread, just as we have access to it outside, we’ll need to create the DelegatingSecurityContextAsyncTaskExecutor bean:

如果我们想在异步线程内部访问本金,就像在外部访问一样,我们需要创建DelegatingSecurityContextAsyncTaskExecutor Bean。

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

By doing so, Spring will use the current SecurityContext inside each @Async call.

通过这样做,Spring将在每个@Async调用中使用当前SecurityContext

Now, let’s run the application again and have a look at the logging information to make sure that’s the case:

现在,让我们再次运行应用程序,看看日志信息,以确保情况属实。

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; ...

And here we are – just as we expected, we’re seeing the same principal inside the async executor thread.

我们在这里–正如我们所期望的那样,我们在异步执行器线程中看到了同样的本金。

6. Use Cases

6.使用案例

There are a few interesting use cases where we might want to make sure the SecurityContext gets propagated like this:

有几个有趣的用例,我们可能想确保SecurityContext像这样被传播。

  • we want to make multiple external requests which can run in parallel and which may take significant time to execute
  • we have some significant processing to do locally and our external request can execute in parallel to that
  • other represent fire-and-forget scenarios, like for example sending an email

7. Conclusion

7.结论

In this quick tutorial, we presented the Spring support for sending asynchronous requests with propagated SecurityContext. From a programming model perspective, the new capabilities appear deceptively simple.

在这个快速教程中,我们介绍了Spring对发送带有传播的SecurityContext的异步请求的支持。从编程模型的角度来看,新的功能似乎很简单。

Please note, that if multiple method calls were previously chained together in a synchronous fashion, converting to an asynchronous approach may require synchronizing results.

请注意,如果以前以同步方式将多个方法调用串联起来,转换为异步方式可能需要同步结果。

This example is also available as a Maven project over on Github.

这个例子也可以作为一个Maven项目在Github上提供。