Propagation du contexte de sécurité de printemps avec @Async

Propagation du contexte de sécurité Spring avec @Async

1. introduction

Dans ce tutoriel, nous allons nous concentrer sur lespropagation of the Spring Security principal with @Async. __

Par défaut, l'authentification de sécurité Spring est liée à unThreadLocal - donc, lorsque le flux d'exécution s'exécute dans un nouveau thread avec @Async, ce ne sera pas un contexte authentifié.

Ce n’est pas idéal - réglons le problème.

2. Dépendances Maven

Afin d'utiliser l'intégration asynchrone dans Spring Security, nous devons inclure la section suivante dans lesdependencies de nospom.xml:


    org.springframework.security
    spring-security-config
    4.2.1.RELEASE

La dernière version des dépendances de Spring Security peut être trouvéehere.

3. Propagation de la sécurité Spring avec@Async

Écrivons d'abord un exemple simple:

@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. Tout d'abord, nous enregistrons le contexte avant l'appel asynchrone, ensuite nous exécutons la méthode asynchrone et enfin nous enregistrons à nouveau le contexte. La méthodeasyncCall() a l'implémentation suivante:

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

Comme nous pouvons le voir, ce n’est qu’une seule ligne de code qui affichera le contexte dans le nouveau thread de la méthode asynchrone.

4. Avant la stratégieSecurityContextHolder

Avant de configurer la stratégieSecurityContextHolder, le contexte de la méthode@Async aura une valeurnull.

En particulier, si nous exécutons la logique asynchrone, nous pourrons enregistrer l'objetAuthentication dans le programme principal, mais lorsque nous l'enregistrerons dans les@Async, il sera null. Voici un exemple de sortie de journaux:

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

Ainsi, comme vous pouvez le voir, à l'intérieur du thread de l'exécuteur, notre appel échoue avec un NPE, comme prévu - car le Principal n'y est pas disponible.

Pour éviter ce comportement, nous devons activer la stratégieSecurityContextHolder.MODE_INHERITABLETHREADLOCAL:

SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);

5. Après la stratégieSecurityContextHolder

Nous devrions maintenant avoir accès au principal dans le fil asynchrone, tout comme nous en avons accès à l'extérieur.

Examinons les informations de journalisation pour nous assurer que c'est le cas:

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

Et nous y voilà - comme nous nous y attendions, nous voyons le même principal dans le thread de l'exécuteur asynchrone. **

6. Cas d'utilisation

Il y a quelques cas d'utilisation intéressants où nous pourrions vouloir nous assurer que lesSecurityContext sont propagés comme ceci:

  • nous voulons faire plusieurs requêtes externes pouvant s'exécuter en parallèle et dont l'exécution peut prendre beaucoup de temps

  • nous avons un traitement important à faire localement et notre requête externe peut être exécutée parallèlement à celle

  • d'autres représentent des scénarios incendiaires, comme par exemple l'envoi d'un courrier électronique

7. Conclusion

Dans ce rapide tutoriel, nous avons présenté la prise en charge de Spring pour l'envoi de requêtes asynchrones avec desSecurityContext. propagés Du point de vue du modèle de programmation, les nouvelles fonctionnalités semblent d'une simplicité trompeuse.

Veuillez noter que si plusieurs appels de méthode étaient auparavant chaînés de manière synchrone, la conversion vers une approche asynchrone peut nécessiter la synchronisation des résultats.

Cet exemple est également disponible en tant que projet Maven surover on Github.