Propagação do contexto de segurança da primavera com @Async
1. Introdução
Neste tutorial, vamos nos concentrar nopropagation of the Spring Security principal with @Async. __
Por padrão, o Spring Security Authentication está vinculado a umThreadLocal - então, quando o fluxo de execução é executado em um novo thread com @Async, esse não será um contexto autenticado.
Isso não é o ideal - vamos consertar.
2. Dependências do Maven
Para usar a integração assíncrona no Spring Security, precisamos incluir a seguinte seção emdependencies de nossopom.xml:
org.springframework.security
spring-security-config
4.2.1.RELEASE
A versão mais recente das dependências do Spring Security pode ser encontradahere.
3. Propagação de segurança Spring com@Async
Vamos primeiro escrever um exemplo simples:
@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. Primeiro, registramos o contexto antes da chamada assíncrona, em seguida, executamos o método assíncrono e, finalmente, registramos o contexto novamente. O métodoasyncCall() tem a seguinte implementação:
@Async
@Override
public void asyncCall() {
log.info("Inside the @Async logic: "
+ SecurityContextHolder.getContext().getAuthentication().getPrincipal());
}
Como podemos ver, é apenas uma linha de código que produzirá o contexto dentro do novo thread do método assíncrono.
4. Antes da estratégiaSecurityContextHolder
Antes de configurarmos a estratégiaSecurityContextHolder, o contexto dentro do método@Async terá um valornull.
Em particular, se executarmos a lógica assíncrona, poderemos registrar o objetoAuthentication no programa principal, mas quando o registrarmos dentro de@Async, será null. Este é um exemplo de saída de logs:
web - 2016-12-30 22:41:58,916 [http-nio-8081-exec-3] INFO
o.example.web.service.AsyncService -
Outside the @Async logic - before the async call:
[email protected]:
Username: temporary; ...
web - 2016-12-30 22:41:58,921 [http-nio-8081-exec-3] INFO
o.example.web.service.AsyncService -
Inside the @Async logic - after the async call:
[email protected]:
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 org.example.web.service.AsyncServiceImpl.asyncCall()'.
java.lang.NullPointerException: null
Então, como você pode ver, dentro do thread do executor, nossa chamada falha com um NPE, como esperado - porque o Principal não está disponível lá.
Para evitar esse comportamento, precisamos habilitar a estratégiaSecurityContextHolder.MODE_INHERITABLETHREADLOCAL:
SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
5. Após a estratégiaSecurityContextHolder
Agora, devemos ter acesso ao principal dentro do encadeamento assíncrono, assim como temos acesso a ele fora.
Vamos dar uma olhada nas informações de registro para ter certeza de que é o caso:
web - 2016-12-30 22:45:18,013 [http-nio-8081-exec-3] INFO
o.example.web.service.AsyncService -
Outside the @Async logic - before the async call:
[email protected]:
Username: temporary; ...
web - 2016-12-30 22:45:18,018 [http-nio-8081-exec-3] INFO
o.example.web.service.AsyncService -
Inside the @Async logic - after the async call:
[email protected]:
Username: temporary; ...
web - 2016-12-30 22:45:18,019 [SimpleAsyncTaskExecutor-1] INFO
o.example.web.service.AsyncService -
Inside the @Async logic:
[email protected]:
Username: temporary; ...
E aqui estamos - exatamente como esperávamos, estamos vendo o mesmo principal dentro do thread do executor assíncrono. **
6. Casos de Uso
Existem alguns casos de uso interessantes em que podemos querer garantir queSecurityContext seja propagado assim:
-
queremos fazer várias solicitações externas que podem ser executadas em paralelo e que podem levar um tempo significativo para serem executadas
-
temos algum processamento significativo para fazer localmente e nossa solicitação externa pode ser executada paralelamente a essa
-
outros representam cenários de ignorar, como por exemplo enviar um email
7. Conclusão
Neste tutorial rápido, apresentamos o suporte Spring para o envio de solicitações assíncronas comSecurityContext. propagado. De uma perspectiva de modelo de programação, os novos recursos parecem aparentemente simples.
Observe que, se várias chamadas de método foram anteriormente encadeadas de maneira síncrona, a conversão para uma abordagem assíncrona pode exigir a sincronização dos resultados.
Este exemplo também está disponível como um projeto Maven emover on Github.