Zum Inhalt

JWT

JSON Web Token (JWT) ist ein offener Standard (RFC 7519), der eine kompakte und in sich geschlossene Methode zur sicheren Übertragung von Informationen zwischen Parteien als JSON-Objekt definiert. Diese Informationen können überprüft und als vertrauenswürdig angesehen werden, da sie digital signiert sind. JWTs können entweder mit einem Geheimnis (unter Verwendung des HMAC-Algorithmus) oder einem öffentlichen/privaten Schlüsselpaar mithilfe von RSA oder ECDSA signiert werden.

Erste Schritte

Der erste Schritt zur Verwendung von JWT besteht darin, die Abhängigkeit in deiner Package.swift hinzuzufügen.

// swift-tools-version:5.2
import PackageDescription

let package = Package(
    name: "my-app",
    dependencies: [
        // Andere Abhängigkeiten...
        .package(url: "https://github.com/vapor/jwt.git", from: "5.0.0-beta"),
    ],
    targets: [
        .target(name: "App", dependencies: [
            // Andere Abhängigkeiten...
            .product(name: "JWT", package: "jwt")
        ]),
        // Weitere Ziele...
    ]
)

Wenn du das Manifest direkt in Xcode bearbeitest, werden die Änderungen automatisch übernommen und die neue Abhängigkeit wird beim Speichern der Datei abgerufen. Andernfalls führe swift package resolve aus, um die neue Abhängigkeit abzurufen.

Konfiguration

Das JWT-Modul fügt Application eine neue Eigenschaft jwt hinzu, die für die Konfiguration verwendet wird. Um JWTs zu signieren oder zu verifizieren, musst du einen Schlüssel hinzufügen. Der einfachste Signieralgorithmus ist HS256 oder HMAC mit SHA-256.

import JWT

// HMAC mit SHA-256 Unterzeichner hinzufügen.
await app.jwt.keys.addHMAC(key: "secret", digestAlgorithm: .sha256)

!!! tip "Hinweis": Das await Schlüsselwort ist erforderlich, da die Schlüsselsammlung ein actor ist.

Der HS256 Unterzeichner benötigt einen Schlüssel zur Initialisierung. Im Gegensatz zu anderen Unterzeichnern wird dieser eine Schlüssel sowohl zum Signieren als auch zum Verifizieren von Tokens verwendet. Erfahre mehr über die verfügbaren Algorithmen unten.

Payload

Versuchen wir, das folgende JWT-Beispiel zu überprüfen.

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ2YXBvciIsImV4cCI6NjQwOTIyMTEyMDAsImFkbWluIjp0cnVlfQ.lS5lpwfRNSZDvpGQk6x5JI1g40gkYCOWqbc3J_ghowo

Du kannst den Inhalt dieses Tokens überprüfen, indem du jwt.io besuchst und das Token in den Debugger einfügst. Setz den Schlüssel im Abschnitt "Verify Signature" auf secret.

Wir müssen eine Struktur erstellen, die mit JWTPayload konform ist und die Struktur des JWT darstellt. Wir werden die in JWT enthaltenen claims verwenden, um gängige Felder wie sub und exp zu behandeln.

// JWT payload structure.
struct TestPayload: JWTPayload {
    // Weist die längeren Swift-Eigenschaftsnamen den
    // kürzeren Schlüsseln im JWT-Payload zu.
    enum CodingKeys: String, CodingKey {
        case subject = "sub"
        case expiration = "exp"
        case isAdmin = "admin"
    }

    // Das "sub" (Subject) Claim identifiziert die Entität, die
    // Gegenstand des JWT ist.
    var subject: SubjectClaim

    // Das "exp" (Ablaufzeit) Claim identifiziert den Zeitpunkt, nach dem
    // das JWT nicht mehr akzeptiert werden darf.
    var expiration: ExpirationClaim

    // Benutzerdefinierte Daten.
    // Wenn wahr, ist der Benutzer ein Admin.
    var isAdmin: Bool

    // Führe hier zusätzliche Überprüfungslogik aus,
    // die über die Signaturüberprüfung hinausgeht.
    // Da wir ein ExpirationClaim haben, werden wir
    // die verify-Methode aufrufen.
    func verify(using algorithm: some JWTAlgorithm) async throws {
        try self.expiration.verifyNotExpired()
    }
}

Verifizierung

Nun, da wir ein JWTPayload haben, können wir das obige JWT an eine Anfrage anhängen und req.jwt verwenden, um es abzurufen und zu verifizieren. Füge deinem Projekt die folgende Route hinzu.

// JWT aus eingehender Anfrage abrufen und verifizieren.
app.get("me") { req async throws -> HTTPStatus in
    let payload = try await req.jwt.verify(as: TestPayload.self)
    print(payload)
    return .ok
}

Der Helfer req.jwt.verify prüft den Authorization Header auf ein Inhaber-Token. Wenn eines vorhanden ist, wird das JWT analysiert und seine Signatur und Claims werden überprüft. Wenn einer dieser Schritte fehlschlägt, wird ein 401 Unauthorized Fehler ausgelöst.

Teste die Route, indem du die folgende HTTP-Anfrage sendest.

GET /me HTTP/1.1
authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ2YXBvciIsImV4cCI6NjQwOTIyMTEyMDAsImFkbWluIjp0cnVlfQ.lS5lpwfRNSZDvpGQk6x5JI1g40gkYCOWqbc3J_ghowo

Wenn alles funktioniert hat, wird eine 200 OK Antwort zurückgegeben und der Payload wird ausgegeben:

TestPayload(
    subject: "vapor", 
    expiration: 4001-01-01 00:00:00 +0000, 
    isAdmin: true
)

Signierung

Dieses Paket kann auch JWTs generieren, auch bekannt als Signatur. Um dies zu demonstrieren, verwenden wir das TestPayload aus dem vorherigen Abschnitt. Füge deinem Projekt die folgende Route hinzu.

// Erstellen und Zurückgeben eines neuen JWT.
app.post("login") { req async throws -> [String: String] in
    // Erzeuge eine neue Instanz unseres JWTPayload
    let payload = TestPayload(
        subject: "vapor",
        expiration: .init(value: .distantFuture),
        isAdmin: true
    )
    // Gib das signierte JWT zurück
    return try await [
        "token": req.jwt.sign(payload, kid: "a"),
    ]
}

Die req.jwt.sign Hilfe verwendet den standardmäßig konfigurierten Signierer, um die JWTPayload zu serialisieren und zu signieren. Das kodierte JWT wird als String zurückgegeben.

Teste die Route, indem du die folgende HTTP-Anfrage sendest.

POST /login HTTP/1.1

Du solltest das neu erzeugte Token in einer 200 OK Antwort zurückerhalten.

{
   "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ2YXBvciIsImV4cCI6NjQwOTIyMTEyMDAsImFkbWluIjp0cnVlfQ.lS5lpwfRNSZDvpGQk6x5JI1g40gkYCOWqbc3J_ghowo"
}

Authentifizierung

Weitere Informationen zur Verwendung von JWT mit Vapors Authentifizierungs-API findest du unter Authentifizierung → JWT.

Algorithmen

Vapor's JWT API unterstützt die Verifizierung und Signierung von Token mit den folgenden Algorithmen.

HMAC

HMAC ist der einfachste JWT-Signieralgorithmus. Er verwendet einen einzigen Schlüssel, der sowohl signieren als auch Token verifizieren kann. Der Schlüssel kann beliebig lang sein.

  • HS256: HMAC mit SHA-256
  • HS384: HMAC mit SHA-384
  • HS512: HMAC mit SHA-512
// HMAC mit SHA-256 Unterzeichner hinzufügen.
await app.jwt.keys.addHMAC(key: "secret", digestAlgorithm: .sha256)

RSA

RSA ist der am häufigsten verwendete JWT-Signieralgorithmus. Er unterstützt unterschiedliche öffentliche und private Schlüssel. Das bedeutet, dass ein öffentlicher Schlüssel verteilt werden kann, um die Authentizität der JWTs zu überprüfen, während der private Schlüssel, der sie erzeugt, geheim gehalten wird.

Warnung

Das JWT-Paket von Vapor unterstützt keine RSA-Schlüssel mit einer Größe von weniger als 2048 Bit. Da RSA aus Sicherheitsgründen von der NIST nicht mehr empfohlen wird, sind RSA-Schlüssel hinter einem Insecure-Namensraum eingeschlossen, um von ihrer Verwendung abzuschrecken.

Um einen RSA-Signierer zu erstellen, muss zunächst ein RSAKey initialisiert werden. Dies kann durch Übergabe der Komponenten geschehen.

// Initialisiere einen privaten RSA-Schlüssel mit Komponenten.
let key = try Insecure.RSA.PrivateKey(
    modulus: modulus, 
    exponent: publicExponent, 
    privateExponent: privateExponent
)

Der Initialisierer für den öffentlichen Schlüssel ist ähnlich.

// Initialisiere einen öffentlichen RSA-Schlüssel mit Komponenten.
let key = try Insecure.RSA.PublicKey(
    modulus: modulus, 
    exponent: publicExponent
)

Du kannst auch eine PEM-Datei laden:

let rsaPublicKey = """
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC0cOtPjzABybjzm3fCg1aCYwnx
PmjXpbCkecAWLj/CcDWEcuTZkYDiSG0zgglbbbhcV0vJQDWSv60tnlA3cjSYutAv
7FPo5Cq8FkvrdDzeacwRSxYuIq1LtYnd6I30qNaNthntjvbqyMmBulJ1mzLI+Xg/
aX4rbSL49Z3dAQn8vQIDAQAB
-----END PUBLIC KEY-----
"""

// Initialisieren Sie einen RSA-Schlüssel mit public pem.
let key = try Insecure.RSA.PublicKey(pem: rsaPublicKey)

Verwende Insecure.RSA.PrivateKey, um private RSA-PEM-Schlüssel zu laden. Diese beginnen mit:

-----BEGIN RSA PRIVATE KEY-----

Sobald du den RSA-Schlüssel hast, kannst du ihn mit der Methode addRSA hinzufügen.

// RSA mit SHA-256 Signierer hinzufügen.
try await app.jwt.keys.addRSA(
    key: Insecure.RSA.PublicKey(pem: rsaPublicKey),
    digestAlgorithm: .sha256
)

PSS

Zusätzlich zum Standard-RSA unterstützt das JWT-Paket von Vapor auch RSA mit PSS-Padding. Dies wird als sicherer als Standard-RSA, wird aber immer noch zugunsten anderer asymmetrischer Algorithmen wie ECDSA abgelehnt. Während PSS lediglich ein anderes Auffüllungsschema als Standard-RSA verwendet, ist die Schlüsselerzeugung und -verwendung die gleiche wie bei RSA.

let key = Insecure.RSA.PublicKey(pem: publicKey)
try app.jwt.keys.addPSS(key: key, digestAlgorithm: .sha256)

ECDSA

ECDSA ist ein modernerer Algorithmus, der mit RSA vergleichbar ist. Er gilt bei einer bestimmten Schlüssellänge als sicherer als RSA1. Sie sollten jedoch eigene Nachforschungen anstellen, bevor Sie sich entscheiden.

Wie bei RSA kannst du ECDSA-Schlüssel über PEM-Dateien laden:

let ecdsaPublicKey = """
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE2adMrdG7aUfZH57aeKFFM01dPnkx
C18ScRb4Z6poMBgJtYlVtd9ly63URv57ZW0Ncs1LiZB7WATb3svu+1c7HQ==
-----END PUBLIC KEY-----
"""

// Initialisiere einen ECDSA-Schlüssel mit öffentlichem PEM.
let key = try ES256PublicKey(pem: ecdsaPublicKey)

Es gibt drei ECDSA-Algorithmen, je nach verwendeter Kurve: - ES256: ECDSA mit einer P-256 Kurve und SHA-256 - ES384: ECDSA mit einer P-384 Kurve und SHA-384 - ES512: ECDSA mit einer P-521 Kurve und SHA-512

Alle Algorithmen bieten sowohl einen öffentlichen Schlüssel als auch einen privaten Schlüssel, wie z. B. ES256PublicKey und ES256PrivateKey.

Du kannst auch einen zufälligen ECDSA-Schlüssel erzeugen, indem du den leeren Initialisierer verwendest. Dies ist für Tests nützlich.

let key = ES256PrivateKey()

Sobald du den ECDSAKey hast, kannst du ihn mit der Methode addECDSA zur Schlüsselsammlung hinzufügen.

// ECDSA mit SHA-256 Unterzeichner hinzufügen.
try await app.jwt.keys.addECDSA(key: ES256PublicKey(pem: ecdsaPublicKey))

Schlüsselidentifikator (kid)

Wenn du mehrere Algorithmen verwendest, kannst du Schlüsselbezeichner (kid) verwenden, um sie zu unterscheiden. Wenn du einen Algorithmus konfigurierst, übergebe den kid Parameter.

// HMAC mit SHA-256 Unterzeichner namens "a" hinzufügen.
await app.jwt.keys.addHMAC(key: "foo", digestAlgorithm: .sha256, kid: "a")
// HMAC mit SHA-256 Unterzeichner namens "b" hinzufügen.
await app.jwt.keys.addHMAC(key: "bar", digestAlgorithm: .sha256, kid: "b")

Wenn du JWTs signierst, übergebe den kid Parameter für den gewünschten Unterzeichner.

// Unterschreiben mit Unterzeichner "a"
try await req.jwt.sign(payload, kid: "a")

Dadurch wird der Name des Unterzeichners automatisch in das Feld "kid" des JWT-Headers aufgenommen. Bei der Verifizierung des JWT wird dieses Feld verwendet, um den entsprechenden Unterzeichner zu finden.

// Überprüfung mit dem im "kid"-Kopf angegebenen Unterzeichner.
// Wenn kein "kid"-Header vorhanden ist, wird der Standardunterzeichner verwendet.
let payload = try await req.jwt.verify(as: TestPayload.self)

Da JWKs bereits kid Werte enthalten, musst du diese bei der Konfiguration nicht angeben.

// JWKs enthalten bereits das "kid"-Feld.
let jwk: JWK = ...
try await app.jwt.keys.use(jwk: jwk)

Claims

Das JWT-Paket von Vapor enthält mehrere Hilfsprogramme für die Implementierung gängiger JWT-Ansprüche.

Claim Type Verify Method
aud AudienceClaim verifyIntendedAudience(includes:)
exp ExpirationClaim verifyNotExpired(currentDate:)
jti IDClaim n/a
iat IssuedAtClaim n/a
iss IssuerClaim n/a
locale LocaleClaim n/a
nbf NotBeforeClaim verifyNotBefore(currentDate:)
sub SubjectClaim n/a

Alle Ansprüche sollten mit der Methode JWTPayload.verify überprüft werden. Wenn der Anspruch eine spezielle verify-Methode hat, kannst du diese verwenden. Andernfalls greife mit value auf den Wert des Anspruchs zu und prüfen, ob er gültig ist.

JWK

Ein JSON Web Key (JWK) ist eine JavaScript Object Notation (JSON) Datenstruktur, die einen kryptografischen Schlüssel darstellt (RFC7517). Diese werden üblicherweise verwendet, um Clients mit Schlüsseln zur Verifizierung von JWTs zu versorgen.

Zum Beispiel hostet Apple sein Sign in with Apple JWKS unter der folgenden URL.

GET https://appleid.apple.com/auth/keys

Du kannst dieses JSON Web Key Set (JWKS) zu JWTKeyCollection hinzufügen. Du kannst dann JWTs von Apple an die Methode verify übergeben. Der Schlüsselbezeichner (kid) im JWT-Header wird verwendet, um automatisch den richtigen Schlüssel für die Verifizierung auszuwählen.

JWT-Herausgeber können deine JWKS rotieren, was bedeutet, dass du gelegentlich einen neuen Download durchführen musst. Siehe Vapor's unterstützte JWT Vendors Liste unten für APIs, die dies automatisch tun.

Vendors

Vapor bietet APIs für die Verarbeitung von JWTs von den unten aufgeführten populären Emittenten.

Apple

Konfiguriere zunächst die Kennung deiner Apple-Anwendung.

// Konfiguriere die Apple-App-Kennung.
app.jwt.apple.applicationIdentifier = "..."

Verwende dann die Hilfsfunktion req.jwt.apple, um ein Apple JWT abzurufen und zu überprüfen.

// Apple JWT aus dem Autorisierungs-Header abrufen und überprüfen.
app.get("apple") { req async throws -> HTTPStatus in
    let token = try await req.jwt.apple.verify()
    print(token) // AppleIdentityToken
    return .ok
}

Google

Konfiguriere zunächst die Google-Anwendungskennung und den G Suite-Domänennamen.

// Konfiguriere den Bezeichner der Google-App und den Domänennamen.
app.jwt.google.applicationIdentifier = "..."
app.jwt.google.gSuiteDomainName = "..."

Verwende dann die Hilfe req.jwt.google, um ein Google JWT zu holen und zu überprüfen.

// Google JWT aus der Autorisierungskopfzeile abrufen und überprüfen.
app.get("google") { req async throws -> HTTPStatus in
    let token = try await req.jwt.google.verify()
    print(token) // GoogleIdentityToken
    return .ok
}

Microsoft

Konfiguriere zunächst die Kennung deiner Microsoft-Anwendung.

// Konfiguriere die Kennung der Microsoft-App.
app.jwt.microsoft.applicationIdentifier = "..."

Verwende dann die Hilfsfunktion req.jwt.microsoft, um ein Microsoft JWT abzurufen und zu überprüfen.

// Microsoft JWT aus dem Autorisierungs-Header abrufen und überprüfen.
app.get("microsoft") { req async throws -> HTTPStatus in
    let token = try await req.jwt.microsoft.verify()
    print(token) // MicrosoftIdentityToken
    return .ok
}

  1. https://www.ssl.com/article/comparing-ecdsa-vs-rsa/