JWT provides a very interesting way to represent claims between applications that can be verified and trusted. My objective here is to show a small sample to generate and validate a token using the excellent
Nimbus JOSE + JWT library.
Overview
One of the best places to get an intro is
here. In brief, to borrow from the material from the jwt.io site, claims are represented as an encoded json in three parts separated with a dot (.)
header.payload.signature
The
header is a json that contains the type of algorithm used for signing the content(RSA in this instance) which is then url and Base64 encoded:
{
"alg": "RS512"
}
The
payload is a json containing all the claims, there are claims which are reserved but private claims are also allowed:
{
"sub": "samplesubject",
"name": "John Doe",
"iss": "sampleissueer",
"admin": true,
"exp": 1451849539
}
here "sub"(subject), "iss"(issuer) and "exp"(expiry) are reserved claims but "name" and "admin" are private claims. The content is then Base64Url encoded.
Finally the header and payload together is signed using either a shared key or a private key and the signature is Base64 url encoded and appended to the token with a (.) separator.
Generating a Keypair
My sample is RSA based one, so the first step is to generate a Key pair.
JWK is a neat way to store the Keys as a JSON representation and Nimbus library provides support for that:
import java.security.KeyPairGenerator
import java.security.interfaces.{RSAPrivateKey, RSAPublicKey}
import com.google.gson.{GsonBuilder, JsonElement, JsonParser}
import com.nimbusds.jose.Algorithm
import com.nimbusds.jose.jwk.{JWKSet, KeyUse, RSAKey}
object JWKGenerator {
def make(keySize: Integer, keyUse: KeyUse, keyAlg: Algorithm, keyId: String) = {
val generator = KeyPairGenerator.getInstance("RSA")
generator.initialize(keySize)
val kp = generator.generateKeyPair()
val publicKey = kp.getPublic().asInstanceOf[RSAPublicKey]
val privateKey = kp.getPrivate().asInstanceOf[RSAPrivateKey]
new RSAKey.Builder(publicKey)
.privateKey(privateKey)
.keyUse(keyUse)
.algorithm(keyAlg)
.keyID(keyId)
.build()
}
...
}
Given this Key Pair, a JWK can be generated from this using
Gson:
def generateJWKKeypair(rsaKey: RSAKey): JsonElement = {
val jwkSet = new JWKSet(rsaKey)
new JsonParser().parse(jwkSet.toJSONObject(false).toJSONString)
}
def generateJWKJson(rsaKey: RSAKey): String = {
val jsonElement = generateJWKKeypair(rsaKey)
val gson = new GsonBuilder().setPrettyPrinting().create()
gson.toJson(jsonElement)
}
A sample JWK based keypair looks like this:
{
"keys": [
{
"p": "2_Fb6K50ayAsnnQl55pPegE_JNTeAjpDo9HThZPp6daX7Cm2s2fShtWuM8JBv42qelKIrypAAVOedLCM75VoRQ",
"kty": "RSA",
"q": "ye5BeGtkx_9z3V4ImX2Pfljhye7QT2rMhO8chMcCGI4JGMsaDBGUmGz56MHvWIlcqBcYbPXIWORidtMPdzp1wQ",
"d": "gSjAIty6uDAm8ZjEHUU4wsJ8VVSJInk9iR2BSKVAAxJUQUrCVN---DKLr7tCKgWH0zlV0DjGtrfy7pO-5tcurKkK59489mOD4-1kYvnqSZmHC_zF9IrCyZWpOiHnI5VnJEeNwRz7EU8y47NjpUHWIaLl_Qsu6gOiku41Vpb14QE",
"e": "AQAB",
"use": "sig",
"kid": "sample",
"qi": "0bbcYShpGL4XNhBVrMI8fKUpUw1bWghgoyp4XeZe-EZ-wsc43REE6ZItCe1B3u14RKU2J2G57Mi9f_gGIP_FqQ",
"dp": "O_qF5d4tQUl04YErFQ2vvsW4QoMKR_E7oOEHndXIZExxAaYefK5DayG6b8L5yxMG-nSncZ1D9ximjYvX4z4LQQ",
"alg": "RS512",
"dq": "jCy-eg9i-IrWLZc3NQW6dKTSqFEFffvPWYB7NZjIVa9TlUh4HmSd2Gnd2bu2oKlKDs1pgUnk-AAicgX1uHh2gQ",
"n": "rX0zzOEJOTtv7h39VbRBoLPQ4dRutCiRn5wnd73Z1gF_QBXYkrafKIIvSUcJbMLAozRn6suVXCd8cVivYoq5hkAmcRiy0v7C4VuB1_Fou7HHoi2ISbwlv-kiZwTmXCn9YSHDBVivCwfMI87L2143ZfYUcNxNTxPt9nY6HJrtJQU"
}
]
}
Generating a JWT
Now that we have a good sample keypair, load up the private and public keys:
import java.time.{LocalDateTime, ZoneOffset}
import java.util.Date
import com.nimbusds.jose._
import com.nimbusds.jose.crypto._
import com.nimbusds.jose.jwk.{JWKSet, RSAKey}
import com.nimbusds.jwt.JWTClaimsSet.Builder
import com.nimbusds.jwt._
object JwtSample {
def main(args: Array[String]): Unit = {
val jwkSet = JWKSet.load(JwtSample.getClass.getResource("/sample.json").toURI.toURL)
val jwk = jwkSet.getKeyByKeyId("sample").asInstanceOf[RSAKey]
val publicKey = jwk.toRSAPublicKey
val privateKey = jwk.toRSAPrivateKey
...
}
Build a payload, sign it and generate the JWT:
val claimsSetBuilder = new Builder()
.subject("samplesubject")
.claim("name", "John Doe")
.claim("admin", true)
.issuer("sampleissueer")
.expirationTime(Date.from(LocalDateTime.now().plusHours(1).toInstant(ZoneOffset.UTC)))
val signer = new RSASSASigner(privateKey)
val signedJWT: SignedJWT = new SignedJWT(
new JWSHeader(JWSAlgorithm.RS512),
claimsSetBuilder.build())
signedJWT.sign(signer)
val s = signedJWT.serialize()
The consumer of this JWT can read the payload and validate it using the public key:
val cSignedJWT = SignedJWT.parse(s)
val verifier = new RSASSAVerifier(publicKey)
println(cSignedJWT.verify(verifier))
println(signedJWT.getJWTClaimsSet().getSubject())
Conclusion
This sample is entirely based on samples provided at the
Nimbus JOSE + JWT site, you should definitely refer to the Nimbus site if you are interested in exploring this further. My samples are
here