Übertragung von Authentisierungsinformationen im JWT

Gültig ab jadice web toolkit 5.10.0.10

Dieser Artikel beschreibt, wie im jadice web toolkit Authentisierungsinformationen, z.B. in Form von Tokens, übertragen werden können.




Anforderung

In Enterprise-Umgebungen kommt der Authentisierung von Benutzern beim Dokumentenzugriff eine zentrale Bedeutung bei. Moderne Lösungen basieren häufig auf der Erzeugung bzw. Validierung von (in der Regel kurzlebigen) Tokens, siehe z.B. JSON Web Tokens bzw. OAuth.

Aufgrund der in der Regel kurzen Lebensdauer der Tokens müssen diese bei jedem Request vom jadice web toolkit Client an den Server übermittelt werden. Serverseitig werden die Tokens entweder direkt validiert oder an einen Backend-Service weitergeleitet, so dass dieser eine Validierung durchführen kann.

Wichtig ist, dass dem jadice web toolkit Server jederzeit ein gültiges Token zur Verfügung steht, damit auch im Falle eines Dokument-Recovery-Vorgangs ein neuerliches Abfragen des Dokuments am Archiv authentisiert ist. Wir benötigen also eine Möglichkeit, Tokens wiederholt vom Client an den Server zu übertragen. 

Übertragung von Tokens vom Client an den Server

Es wird vorausgesetzt, dass clientseitig im Integrations-Code bereits ein Token am Identity Provider angefordert wurde.

Dieses Token wird dem jadice web toolkit gesetzt und daraufhin im Hintergrund an den jadice web toolkit Server übertragen - und zwar vollkommen unabhängig vom eingestellten Transportverfahren  (also egal ob Longpoll, Websocket oder Server Sent Events). Da das jadice web toolkit verschiedene Transportverfahren unterstützt, funktioniert natürlich eine Übertragung im HTTP Header nicht, wie sie z.B. für JSON Web Tokens standardisiert ist.

Im jadice web toolkit Server steht nun jeweils das aktuelle Token zur jeweiligen Client-ID zur Verfügung.

Vorteile

  • Funktionalität unabhängig vom genutzten Protokoll (siehe JWT-3197)
  • Einfache Einbindung der Funktionalität → siehe Showcase
  • Erkennen und Behandeln von Failover-Fällen: Die Lösung bietet ebenfalls eine Absicherung in Failover-Fällen und sendet in solchen das Token erneut an den Server.

Die API im Detail

Clientseitig ist das aktuelle Token über die ServerConnection zu setzen - und zwar immer dann, sobald ein neues Token vorliegt:

Client: Token setzen
 ServerConnection.get().setAuthenticationInfo(token);

Serverseitig wird der DocumentDataProvider über eine Factory erzeugt, die das jeweils aktuelle Token aus dem AuthenticationManager ausliest und dem DocumentDataProvider übergibt. Dieser kann mit Hilfe des Tokens selbst eine Validierung vornehmen oder das Token zur Authentisierung an den Backend-Service weiterleiten.

Server: Token auslesen und übergeben
public class DataProviderFactoryWithAuthentication implements ContextualFactory<DataProviderWithAuthentication> {
 
  @Override
  public DataProviderWithAuthentication create(InvocationContext context) {
    String authenticationInfo = AuthenticationInfoManager.getInstance().getAuthenticationInfo(context.getClientId());
    return new DataProviderWithAuthentication(authenticationInfo);
  }
}

Gleiches gilt für ServerOperations, die eine Authentisierung erfordern (z.B. zum Speichern von Annotationen). Auch hier kann über eine ContextualFactory das jeweils aktuelle Token ausgelesen und in die ServerOperation übergeben werden.

Variante: Session-Cookies kombiniert mit JSON Web Tokens

Neben der direkten Übergabe von Tokens werden auch kombinierte Varianten vom jadice web toolkit unterstützt - beispielsweise im Zusammenspiel mit WebSeal als Security Proxy. Dies setzt allerdings eine HTTP-basierte Kommunikation voraus, funktioniert also nur mit Longpoll oder Server Sent Events als Transportprotokoll im jadice web toolkit!

Das Szenario sieht hierbei wie folgt aus:

  • Requests der Business-Anwendung werden über WebSeal geleitet
    • WebSeal stellt ein JSON Web Token aus und reicht es nach hinten an den Server mit der Business Application weiter
    • Der Business-Client bekommt nun ein Session-Cookie ausgestellt. (Der Business-Client ist hierbei ebenfalls eine Web-Applikation, z.B. ein Fenster mit IFrame, in dem das jadice web toolkit gestartet wird.)
  • Die URL zum Aufruf des jadice web toolkit wird so umgebogen, dass WebSeal angesprochen wird.
    • WebSeal identifiziert den Call über das Session-Cookie, das auch hier vom Browser mitgeschickt wird. 
    • WebSeal reicht das zugehörige JSON Web Token im Html Header mit nach hinten zum jadice web toolkit Server 
  • Der jadice web toolkit server reicht nun das Token an den Archiv-Service weiter, der dieses wiederum zur Authentisierung + Autorisierung nutzt

Im jadice web toolkit wird dies folgendermaßen umgesetzt:

  1. Einen javax.servlet.Filter implementieren, der das JWT der HttpSession als Attribut setzt.
  2. Sowohl den DocumentDataProvider als auch die ServerOperation über eine com.levigo.jadice.web.server.ContextualFactory erzeugen, damit dort aus der HttpSession das JSON Web Token extrahiert und an den Archiv-Service weitergereicht werden kann.

zu 1. Der Filter holt aus dem HttpServletRequest das JSON Web Token und setzt es als Attribut der HttpSession. Das Token wird hierbei durch WebSeal im Http-Header übergeben:

CookieFilter.java
@Override
public void doFilter(...) {
    HttpServletRequest r = (HttpServletRequest) request;
    String authToken = r.getHeader("Authorization");
    r.getSession().setAttribute(JWT_KEY, authToken);
}

zu 2. Beim Erzeugen von DocumentDataProvider bzw. ServerOperation holen wir über eine ContextualFactory die HttpSession und füttern die Instanzen damit. Beispiel:

MyDataProviderFactory.java
import javax.servlet.http.HttpSession;

import com.levigo.jadice.web.server.ContextualFactory;
import com.levigo.jadice.web.server.DocumentDataProvider;
import com.levigo.jadice.web.server.InvocationContext;
import com.levigo.jadice.web.server.ServletInvocationContext;

@Component
public class MyDataProviderFactory implements ContextualFactory<MyDataProviderSource, MyDataProviderHandle> {

	 @Override
	  public MyDataProvider<MyDataProviderSource, MyDataProviderHandle> create(InvocationContext context) {	 
		 ServletInvocationContext servletInvocationContext = (ServletInvocationContext) context;
		 HttpSession session = servletInvocationContext.getSession();
		 return new MyDataProvider(session);
	  }
}

Ein grundsätzliches Beispiel zur Implementierung findet sich im Showcase zur HttpSessionServerOperationFactory. Die Registrierung der ServerOperation-Factory ist im Referenzhandbuch beschrieben.