GEDOPLAN
Jakarta EE (Java EE)

JSON Web Tokens – JWT – Java

Jakarta EE (Java EE)

JWT (Json Web Token) ist ein Technik zum Austausch von gesicherten Daten die Zustandslos über HTTP übertragen werden können. In dieser zweiteiligen Serie werfen wir einen kurzen Blick auf die Verwendung zur Authentifizierung zwischen einer Java EE und Angular Anwendung.

Um JWT in unserer Anwendung einzusetzen verwenden wir eine zusätzliche Bibliothek die wir mittels Maven wie gewohnt einbinden können:

        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.7.0</version>
        </dependency>

Mit Hilfe dieser Bibliothek sind wir nun in der Lage JWT Tokens zu generieren. Hier ein Beispiel zur Erzeugung dieser Tokens und die Verwendung der API:

  public String generateJWTToken(String user) {
    String token = Jwts.builder()
        .setSubject(user)
        .claim("groups", new String[] { "admin", "customer" })
        .claim("mail", "dominik.mathmann@gedoplan.de")
        .signWith(SignatureAlgorithm.HS512, System.getProperty("JWT-KEY"))
        .compact();
    return token;
  }

Der Payload des JWT-Tokens enthält so genannte “claims”, Gruppen von Informationen die wir beliebig setzen können. Es existieren einige reservierte Claims die spezifiziert sind. Einen davon sehen wir im obigen Beispiel, so ist das “Subject” ein solcher Claim der den Benutzer identifiziert. Es folgen dann einige eigens definierte Claims: die Gruppen des Benutzers (die natürlich normalerweise z.B. aus einer Datenbank kommen) und eine Mail Adresse. Anschließen “verpacken” wir diese Informationen in einen HS512 Codierten Token der mit einem Schlüssel signiert wird. Es existieren noch einige weitere Möglichkeiten, so lässt sich zum Beispiel eine Gültigkeit festlegen, nach der dieser Token seine Gültigkeit verliert.

Das Ergebnis sieht für das Beispiel dann wie folgt aus:

eyJhbGciOiJIUzUxMiJ9.
eyJzdWIiOiJkZW1vIiwiZ3JvdXBzIjpbImF
kbWluIiwiY3VzdG9tZXIiXSwibWFpbCI6ImRvbWluaWsubWF0aG1hbm5AZ2Vkb3BsYW4uZGUif.
70b3xatjS8za28ekb1eQRo-wgB2Y7mKSqXSc6_IcIOmDsmR5nJbKZXqKJeegtwzk7i0rnpvgK50dgqdWrN9H6g

Durch den “.” getrennt lässt sich hier auch der allgemeine Aufbau eines JWT-Tokens erkenne, der aus 3 Teilen besteht:

  • Header, enthält den Typ des Tokens und die Verschlüsselungs-Variante
  • Payload, enthält unsere Claims
  • Signature, zur Validierung des Tokens

Dieser Token kann nun zum Client übertragen werden und sollte bei jedem Request an den Server zurück übermittelt werden um zu prüfen ob dieser noch gültig ist und ob der identifizierte Benutzer berechtigt ist die angeforderte Resource zu erhalten. Diese Übertragung erfolgt in aller Regel im HTTP Header “Authorization” im Format:

“Bearer [Token]”

JAX-RS mit JWT

Um einen solchen JWT Token nun zur Identifizierung und Absicherung unserer Rest-Schnittstellen zu verwenden bietet es sich an einen JAX-RS Filter zu verwenden (ähnlich den Inteceptoren von CDI). Dazu implementieren wir zuerst eine Annotation die dann mit einer entsprechenden Filter-Implementierung versehen wird:

Annotation

@javax.ws.rs.NameBinding
@Retention(RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
public @interface JWTAuthed {
}

Filter-Implementierung

@Provider
@JWTAuthed
@Priority(Priorities.AUTHENTICATION)
public class JWTAuthedFilter implements ContainerRequestFilter {

  @Inject
  private JWTService jwtService;

  @Override
  public void filter(ContainerRequestContext requestContext) throws IOException {
    String token = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION);

    try {
      jwtService.valid(token.split(" ")[1]);
    } catch (Exception e) {
      requestContext
      .abortWith(Response.status(Response.Status.UNAUTHORIZED)
      .build());
    }
  }

  public void valid(String token) {
    JwtParser signed = Jwts.parser().setSigningKey(System.getProperty("JWT-KEY"));

    String username = signed.parseClaimsJws(token).getBody().getSubject();
    System.out.println("Request is JWT-signed with user: " + username);
  }
}

Nun reicht es unsere Annotation einfach an die zu sichernden Methoden zu setzen und der Container führt automatisch eine entsprechende Validierung und Identifizierung unseres Tokens durch und würde in einem Fehlerfall einen entsprechenden Fehler auswerfen.

  @GET
  @JWTAuthed
  public DemoEntity getHello() {
    ...
  }

Ich will nicht mehr, Logout

Rein technisch bleiben diese Token unendlich gültig so lange der Signing-Key nicht verändert wird ( und falls kein Zeitstempel definiert wurde ab wann der Token ungültig wird ) Ein klassischer Logout ist somit erst mal nicht möglich. Hierzu gibt es diverse Möglichkeiten dies dennoch zu implementieren. Zum Beispiel  könnte der Login/Logout Status in der Datenbank festgehalten werden und zusätzlich geprüft werden, was jedoch bei jedem Request zu einer Datenbankabfrage führen würde. Ein sehr einfacher Weg, der die Zustandslosigkeit ein wenig zunichte macht wäre das Vorhalten von gültigen Token in einer Application-Scoped Bean. Sollte die Anwendung in einer geclusterten Umgebung ausgeführt werden müsste diese Information allerdings repliziert werden, außerdem führt ein Neustart des Anwendungsservers dazu das alle Benutzer sich erneut einloggen müssen

private List<String> validJWTTokens = new ArrayList();

  public String generateJWTToken(String user) {
    String token = ...

    this.validJWTTokens.add(token);
    return token;
  }

  public void valid(String token) {
    if (!this.validJWTTokens.contains(token)) {
      throw new RuntimeException("Token is not valid anymore");
    }
     ...
  }

JWT in Java ist dank JJWT kein Hexenwerk und lässt sich relativ einfach in die eigenen Anwendung einbringen um Authentifizierung durchzuführen oder verschlüsselte Informationen aus zu tauschen.

Alles Live in Farbe auf github.com/GEDOPLAN

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Bitte füllen Sie dieses Feld aus.
Bitte füllen Sie dieses Feld aus.
Bitte gib eine gültige E-Mail-Adresse ein.
Sie müssen den Bedingungen zustimmen, um fortzufahren.

Autor

Diesen Artikel teilen

LinkedIn
Xing

Gibt es noch Fragen?

Fragen beantworten wir sehr gerne! Schreibe uns einfach per Kontaktformular.

Kurse

weitere Blogbeiträge

IT-Training - GEDOPLAN
Java SE

Is 1.0 equals 1.00?

If you use BigDecimal for floating point numbers, you may want to compare them by equals. But beware: BigDecimal.equals takes…
mask g258b04055 640
Web Security, Jakarta EE (Java EE), Spring

Jackson JSON Filter

JSON als Datenstruktur ist bei der Implementierung von Webservices kaum wegzudenken. Insbesondere Jackson als JSON Parser bietet eine ganze Reihe von Möglichkeiten, um auf die Generierung einzuwirken. Ein naheliegen Anwendungsfall hier z.B. das Ausblenden von bestimmten Attributen basierend auf Benutzer-Rollen oder Berechtigungen. Vorhang auf für: @JsonFilter

Work Life Balance. Jobs bei Gedoplan

We are looking for you!

Lust bei GEDOPLAN mitzuarbeiten? Wir suchen immer Verstärkung – egal ob Entwickler, Dozent, Trainerberater oder für unser IT-Marketing! Schau doch einfach mal auf unsere Jobseiten! Wir freuen uns auf Dich!

Work Life Balance. Jobs bei Gedoplan

We are looking for you!

Lust bei GEDOPLAN mitzuarbeiten? Wir suchen immer Verstärkung – egal ob Entwickler, Dozent, Trainerberater oder für unser IT-Marketing! Schau doch einfach mal auf unsere Jobseiten! Wir freuen uns auf Dich!