JWT¶
JSON Web Token (JWT) is een open standaard (RFC 7519) die een compacte en op zichzelf staande manier definieert voor het veilig verzenden van informatie tussen partijen als een JSON-object. Deze informatie kan worden geverifieerd en vertrouwd omdat ze digitaal ondertekend is. JWT's kunnen worden ondertekend met een geheim (met het HMAC-algoritme) of een openbaar/particulier sleutelpaar met RSA of ECDSA.
Getting Started¶
De eerste stap om JWT te gebruiken is het toevoegen van de afhankelijkheid aan uw Package.swift.
// swift-tools-version:5.2
import PackageDescription
let package = Package(
name: "my-app",
dependencies: [
// Andere afhankelijkheden...
.package(url: "https://github.com/vapor/jwt.git", from: "4.0.0"),
],
targets: [
.target(name: "App", dependencies: [
// Andere afhankelijkheden...
.product(name: "JWT", package: "jwt")
]),
// Andere targets...
]
)
Als u het manifest direct in Xcode bewerkt, zal het automatisch de wijzigingen oppikken en de nieuwe afhankelijkheid ophalen wanneer het bestand wordt opgeslagen. Anders, voer swift package resolve
uit om de nieuwe dependency op te halen.
Configuratie¶
De JWT module voegt een nieuwe property jwt
toe aan Application
die wordt gebruikt voor configuratie. Om JWTs te ondertekenen of verifiëren, moet je een ondertekenaar toevoegen. Het eenvoudigste onderteken algoritme is HS256
of HMAC met SHA-256.
import JWT
// Voeg HMAC toe met SHA-256 ondertekenaar.
app.jwt.signers.use(.hs256(key: "secret"))
De HS256
ondertekenaar heeft een sleutel nodig om te initialiseren. In tegenstelling tot andere ondertekenaars, wordt deze sleutel gebruikt voor zowel het ondertekenen als het verifiëren van tokens. Hieronder vindt u meer informatie over de beschikbare algoritmen.
Payload¶
Laten we proberen het volgende voorbeeld JWT te verifiëren.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ2YXBvciIsImV4cCI6NjQwOTIyMTEyMDAsImFkbWluIjp0cnVlfQ.lS5lpwfRNSZDvpGQk6x5JI1g40gkYCOWqbc3J_ghowo
U kunt de inhoud van dit token inspecteren door jwt.io te bezoeken en het token in de debugger te plakken. Zet de sleutel in de "Verify Signature" sectie op secret
.
We moeten een struct maken die voldoet aan JWTPayload
die de structuur van de JWT weergeeft. We zullen JWT's meegeleverde claims gebruiken om veel voorkomende velden zoals sub
en exp
te behandelen.
// JWT payload structuur.
struct TestPayload: JWTPayload {
// Zet de langere Swift-eigenschapnamen om in de
// verkorte sleutels die gebruikt worden in de JWT payload.
enum CodingKeys: String, CodingKey {
case subject = "sub"
case expiration = "exp"
case isAdmin = "admin"
}
// De "sub" (onderwerp) claim identificeert de principal die het
// onderwerp van de JWT is.
var subject: SubjectClaim
// De "exp" (vervaltijd) claim identificeert de vervaltijd op
// of waarna de JWT NIET voor verwerking MOET worden geaccepteerd.
var expiration: ExpirationClaim
// Aangepaste gegevens.
// Indien waar, de gebruiker is een admin.
var isAdmin: Bool
// Voer eventuele extra verificatielogica uit
// handtekening verificatie hier.
// Omdat we een ExpirationClaim hebben, zullen we
// zijn verificatiemethode aanroepen.
func verify(using signer: JWTSigner) throws {
try self.expiration.verifyNotExpired()
}
}
Verifiëren¶
Nu we een JWTPayload
hebben, kunnen we de bovenstaande JWT aan een request koppelen en req.jwt
gebruiken om het op te halen en te verifiëren. Voeg de volgende route toe aan je project.
// Haal en verifieer JWT van inkomend verzoek.
app.get("me") { req -> HTTPStatus in
let payload = try req.jwt.verify(as: TestPayload.self)
print(payload)
return .ok
}
De req.jwt.verify
helper controleert de Authorization
header voor een bearer token. Als er een bestaat, zal het de JWT parsen en de handtekening en claims verifiëren. Als een van deze stappen mislukt, zal een 401 Unauthorized foutmelding worden gegeven.
Test de route door het volgende HTTP verzoek te versturen.
GET /me HTTP/1.1
authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ2YXBvciIsImV4cCI6NjQwOTIyMTEyMDAsImFkbWluIjp0cnVlfQ.lS5lpwfRNSZDvpGQk6x5JI1g40gkYCOWqbc3J_ghowo
Als alles gelukt is, wordt een 200 OK antwoord teruggestuurd en wordt de payload afgedrukt:
TestPayload(
subject: "vapor",
expiration: 4001-01-01 00:00:00 +0000,
isAdmin: true
)
Ondertekenen¶
Dit pakket kan ook JWTs genereren, ook bekend als ondertekenen. Om dit te demonstreren, laten we de TestPayload
uit de vorige sectie gebruiken. Voeg de volgende route toe aan je project.
// Genereer en stuur een nieuwe JWT terug.
app.post("login") { req -> [String: String] in
// Maak een nieuwe instantie van onze JWTPayload
let payload = TestPayload(
subject: "vapor",
expiration: .init(value: .distantFuture),
isAdmin: true
)
// Geef de ondertekende JWT terug
return try [
"token": req.jwt.sign(payload)
]
}
De req.jwt.sign
helper zal de standaard geconfigureerde signer gebruiken om de JWTPayload
te serialiseren en te ondertekenen. De ge-encodeerde JWT wordt geretourneerd als een String
.
Test de route door het volgende HTTP verzoek te versturen.
POST /login HTTP/1.1
U zou het nieuw gegenereerde token moeten zien terugkomen in een 200 OK antwoord.
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ2YXBvciIsImV4cCI6NjQwOTIyMTEyMDAsImFkbWluIjp0cnVlfQ.lS5lpwfRNSZDvpGQk6x5JI1g40gkYCOWqbc3J_ghowo"
}
Authenticatie¶
Ga voor meer informatie over het gebruik van JWT met de authenticatie-API van Vapor naar Authenticatie → JWT.
Algoritmes¶
Vapor's JWT API ondersteunt het verifiëren en ondertekenen van tokens met de volgende algoritmen.
HMAC¶
HMAC is het eenvoudigste JWT-signaleringsalgoritme. Het gebruikt een enkele sleutel die zowel tokens kan ondertekenen als verifiëren. De sleutel kan elke lengte hebben.
hs256
: HMAC met SHA-256hs384
: HMAC met SHA-384hs512
: HMAC met SHA-512
// Voeg HMAC toe met SHA-256 signer.
app.jwt.signers.use(.hs256(key: "secret"))
RSA¶
RSA is het meest gebruikte JWT-signaleringsalgoritme. Het ondersteunt verschillende publieke en private sleutels. Dit betekent dat een publieke sleutel kan worden verspreid om te verifiëren of JWT's authentiek zijn, terwijl de private sleutel die ze genereert, geheim wordt gehouden.
Om een RSA signer te maken, moet eerst een RSAKey
geïnitialiseerd worden. Dit kan gedaan worden door de componenten in te voeren.
// Initialiseer een RSA sleutel met componenten.
let key = RSAKey(
modulus: "...",
exponent: "...",
// Alleen opgenomen in private sleutels.
privateExponent: "..."
)
U kunt er ook voor kiezen een PEM-bestand in te laden:
let rsaPublicKey = """
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC0cOtPjzABybjzm3fCg1aCYwnx
PmjXpbCkecAWLj/CcDWEcuTZkYDiSG0zgglbbbhcV0vJQDWSv60tnlA3cjSYutAv
7FPo5Cq8FkvrdDzeacwRSxYuIq1LtYnd6I30qNaNthntjvbqyMmBulJ1mzLI+Xg/
aX4rbSL49Z3dAQn8vQIDAQAB
-----END PUBLIC KEY-----
"""
// Initialiseer een RSA sleutel met publieke pem.
let key = RSAKey.public(pem: rsaPublicKey)
Gebruik .private
voor het laden van private RSA PEM sleutels. Deze beginnen met:
-----BEGIN RSA PRIVATE KEY-----
Zodra u de RSAKey hebt, kunt u die gebruiken om een RSA-signer aan te maken.
rs256
: RSA met SHA-256rs384
: RSA met SHA-384rs512
: RSA met SHA-512
// RSA toevoegen met SHA-256 ondertekenaar.
try app.jwt.signers.use(.rs256(key: .public(pem: rsaPublicKey)))
ECDSA¶
ECDSA is een moderner algoritme dat lijkt op RSA. Het wordt geacht veiliger te zijn voor een gegeven sleutellengte dan RSA1. U moet echter uw eigen onderzoek doen voordat u een beslissing neemt.
Net als RSA kunt u ECDSA-sleutels inladen met PEM-bestanden:
let ecdsaPublicKey = """
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE2adMrdG7aUfZH57aeKFFM01dPnkx
C18ScRb4Z6poMBgJtYlVtd9ly63URv57ZW0Ncs1LiZB7WATb3svu+1c7HQ==
-----END PUBLIC KEY-----
"""
// Initialiseer een ECDSA sleutel met publieke PEM.
let key = ECDSAKey.public(pem: ecdsaPublicKey)
Gebruik .private
voor het laden van private ECDSA PEM sleutels. Deze beginnen met:
-----BEGIN PRIVATE KEY-----
U kunt ook willekeurige ECDSA genereren met de generate()
methode. Dit is handig voor testen.
let key = try ECDSAKey.generate()
Als u de ECDSAKey heeft, kunt u die gebruiken om een ECDSA-signer aan te maken.
es256
: ECDSA met SHA-256es384
: ECDSA met SHA-384es512
: ECDSA met SHA-512
// ECDSA toevoegen met SHA-256 ondertekenaar.
try app.jwt.signers.use(.es256(key: .public(pem: ecdsaPublicKey)))
Key Identifier (kid)¶
Als je meerdere algoritmes gebruikt, kun je sleutel-identifiers (kid
s) gebruiken om ze te onderscheiden. Wanneer je een algoritme configureert, geef je de kid
parameter door.
// Voeg HMAC toe met SHA-256 ondertekenaar genaamd "a".
app.jwt.signers.use(.hs256(key: "foo"), kid: "a")
// Voeg HMAC toe met SHA-256 ondertekenaar genaamd "b".
app.jwt.signers.use(.hs256(key: "bar"), kid: "b")
Bij het ondertekenen van JWTs, geef de kid
parameter door voor de gewenste ondertekenaar.
// Onderteken met ondertekenaar "a"
req.jwt.sign(payload, kid: "a")
Hierdoor wordt de naam van de ondertekenaar automatisch opgenomen in het "kid"
veld van de JWT header. Bij het verifiëren van de JWT, zal dit veld gebruikt worden om de juiste ondertekenaar op te zoeken.
// Verifieer met de ondertekenaar gespecificeerd door de "kid" header.
// Als er geen "kid" header aanwezig is, zal de standaard ondertekenaar gebruikt worden.
let payload = try req.jwt.verify(as: TestPayload.self)
Omdat JWKs al kid
waarden bevatten, hoeft u deze niet te specificeren tijdens de configuratie.
// JWK's bevatten al het "kid" veld.
let jwk: JWK = ...
app.jwt.signers.use(jwk: jwk)
Claims¶
Het JWT-pakket van Vapor bevat verschillende helpers voor de implementatie van veelvoorkomende JWT-claims.
Claim | Type | Verifieer Methode |
---|---|---|
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 claims moeten geverifieerd worden in de JWTPayload.verify
methode. Als de claim een speciale verifieer
methode heeft, kun je die gebruiken. Anders kun je met value
de waarde van de claim opvragen en controleren of deze geldig is.
JWK¶
Een JSON Web Key (JWK) is een JavaScript Object Notation (JSON) datastructuur die een cryptografische sleutel voorstelt (RFC7517). Deze worden gewoonlijk gebruikt om cliënten sleutels te verstrekken voor het verifiëren van JWT's.
Bijvoorbeeld, Apple host zijn Sign in with Apple JWKS op de volgende URL.
GET https://appleid.apple.com/auth/keys
U kunt deze JSON Web Key Set (JWKS) toevoegen aan uw JWTSigners
.
import JWT
import Vapor
// Download de JWKS.
// Dit kan asynchroon gedaan worden indien nodig.
let jwksData = try Data(
contentsOf: URL(string: "https://appleid.apple.com/auth/keys")!
)
// Decodeer de gedownloade JSON.
let jwks = try JSONDecoder().decode(JWKS.self, from: jwksData)
// Creëer ondertekenaars en voeg JWKS toe.
try app.jwt.signers.use(jwks: jwks)
U kunt nu JWTs van Apple doorgeven aan de verify
methode. De key identifier (kid
) in de JWT header zal worden gebruikt om automatisch de juiste key te selecteren voor verificatie.
Op het moment van schrijven ondersteunt JWK alleen RSA-sleutels. Bovendien kunnen JWT-emittenten hun JWKS roteren, wat betekent dat u deze af en toe opnieuw moet downloaden. Zie Vapor's ondersteunde JWT Vendors lijst hieronder voor API's die dit automatisch doen.
Vendors¶
Vapor biedt API's voor het verwerken van JWT's van de populaire uitgevers hieronder.
Apple¶
Configureer eerst uw Apple applicatie-id.
// Configureer Apple app identificatie.
app.jwt.apple.applicationIdentifier = "..."
Gebruik dan de req.jwt.apple
helper om een Apple JWT op te halen en te verifiëren.
// Haal en verifieer Apple JWT van de autorisatie header.
app.get("apple") { req -> EventLoopFuture<HTTPStatus> in
req.jwt.apple.verify().map { token in
print(token) // AppleIdentityToken
return .ok
}
}
// Of
app.get("apple") { req async throws -> HTTPStatus in
let token = try await req.jwt.apple.verify()
print(token) // AppleIdentityToken
return .ok
}
Google¶
Configureer eerst uw Google applicatie-id en G Suite domeinnaam.
// Configureer Google app identifier en domeinnaam.
app.jwt.google.applicationIdentifier = "..."
app.jwt.google.gSuiteDomainName = "..."
Gebruik dan de req.jwt.google
helper om een Google JWT op te halen en te verifiëren.
// Haal en verifieer Google JWT uit de autorisatie header.
app.get("google") { req -> EventLoopFuture<HTTPStatus> in
req.jwt.google.verify().map { token in
print(token) // GoogleIdentityToken
return .ok
}
}
// of
app.get("google") { req async throws -> HTTPStatus in
let token = try await req.jwt.google.verify()
print(token) // GoogleIdentityToken
return .ok
}
Microsoft¶
Configureer eerst uw Microsoft applicatie-id.
// Configureer Microsoft app identifier.
app.jwt.microsoft.applicationIdentifier = "..."
Gebruik dan de req.jwt.microsoft
helper om een Microsoft JWT op te halen en te verifiëren.
// Haal en verifieer Microsoft JWT van de autorisatie header.
app.get("microsoft") { req -> EventLoopFuture<HTTPStatus> in
req.jwt.microsoft.verify().map { token in
print(token) // MicrosoftIdentityToken
return .ok
}
}
// Of
app.get("microsoft") { req async throws -> HTTPStatus in
let token = try await req.jwt.microsoft.verify()
print(token) // MicrosoftIdentityToken
return .ok
}
-
https://www.ssl.com/article/comparing-ecdsa-vs-rsa/ ↩