Spring Security Context Propagation mit @Async

Spring Security Context Propagation mit @Async

1. Einführung

In diesem Tutorial konzentrieren wir uns aufpropagation of the Spring Security principal with @Async. __

Standardmäßig ist die Spring Security-Authentifizierung anThreadLocal gebunden. Wenn der Ausführungsfluss in einem neuen Thread mit @Async ausgeführt wird, ist dies kein authentifizierter Kontext.

Das ist nicht ideal - lassen Sie es uns beheben.

2. Maven-Abhängigkeiten

Um die asynchrone Integration in Spring Security verwenden zu können, müssen wir den folgenden Abschnitt independencies unsererpom.xml aufnehmen:


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

Die neueste Version der Spring Security-Abhängigkeiten finden Sie inhere.

3. Spring Security Propagation mit@Async

Schreiben wir zunächst ein einfaches Beispiel:

@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. Zuerst protokollieren wir den Kontext vor dem asynchronen Aufruf, dann führen wir die asynchrone Methode aus und schließlich protokollieren wir den Kontext erneut. DieasyncCall()-Methode hat die folgende Implementierung:

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

Wie wir sehen können, gibt nur eine Codezeile den Kontext innerhalb des neuen Threads der asynchronen Methode aus.

4. Vor derSecurityContextHoldertrategie

Bevor wir die Strategie vonSecurityContextHoldereinrichten, hat der Kontext in der Methode von@Asynceinen Wert vonnull.

Insbesondere wenn wir die asynchrone Logik ausführen, können wir dasAuthentication-Objekt im Hauptprogramm protokollieren, aber wenn wir es in@Async protokollieren, wird es so sein null. Dies ist ein Beispiel für die Protokollausgabe:

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

Wie Sie sehen, schlägt unser Aufruf im Executor-Thread erwartungsgemäß mit einer NPE fehl - da der Principal dort nicht verfügbar ist.

Um dieses Verhalten zu verhindern, müssen wir die Strategie vonSecurityContextHolder.MODE_INHERITABLETHREADLOCALaktivieren:

SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);

5. Nach derSecurityContextHoldertrategie

Jetzt sollten wir Zugriff auf den Principal innerhalb des asynchronen Threads haben, genauso wie wir Zugriff auf ihn außerhalb haben.

Lassen Sie uns die Protokollierungsinformationen ausführen und überprüfen, um sicherzustellen, dass dies der Fall ist:

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

Und hier sind wir - genau wie wir erwartet haben, sehen wir dasselbe Prinzip im asynchronen Executor-Thread. **

6. Anwendungsfälle

Es gibt einige interessante Anwendungsfälle, in denen wir sicherstellen möchten, dassSecurityContext wie folgt weitergegeben werden:

  • Wir möchten mehrere externe Anforderungen erstellen, die parallel ausgeführt werden können und deren Ausführung möglicherweise viel Zeit in Anspruch nimmt

  • Wir müssen einige wichtige Vorgänge lokal ausführen, und unsere externe Anforderung kann parallel dazu ausgeführt werden

  • Andere stellen Feuer-und-Vergessen-Szenarien dar, wie zum Beispiel das Senden einer E-Mail

7. Fazit

In diesem kurzen Tutorial haben wir die Spring-Unterstützung für das Senden asynchroner Anforderungen mit propagiertenSecurityContext. vorgestellt. Aus Sicht des Programmiermodells erscheinen die neuen Funktionen täuschend einfach.

Beachten Sie, dass für die Konvertierung in einen asynchronen Ansatz möglicherweise Synchronisierungsergebnisse erforderlich sind, wenn zuvor mehrere Methodenaufrufe synchron miteinander verkettet wurden.

Dieses Beispiel ist auch als Maven-Projekt fürover on Github verfügbar.