Creating, Signing, and Verifying JWT in Java
Abstract
You use JWTs don’t you? Everyone does, right? But do you know how to generate, sign, and verify them? The purpose of this post is to demonstrate how to code all these operations.
Disclaimer
This post is solely informative. Critically think before using any information presented. Learn from it but ultimately make your own decisions at your own risk.
Requirements
I did all of the work for this post using the following major technologies. You may be able to do the same thing with different technologies or versions, but no guarantees.
- Java 11
- Maven 3.8.6 (Bundled with NetBeans)
Download
Visit my GitHub page https://github.com/mjremijan to see all of my open source projects. The code for this post is located at: https://github.com/mjremijan/thoth-jwt
Introduction
A JWT is a simple three-part string of encoded characters – header, payload, signature – separated by 2 “.” characters.
xxxxx.yyyyy.zzzzz
JWT technology has been around for years. Read all about the JWT specification on the Introduction to JSON Web Tokens at https://jwt.io/introduction. This blog focuses on the Java code to create and verify JWT values. There are 2 examples:
- JWT with Symmetric HMAC SHA256 Signature
- JWT with Asymmetric RSA SHA256 Signature
Let’s take a look at them.
JWT with Symmetric HMAC SHA256 Signature
Listing 1 shows the code and Listing 2 shows example output.
Listing 1 – JWT with Symmetric HMAC SHA256 Signature
package org.thoth.jwt.main; import java.util.Base64; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; /** * * @author Michael Remijan mjremijan@yahoo.com @mjremijan . */ public class SignatureWithSymmetricalHmacSha256Main { public static void main(String[] args) throws Exception { // JWT HEADER // // This is the xxxxx of a JWT xxxxx.yyyyy.zzzzz // // Given the following JSON document, encode it // using Java as defined in the JWT specifications String header = "{\"alg\":\"HS256\",\"typ\": \"JWT\"}"; String headerEncoded = Base64.getUrlEncoder() .withoutPadding() .encodeToString( header.getBytes() ); String headerDecoded = new String( Base64.getUrlDecoder().decode(headerEncoded) ); System.out.printf("Header Plain : %s%n", header); System.out.printf("Header Encoded : %s%n", headerEncoded); System.out.printf("Header Decoded : %s%n", headerDecoded); // JWT PAYLOAD // // This is the yyyyy of a JWT xxxxx.yyyyy.zzzzz // // Given the following JSON document, encode it // using Java as defined in the JWT specifications String payload = "{\"sub\":\"TMJR00001\",\"name\":\"Michael J. Remijan\",\"exp\":61475608800,\"iss\":\"info@wstutorial.com\",\"groups\":[\"user\",\"admin\"]}"; String payloadEncoded = Base64.getUrlEncoder() .withoutPadding() .encodeToString( payload.getBytes() ); String payloadDecoded = new String( Base64.getUrlDecoder().decode(payloadEncoded) ); System.out.printf("%n"); System.out.printf("Payload Plain : %s%n", payload); System.out.printf("Payload Encoded : %s%n", payloadEncoded); System.out.printf("Payload Decoded : %s%n", payloadDecoded); // SIGNATURE / VERIFY // This is the zzzzz of a JWT xxxxx.yyyyy.zzzzz // // Hash-based message authentication code(HMAC) // is a specific type of message authentication code // (MAC) involving a cryptographic hash function and // a secret cryptographic key. As with any MAC, it // may be used to simultaneously verify both the data // integrity and authenticity of a message. // // A cryptographic hash function (CHF) is any function // that can be used to map data of arbitrary size to // a fixed-size number of n bits that has special // properties desirable for a cryptographic application. // // For this example, the process will use the SHA256 // cryptographic hash function and a secret key // to generate a signatureCreatedFromThisData (hash) of the JWT data. // This signatureCreatedFromThisData can then be used to verify the // JWT data has not been tampered. // // Typically the secret key is only available on the // Authentication Server. The key is used to create the // signatureCreatedFromThisData for the JWT. Clients will typically make // an authentication request (HTTPS) to the Authentication // server to verify a JWT. Clients cannot verify a JWT // themselves because they do not have access to the // secret key. However, if a Client is 100% trusted, // The secret key can be shared with the Client so // that the Client can do its own verification. // WARNING: This means the Client will also be able // to make new JWTs, which can be dangerous. String algorithm = "HmacSHA256"; String secret = "thisismysupersecretkeywhichshouldonlybeontheauthenticationserver"; SecretKeySpec key = new SecretKeySpec(secret.getBytes(), algorithm); Mac mac = Mac.getInstance(algorithm); mac.init(key); String signatureCreatedFromThisData = headerEncoded + "." + payloadEncoded; String signatureEncoded = Base64.getUrlEncoder() .withoutPadding() .encodeToString(mac.doFinal( signatureCreatedFromThisData.getBytes() ) ); System.out.printf("%n"); System.out.printf("Signature Algorithm : %s%n", algorithm); System.out.printf("Signature Secret : %s%n", secret); System.out.printf("Signaure Encoded :%s%n", signatureEncoded); } }
Listing 2 – HMAC Example Output
Header Plain : {"alg":"HS256","typ": "JWT"} Header Encoded : eyJhbGciOiJIUzI1NiIsInR5cCI6ICJKV1QifQ Header Decoded : {"alg":"HS256","typ": "JWT"} Payload Plain : {"sub":"TMJR00001","name":"Michael J. Remijan","exp":61475608800,"iss":"info@wstutorial.com","groups":["user","admin"]} Payload Encoded : eyJzdWIiOiJUTUpSMDAwMDEiLCJuYW1lIjoiTWljaGFlbCBKLiBSZW1pamFuIiwiZXhwIjo2MTQ3NTYwODgwMCwiaXNzIjoiaW5mb0B3c3R1dG9yaWFsLmNvbSIsImdyb3VwcyI6WyJ1c2VyIiwiYWRtaW4iXX0 Payload Decoded : {"sub":"TMJR00001","name":"Michael J. Remijan","exp":61475608800,"iss":"info@wstutorial.com","groups":["user","admin"]} Signature Algorithm : HmacSHA256 Signature Secret : thisismysupersecretkeywhichshouldonlybeontheauthenticationserver Signaure Encoded :Xi6kVafrGX18FQIkNZuVJVBbmGbmEzI8cM-5G02S32A
Line #21 of Listing 1 starts the creation of the JWT header. This is the xxxxx
part of a xxxxx.yyyyy.zzzzz
JWT. As you can see, the code is simple. Use Base64.getUrlEncoder().withoutPadding()
for encoding and Base64.getUrlDecoder()
for decoding.
NOTE:Make sure to use the
**.withoutPadding()**
encoder. If not, trailing “=” characters will be added by the encoder to make the encoded string the necessary length. These trailing “=” are not allowed by the JWT specification so if you have them, other JWT decoders won’t be able to decode your JWT properly.
Line #44 of Listing 1 starts the creation of the JWT payload, typically user information, but in theory can be anything. This is the yyyyy
part of a xxxxx.yyyyy.zzzzz
JWT. As you can see, the code is simple. Use Base64.getUrlEncoder().withoutPadding()
for encoding and Base64.getUrlDecoder()
for decoding. See NOTE above about using the .withoutPadding()
encoder.
Line #95 of Listing 1 starts the creation of the JWT signature. This is the zzzzz
part of a xxxxx.yyyyy.zzzzz
JWT. Listing 1 is an example of using the "alg":"HS256"
aka HmacSHA256
algorithm. This is a single-key, symmetric algorithm which relies on a user-generated secret
value as seen on line #96. This secret
typically is stored outside the application in some kind of configuration system (file, git, database, etc.). Staring with line #102, you see how the MAC
is used to finish the hash and the Base64.getUrlEncoder().withoutPadding()
is used to encode the hash.
You’ll notice that after signing, there is no more code in Listing 1. Where’s the code showing how to verify a JWT? Well with a single-key, symmetric algorithm like HmacSHA256
, the signing and verifying steps are exactly the same. To verify, the signature needs to be generated again and compared with the zzzzz
part of a xxxxx.yyyyy.zzzzz
JWT.
That’s it for JWT with Symmetric HMAC SHA256 Signature.
JWT with Asymmetric RSA SHA256 Signature
Listing 3 shows the code and Listing 4 shows example output.
Listing 3 – JWT with Asymmetric RSA SHA256 Signature
package org.thoth.jwt.main; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.PrivateKey; import java.security.PublicKey; import java.security.Signature; import java.util.Base64; /** * * @author Michael Remijan mjremijan@yahoo.com @mjremijan */ public class SignatureWithAsymmetricalRsaSha256Main { public static void main(String[] args) throws Exception { // JWT HEADER // // This is the xxxxx of a JWT xxxxx.yyyyy.zzzzz // // Given the following JSON document, encode it // using Java as defined in the JWT specifications String header = "{\"alg\":\"RS256\",\"typ\": \"JWT\"}"; String headerEncoded = Base64.getUrlEncoder() .withoutPadding() .encodeToString( header.getBytes() ); String headerDecoded = new String( Base64.getUrlDecoder().decode(headerEncoded) ); System.out.printf("Header Plain : %s%n", header); System.out.printf("Header Encoded : %s%n", headerEncoded); System.out.printf("Header Decoded : %s%n", headerDecoded); // JWT PAYLOAD // // This is the yyyyy of a JWT xxxxx.yyyyy.zzzzz // // Given the following JSON document, encode it // using Java as defined in the JWT specifications String payload = "{\"sub\":\"TMJR00001\",\"name\":\"Michael J. Remijan\",\"exp\":61475608800,\"iss\":\"info@wstutorial.com\",\"groups\":[\"user\",\"admin\"]}"; String payloadEncoded = Base64.getUrlEncoder() .withoutPadding() .encodeToString( payload.getBytes() ); String payloadDecoded = new String( Base64.getUrlDecoder().decode(payloadEncoded) ); System.out.printf("%n"); System.out.printf("Payload Plain : %s%n", payload); System.out.printf("Payload Encoded : %s%n", payloadEncoded); System.out.printf("Payload Decoded : %s%n", payloadDecoded); // SIGNATURE // // This is the zzzzz of a JWT xxxxx.yyyyy.zzzzz // // RSA (Rivest--Shamir--Adleman) is a public-key cryptosystem // that is widely used for secure data transmission. // In a public-key cryptosystem, the public key is used for // encryption and the private key is used for decryption. The // private key is also used for creating digital signatures // of data and the public key is used for verifying the // digital signature. // // A cryptographic hash function (CHF) is any function // that can be used to map data of arbitrary size to // a fixed-size number of n bits that has special // properties desirable for a cryptographic application. // // For this example, the process will use the SHA256 // cryptographic hash function along with a public/private // keypair and the RSA encryption algorithm to generate // a signature for the JWT. // // The private key is used for creating the signature. // KeyPairGenerator keyGenerator = KeyPairGenerator.getInstance("RSA"); keyGenerator.initialize(1024); KeyPair kp = keyGenerator.genKeyPair(); PublicKey publicKey = (PublicKey) kp.getPublic(); PrivateKey privateKey = (PrivateKey) kp.getPrivate(); String algorithm = "SHA256withRSA"; String signatureCreatedFromThisData = headerEncoded + "." + headerDecoded; Signature privateSignature = Signature.getInstance(algorithm); privateSignature.initSign(privateKey); System.out.printf("%n"); System.out.printf("Algorithm : %s%n", algorithm); System.out.printf("Public Key : %s%n", Base64.getEncoder().encodeToString(publicKey.getEncoded())); System.out.printf("Private Key : %s%n", Base64.getEncoder().encodeToString(privateKey.getEncoded())); privateSignature.update(signatureCreatedFromThisData.getBytes()); String signatureEncoded = Base64.getUrlEncoder() .withoutPadding() .encodeToString( privateSignature.sign() ); System.out.printf("%n"); System.out.printf("Signaure Encoded : %s%n", signatureEncoded); // VERIFY // This is the zzzzz of a JWT xxxxx.yyyyy.zzzzz // // The public key is used for verifying the signature. // // Becuase the public key is used for creating a signature, // it safe to distribute the public key to Clients so // that Clients can verify the JWT signature without // having to ask the Authentication Server for verification // Signature publicSignature = Signature.getInstance(algorithm); publicSignature.initVerify(publicKey); publicSignature.update(signatureCreatedFromThisData.getBytes()); boolean verified = publicSignature.verify( Base64.getUrlDecoder().decode(signatureEncoded) ); System.out.printf("Signature Verified (t/f) : %b%n", verified); } }
Listing 4 – RSA Example Output
Header Plain : {"alg":"RS256","typ": "JWT"} Header Encoded : eyJhbGciOiJSUzI1NiIsInR5cCI6ICJKV1QifQ Header Decoded : {"alg":"RS256","typ": "JWT"} Payload Plain : {"sub":"TMJR00001","name":"Michael J. Remijan","exp":61475608800,"iss":"info@wstutorial.com","groups":["user","admin"]} Payload Encoded : eyJzdWIiOiJUTUpSMDAwMDEiLCJuYW1lIjoiTWljaGFlbCBKLiBSZW1pamFuIiwiZXhwIjo2MTQ3NTYwODgwMCwiaXNzIjoiaW5mb0B3c3R1dG9yaWFsLmNvbSIsImdyb3VwcyI6WyJ1c2VyIiwiYWRtaW4iXX0 Payload Decoded : {"sub":"TMJR00001","name":"Michael J. Remijan","exp":61475608800,"iss":"info@wstutorial.com","groups":["user","admin"]} Algorithm : SHA256withRSA Public Key : MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDRxw6Ncvsx0/kDYKwA6pLwn3hSbRdYFBOv1aiBomF7lPfOPfqaTgN2yPN6hErlLAP2d+94ig4uXv7MROXlsn8n7jdr2g5yo/kC92RJwALpffzBlWh29hEadiznWp2u0b0h++Cn4HJejfJpZOek6wurBL/7K2Y2TELOg8eg1uipEwIDAQAB Private Key : MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBANHHDo1y+zHT+QNgrADqkvCfeFJtF1gUE6/VqIGiYXuU9849+ppOA3bI83qESuUsA/Z373iKDi5e/sxE5eWyfyfuN2vaDnKj+QL3ZEnAAul9/MGVaHb2ERp2LOdana7RvSH74Kfgcl6N8mlk56TrC6sEv/srZjZMQs6Dx6DW6KkTAgMBAAECgYBqVyPzZGQeADxtD+ZhmIfgXpaaAh8hURwhuIdxH6WXBg8Qh66v5fgvkPKMGt/0iHmByY6lZiaGLzWuywZXiEKYSl6tpK8WtiY+gyYxOVgFckAKzjBJ4GYb6YvPI5p5/qDFqN/9Ca4vDn9URFEIRSBIc1it8TWzze8x2Ljd4vu54QJBAOmlSJ/m4dHJMzLnyM6Y1x/e2fqm48DbfV3m+jDjyR7YrTwcVoZSC17B1z4J7W+/Ea7N61UWRRvelvC4c8OKkEkCQQDl2StC7vbKCsnDAFyDjUADq7p2aE+vVH7v7ZUjHhsTXMF8TFMgfkxl5cH58nDNq1Yo82SKGvMeRnmBYHeHlqZ7AkBP1Ur4YBJ+9QmKdkpV1UGEQUgn7ghaKGUwxbBtLhfVc2HV7TTfVn9OFFuwdgHsMdQf73peq2pXuHnIrK3ZfaoJAkEAh0nXg/NCAdRtw8C/s5L9feujujRVyt6SRMj0ApKi3ze2j2Ihf7u3XjbpgSRprzVNZpc0s3F/bm+O708HrCBJZwJBANPeVhBizgqPZOiQxLRxpNN2+EvEfs8js7YFRwB45orM/+9yVelNojEKxcHT7zS6j59dTlwvbGp6LVrKCrwtwLw= Signaure Encoded : HO4FLrLDt4ObECVWRiUGIGUimU1M70Y9aILT5op0UkV-kbEx8AqjCsLTh-Y1zOAisvFmuH5LYRw1wQyncQ5uEUWJYcoeldr-1_uFlpD2LqUy-QZfng8e6pxXOopL8Of_OcNEOqRijmI_dob8Gf0UnT7GQWpGTl32cIuuIFDeRHo Signature Verified (t/f) : true
Line #24 of Listing 3 starts the creation of the JWT header. This is the xxxxx
part of a xxxxx.yyyyy.zzzzz
JWT. As you can see, the code is simple. Use Base64.getUrlEncoder().withoutPadding()
for encoding and Base64.getUrlDecoder()
for decoding.
NOTEMake sure to use the
**.withoutPadding()**
encoder. If not, trailing “=” characters will be added by the encoder to make the encoded string the necessary length. These trailing “=” are not allowed by the JWT specification so if you have them, other JWT decoders won’t be able to decode your JWT properly.
Line #47 of Listing 3 starts the creation of the JWT payload, typically user information, but in theory can be anything. This is the yyyyy
part of a xxxxx.yyyyy.zzzzz
JWT. As you can see, the code is simple. Use Base64.getUrlEncoder().withoutPadding()
for encoding and Base64.getUrlDecoder()
for decoding. See NOTE above about using the .withoutPadding()
encoder.
Line #90 of Listing 3 starts the creation of the JWT signature. This is the zzzzz
part of a xxxxx.yyyyy.zzzzz
JWT. Listing 3 is an example of using the "alg":"RS256"
aka SHA256withRSA
algorithm. This is a two-key, asymmetric algorithm which relies on a public/private keypair created on Line #92. A Signature
object is created on line #99 and it is initialized with the private key. Staring with line #108, see how the Signature
is used to create a signature and the Base64.getUrlEncoder().withoutPadding()
is used to encode the signature.
NOTEThe public/private keypair will need to be generated outside the application and kept in some kind of configuration store (file, git, database, etc.). This is an exercise left up to you.
Line #129 of Listing 3 starts the verify process. A Client may verify a JWT it receives from an Authentication server to guard against tampering while in transit. To verify a JWT created using an asymmetric RSA SHA256 signature, the Client will need the public key. This typically is not a problem since public keys are designed to be giving away. Line #132 demonstrates the call to .verify()
.
That’s it for JWT with Asymmetric RSA SHA256 Signature.
Summary
Most of this blog is the code. Review the code, top to bottom, it is not overly complicated. But now you know how to create and verify JWT values using both a Symmetric HMAC SHA256 Signature and an Asymmetric RSA SHA256 Signature.
Enjoy!
Published on Java Code Geeks with permission by Michael Remijan, partner at our JCG program. See the original article here: Creating, Signing, and Verifying JWT in Java Opinions expressed by Java Code Geeks contributors are their own. |
Fantastic, bloody fantastic.
no fluff. No messing about. Exactly the code I need to verify JWT tokens.
Thank you so much.