Trust in Transit: Implementing JSON Web Signatures (JWS) in Java

Article

In the world of distributed systems, microservices, and API-driven architectures, establishing trust between parties is paramount. When a service receives a token or a message, how can it be certain that the content hasn't been tampered with and that it truly originated from a legitimate source? JSON Web Signatures (JWS) provide a standardized, compact, and secure mechanism for signing arbitrary content, ensuring both authenticity and integrity. For Java developers, mastering JWS is essential for building secure authentication systems, API security layers, and trusted data exchange.

What is JSON Web Signature (JWS)?

JWS, defined in RFC 7515, is a means of representing content secured with digital signatures or Message Authentication Codes (MACs) using JSON-based data structures. A JWS consists of:

  1. Header: Contains metadata about the signing algorithm and key
  2. Payload: The content being signed (can be any data, often a JWT claims set)
  3. Signature: The cryptographic signature covering the header and payload

JWS can be used in two serialization formats:

  • Compact Serialization: A compact, URL-safe string format (most common)
  • JSON Serialization: A JSON object format for multiple signatures

Why JWS Matters for Java Applications

  1. Authentication: Verify the origin of tokens and messages
  2. Integrity: Detect tampering of data in transit
  3. Non-Repudiation: With digital signatures, signers cannot deny signing
  4. Standardization: Works across different platforms and languages
  5. JWT Foundation: JSON Web Tokens are built on JWS

JWS Structure (Compact Serialization)

[Header].[Payload].[Signature]
Example:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Complete JWS Implementation in Java

1. Dependencies and Setup

<dependencies>
<!-- Nimbus JOSE + JWT (excellent JOSE library) -->
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
<version>9.37.3</version>
</dependency>
<!-- Bouncy Castle for additional algorithms -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
<version>1.77</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk18on</artifactId>
<version>1.77</version>
</dependency>
<!-- For JSON processing -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version>
</dependency>
<!-- Spring Security integration (optional) -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-jose</artifactId>
<version>6.1.5</version>
</dependency>
</dependencies>

2. Basic JWS with HMAC (Symmetric Signing)

import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.MACSigner;
import com.nimbusds.jose.crypto.MACVerifier;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import java.security.SecureRandom;
import java.util.Base64;
import java.util.Date;
import java.util.UUID;
/**
* Basic JWS with HMAC (symmetric signing)
*/
public class JWSHMACExample {
private static final SecureRandom SECURE_RANDOM = new SecureRandom();
/**
* Generate a secure HMAC key (256 bits for HS256)
*/
public static String generateHMACKey() {
byte[] keyBytes = new byte[32]; // 256 bits
SECURE_RANDOM.nextBytes(keyBytes);
return Base64.getUrlEncoder().withoutPadding().encodeToString(keyBytes);
}
/**
* Create a signed JWT using HMAC
*/
public static String createSignedJWT(String subject, String issuer, 
String hmacKey) throws JOSEException {
// Create claims
JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
.subject(subject)
.issuer(issuer)
.claim("name", "John Doe")
.claim("email", "[email protected]")
.claim("roles", new String[]{"user", "admin"})
.issueTime(new Date())
.expirationTime(new Date(System.currentTimeMillis() + 3600_000)) // 1 hour
.jwtID(UUID.randomUUID().toString())
.build();
// Create JWS header with HS256 algorithm
JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.HS256)
.type(JOSEObjectType.JWT)
.contentType("JWT")
.build();
// Create signed JWT
SignedJWT signedJWT = new SignedJWT(header, claimsSet);
// Sign with HMAC key
MACSigner signer = new MACSigner(hmacKey);
signedJWT.sign(signer);
return signedJWT.serialize();
}
/**
* Verify and parse a signed JWT
*/
public static JWTClaimsSet verifyAndParseJWT(String jwtString, String hmacKey) 
throws Exception {
// Parse the JWT
SignedJWT signedJWT = SignedJWT.parse(jwtString);
// Verify the signature
MACVerifier verifier = new MACVerifier(hmacKey);
boolean verified = signedJWT.verify(verifier);
if (!verified) {
throw new SecurityException("JWT signature verification failed");
}
// Check expiration
JWTClaimsSet claimsSet = signedJWT.getJWTClaimsSet();
Date expirationTime = claimsSet.getExpirationTime();
if (expirationTime != null && expirationTime.before(new Date())) {
throw new SecurityException("JWT has expired");
}
return claimsSet;
}
public static void main(String[] args) throws Exception {
// Generate HMAC key
String hmacKey = generateHMACKey();
System.out.println("HMAC Key: " + hmacKey);
// Create signed JWT
String jwt = createSignedJWT("user-12345", "https://auth.example.com", hmacKey);
System.out.println("\nJWT (Compact):");
System.out.println(jwt);
// Verify and extract claims
JWTClaimsSet claims = verifyAndParseJWT(jwt, hmacKey);
System.out.println("\nVerified Claims:");
System.out.println("Subject: " + claims.getSubject());
System.out.println("Issuer: " + claims.getIssuer());
System.out.println("Issue Time: " + claims.getIssueTime());
System.out.println("Expiration: " + claims.getExpirationTime());
System.out.println("JWT ID: " + claims.getJWTID());
System.out.println("Name: " + claims.getClaim("name"));
System.out.println("Email: " + claims.getClaim("email"));
System.out.println("Roles: " + claims.getClaim("roles"));
// Tamper with the JWT
String[] parts = jwt.split("\\.");
String tampered = parts[0] + "." + parts[1] + ".tampered";
try {
verifyAndParseJWT(tampered, hmacKey);
System.out.println("ERROR: Tampering not detected!");
} catch (SecurityException e) {
System.out.println("\n✓ Tampering detected: " + e.getMessage());
}
}
}

3. JWS with RSA Digital Signatures (Asymmetric)

import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.RSASSASigner;
import com.nimbusds.jose.crypto.RSASSAVerifier;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.gen.RSAKeyGenerator;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.Date;
import java.util.UUID;
/**
* JWS with RSA signatures (asymmetric)
*/
public class JWSRSAExample {
/**
* Generate an RSA key pair for signing
*/
public static RSAKey generateRSAKey() throws JOSEException {
return new RSAKeyGenerator(2048)
.keyID(UUID.randomUUID().toString())
.generate();
}
/**
* Create a signed JWT using RSA
*/
public static String createSignedJWT(String subject, RSAKey rsaKey) throws JOSEException {
// Create claims
JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
.subject(subject)
.issuer("https://auth.example.com")
.claim("name", "John Doe")
.claim("email", "[email protected]")
.issueTime(new Date())
.expirationTime(new Date(System.currentTimeMillis() + 3600_000))
.jwtID(UUID.randomUUID().toString())
.build();
// Create JWS header with RS256 algorithm and key ID
JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.RS256)
.type(JOSEObjectType.JWT)
.keyID(rsaKey.getKeyID())
.build();
// Create signed JWT
SignedJWT signedJWT = new SignedJWT(header, claimsSet);
// Sign with RSA private key
RSASSASigner signer = new RSASSASigner(rsaKey);
signedJWT.sign(signer);
return signedJWT.serialize();
}
/**
* Verify a JWT using RSA public key
*/
public static JWTClaimsSet verifyJWT(String jwtString, RSAPublicKey publicKey) 
throws Exception {
// Parse JWT
SignedJWT signedJWT = SignedJWT.parse(jwtString);
// Verify signature
RSASSAVerifier verifier = new RSASSAVerifier(publicKey);
boolean verified = signedJWT.verify(verifier);
if (!verified) {
throw new SecurityException("RSA signature verification failed");
}
// Check expiration
JWTClaimsSet claimsSet = signedJWT.getJWTClaimsSet();
Date expirationTime = claimsSet.getExpirationTime();
if (expirationTime != null && expirationTime.before(new Date())) {
throw new SecurityException("JWT has expired");
}
return claimsSet;
}
public static void main(String[] args) throws Exception {
// Generate RSA key pair
RSAKey rsaKey = generateRSAKey();
RSAPublicKey publicKey = rsaKey.toRSAPublicKey();
RSAPrivateKey privateKey = rsaKey.toRSAPrivateKey();
System.out.println("RSA Key Pair Generated:");
System.out.println("Key ID: " + rsaKey.getKeyID());
System.out.println("Public Key: " + rsaKey.toPublicJWK().toJSONString());
// Create signed JWT
String jwt = createSignedJWT("user-12345", rsaKey);
System.out.println("\nRSA Signed JWT:");
System.out.println(jwt);
// Verify with public key
JWTClaimsSet claims = verifyJWT(jwt, publicKey);
System.out.println("\nVerified Claims:");
System.out.println("Subject: " + claims.getSubject());
System.out.println("Issuer: " + claims.getIssuer());
System.out.println("Name: " + claims.getClaim("name"));
// Verification with wrong public key should fail
RSAKey wrongKey = generateRSAKey();
try {
verifyJWT(jwt, wrongKey.toRSAPublicKey());
System.out.println("ERROR: Wrong key accepted!");
} catch (SecurityException e) {
System.out.println("\n✓ Wrong key rejected: " + e.getMessage());
}
}
}

4. JWS with ECDSA (Elliptic Curve)

import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.ECDSASigner;
import com.nimbusds.jose.crypto.ECDSAVerifier;
import com.nimbusds.jose.jwk.Curve;
import com.nimbusds.jose.jwk.ECKey;
import com.nimbusds.jose.jwk.gen.ECKeyGenerator;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import java.security.interfaces.ECPublicKey;
import java.util.Date;
/**
* JWS with ECDSA signatures (elliptic curve)
*/
public class JWSECDSExample {
/**
* Generate an EC key pair for signing
*/
public static ECKey generateECKey(Curve curve) throws JOSEException {
return new ECKeyGenerator(curve)
.keyID("ec-key-1")
.generate();
}
/**
* Create a signed JWT using ECDSA
*/
public static String createSignedJWT(String subject, ECKey ecKey, 
JWSAlgorithm algorithm) throws JOSEException {
JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
.subject(subject)
.issuer("https://auth.example.com")
.claim("name", "John Doe")
.issueTime(new Date())
.expirationTime(new Date(System.currentTimeMillis() + 3600_000))
.build();
JWSHeader header = new JWSHeader.Builder(algorithm)
.type(JOSEObjectType.JWT)
.keyID(ecKey.getKeyID())
.build();
SignedJWT signedJWT = new SignedJWT(header, claimsSet);
// Sign with EC private key
ECDSASigner signer = new ECDSASigner(ecKey);
signedJWT.sign(signer);
return signedJWT.serialize();
}
/**
* Verify a JWT using EC public key
*/
public static JWTClaimsSet verifyJWT(String jwtString, ECPublicKey publicKey, 
JWSAlgorithm algorithm) throws Exception {
SignedJWT signedJWT = SignedJWT.parse(jwtString);
ECDSAVerifier verifier = new ECDSAVerifier(publicKey);
boolean verified = signedJWT.verify(verifier);
if (!verified) {
throw new SecurityException("ECDSA signature verification failed");
}
return signedJWT.getJWTClaimsSet();
}
public static void main(String[] args) throws Exception {
// Test different EC curves
Curve[] curves = {Curve.P_256, Curve.P_384, Curve.P_521};
String[] curveNames = {"ES256 (P-256)", "ES384 (P-384)", "ES512 (P-521)"};
JWSAlgorithm[] algorithms = {
JWSAlgorithm.ES256, 
JWSAlgorithm.ES384, 
JWSAlgorithm.ES512
};
for (int i = 0; i < curves.length; i++) {
System.out.println("\n=== " + curveNames[i] + " ===");
// Generate EC key
ECKey ecKey = generateECKey(curves[i]);
// Create signed JWT
String jwt = createSignedJWT("user-" + i, ecKey, algorithms[i]);
System.out.println("JWT (first 50 chars): " + jwt.substring(0, 50) + "...");
System.out.println("JWT length: " + jwt.length() + " chars");
// Verify
JWTClaimsSet claims = verifyJWT(jwt, ecKey.toECPublicKey(), algorithms[i]);
System.out.println("Verified subject: " + claims.getSubject());
}
// Compare with RSA sizes
System.out.println("\n=== Size Comparison ===");
System.out.println("ES256 (P-256): ~" + createSignedJWT("test", 
generateECKey(Curve.P_256), JWSAlgorithm.ES256).length() + " chars");
System.out.println("ES384 (P-384): ~" + createSignedJWT("test", 
generateECKey(Curve.P_384), JWSAlgorithm.ES384).length() + " chars");
System.out.println("ES512 (P-521): ~" + createSignedJWT("test", 
generateECKey(Curve.P_521), JWSAlgorithm.ES512).length() + " chars");
System.out.println("RSA 2048: ~" + createRSAJWT() + " chars");
}
private static String createRSAJWT() throws JOSEException {
RSAKey rsaKey = new RSAKeyGenerator(2048).generate();
return JWSRSAExample.createSignedJWT("test", rsaKey);
}
}

5. JWS with Multiple Signatures (JSON Serialization)

import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.ECDSASigner;
import com.nimbusds.jose.crypto.MACSigner;
import com.nimbusds.jose.crypto.RSASSASigner;
import com.nimbusds.jose.jwk.ECKey;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.util.Base64URL;
import com.nimbusds.jwt.JWTClaimsSet;
import java.net.URI;
import java.util.*;
/**
* JWS with multiple signatures (JSON serialization)
*/
public class JWSMultiSignatureExample {
public static class MultiSignatureJWS {
private final Payload payload;
private final List<JWSObject> signatures;
private final String jsonSerialization;
public MultiSignatureJWS(Payload payload, List<JWSObject> signatures, 
String jsonSerialization) {
this.payload = payload;
this.signatures = new ArrayList<>(signatures);
this.jsonSerialization = jsonSerialization;
}
public Payload getPayload() { return payload; }
public List<JWSObject> getSignatures() { return new ArrayList<>(signatures); }
public String getJsonSerialization() { return jsonSerialization; }
}
/**
* Create a JWS with multiple signatures
*/
public static MultiSignatureJWS createMultiSignatureJWT(
JWTClaimsSet claimsSet,
List<JWSSigner> signers,
List<JWSAlgorithm> algorithms,
List<String> keyIds) throws JOSEException {
Payload payload = new Payload(claimsSet.toJSONObject());
// Create JWS objects for each signature
List<JWSObject> jwsObjects = new ArrayList<>();
for (int i = 0; i < signers.size(); i++) {
JWSHeader header = new JWSHeader.Builder(algorithms.get(i))
.keyID(keyIds.get(i))
.build();
JWSObject jwsObject = new JWSObject(header, payload);
jwsObject.sign(signers.get(i));
jwsObjects.add(jwsObject);
}
// Create JSON serialization
Map<String, Object> json = new LinkedHashMap<>();
// Add payload (base64url encoded)
json.put("payload", payload.toBase64URL().toString());
// Add signatures
List<Map<String, Object>> signatures = new ArrayList<>();
for (JWSObject jws : jwsObjects) {
Map<String, Object> sig = new LinkedHashMap<>();
// Protected header (base64url encoded)
sig.put("protected", jws.getHeader().toBase64URL().toString());
// Signature
sig.put("signature", jws.getSignature().toString());
signatures.add(sig);
}
json.put("signatures", signatures);
// Convert to JSON string
String jsonSerialization = new com.nimbusds.jose.util.JSONObjectUtils.toJSONString(json);
return new MultiSignatureJWS(payload, jwsObjects, jsonSerialization);
}
/**
* Verify all signatures in a multi-signature JWS
*/
public static boolean verifyMultiSignatureJWS(String jsonSerialization,
List<JWSVerifier> verifiers) 
throws Exception {
// Parse JSON
Map<String, Object> json = com.nimbusds.jose.util.JSONObjectUtils.parse(jsonSerialization);
// Get payload
String payloadB64 = (String) json.get("payload");
Payload payload = new Payload(new Base64URL(payloadB64));
// Get signatures
List<Object> sigs = (List<Object>) json.get("signatures");
if (sigs.size() != verifiers.size()) {
return false;
}
// Verify each signature
for (int i = 0; i < sigs.size(); i++) {
Map<String, Object> sigMap = (Map<String, Object>) sigs.get(i);
String protectedB64 = (String) sigMap.get("protected");
String signatureB64 = (String) sigMap.get("signature");
JWSHeader header = JWSHeader.parse(new Base64URL(protectedB64));
Base64URL signature = new Base64URL(signatureB64);
// Reconstruct signing input
String signingInput = protectedB64 + "." + payloadB64;
if (!verifiers.get(i).verify(header, signingInput.getBytes("UTF-8"), signature)) {
return false;
}
}
return true;
}
public static void main(String[] args) throws Exception {
// Create claims
JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
.subject("multi-party-document")
.issuer("contract.example.com")
.claim("document_id", "DOC-2024-001")
.claim("parties", new String[]{"Alice", "Bob", "Charlie"})
.issueTime(new Date())
.build();
// Create signers for different parties
List<JWSSigner> signers = new ArrayList<>();
List<JWSAlgorithm> algorithms = new ArrayList<>();
List<String> keyIds = new ArrayList<>();
// Party 1: HMAC (symmetric)
String hmacKey = JWSHMACExample.generateHMACKey();
signers.add(new MACSigner(hmacKey));
algorithms.add(JWSAlgorithm.HS256);
keyIds.add("hmac-key-1");
// Party 2: RSA
RSAKey rsaKey = new RSAKeyGenerator(2048).keyID("rsa-key-1").generate();
signers.add(new RSASSASigner(rsaKey));
algorithms.add(JWSAlgorithm.RS256);
keyIds.add(rsaKey.getKeyID());
// Party 3: EC
ECKey ecKey = new ECKeyGenerator(Curve.P_256).keyID("ec-key-1").generate();
signers.add(new ECDSASigner(ecKey));
algorithms.add(JWSAlgorithm.ES256);
keyIds.add(ecKey.getKeyID());
System.out.println("Creating multi-signature JWS with " + signers.size() + " signatures");
// Create multi-signature JWS
MultiSignatureJWS multiSig = createMultiSignatureJWT(
claimsSet, signers, algorithms, keyIds);
System.out.println("\nJSON Serialization:");
System.out.println(multiSig.getJsonSerialization());
// Create verifiers
List<JWSVerifier> verifiers = new ArrayList<>();
verifiers.add(new MACVerifier(hmacKey));
verifiers.add(new RSASSAVerifier(rsaKey.toRSAPublicKey()));
verifiers.add(new ECDSAVerifier(ecKey.toECPublicKey()));
// Verify all signatures
boolean allValid = verifyMultiSignatureJWS(
multiSig.getJsonSerialization(), verifiers);
System.out.println("\nAll signatures valid: " + allValid);
// Try with missing verifier
List<JWSVerifier> partialVerifiers = verifiers.subList(0, 2);
boolean partialValid = verifyMultiSignatureJWS(
multiSig.getJsonSerialization(), partialVerifiers);
System.out.println("Partial signatures valid: " + partialValid);
}
}

6. JWS with Custom Headers and Claims

import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.MACSigner;
import com.nimbusds.jose.crypto.MACVerifier;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import java.net.URI;
import java.util.*;
/**
* JWS with custom headers and advanced claims
*/
public class JWSCustomExample {
/**
* Create a JWT with custom headers and claims
*/
public static String createCustomJWT(String hmacKey) throws JOSEException {
// Custom headers
Map<String, Object> customHeaders = new LinkedHashMap<>();
customHeaders.put("kid", "signing-key-2024");
customHeaders.put("cty", "JWT");
customHeaders.put("crit", Arrays.asList("exp", "custom-header"));
customHeaders.put("custom-header", "custom-value");
customHeaders.put("https://example.com/custom", "uri-based-header");
JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.HS256)
.customParams(customHeaders)
.build();
// Advanced claims
Map<String, Object> customClaims = new LinkedHashMap<>();
customClaims.put("string", "value");
customClaims.put("number", 12345);
customClaims.put("boolean", true);
customClaims.put("null", null);
customClaims.put("array", Arrays.asList(1, 2, 3, 4, 5));
Map<String, Object> nested = new LinkedHashMap<>();
nested.put("nested-key", "nested-value");
nested.put("nested-number", 42);
customClaims.put("object", nested);
JWTClaimsSet.Builder claimsBuilder = new JWTClaimsSet.Builder()
.subject("custom-subject")
.issuer("https://custom-issuer.example.com")
.issueTime(new Date())
.expirationTime(new Date(System.currentTimeMillis() + 3600_000))
.jwtID(UUID.randomUUID().toString())
.audience("https://api1.example.com", "https://api2.example.com")
.notBeforeTime(new Date(System.currentTimeMillis() - 60_000));
// Add custom claims
for (Map.Entry<String, Object> entry : customClaims.entrySet()) {
claimsBuilder.claim(entry.getKey(), entry.getValue());
}
// Add URI claim
claimsBuilder.claim("homepage", URI.create("https://example.com/profile"));
JWTClaimsSet claimsSet = claimsBuilder.build();
// Create and sign JWT
SignedJWT signedJWT = new SignedJWT(header, claimsSet);
MACSigner signer = new MACSigner(hmacKey);
signedJWT.sign(signer);
return signedJWT.serialize();
}
/**
* Parse and display JWT structure
*/
public static void inspectJWT(String jwtString, String hmacKey) throws Exception {
System.out.println("\n=== JWT Inspection ===");
System.out.println("Full JWT: " + jwtString);
// Split into parts
String[] parts = jwtString.split("\\.");
System.out.println("\nParts:");
System.out.println("  Header (Base64): " + parts[0]);
System.out.println("  Payload (Base64): " + parts[1]);
System.out.println("  Signature (Base64): " + parts[2].substring(0, 20) + "...");
// Decode header
String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]), "UTF-8");
System.out.println("\nHeader (JSON):");
System.out.println("  " + headerJson);
// Decode payload
String payloadJson = new String(Base64.getUrlDecoder().decode(parts[1]), "UTF-8");
System.out.println("\nPayload (JSON):");
System.out.println("  " + payloadJson);
// Verify signature
SignedJWT signedJWT = SignedJWT.parse(jwtString);
MACVerifier verifier = new MACVerifier(hmacKey);
boolean verified = signedJWT.verify(verifier);
System.out.println("\nSignature valid: " + verified);
// Extract all claims
JWTClaimsSet claims = signedJWT.getJWTClaimsSet();
System.out.println("\nAll Claims:");
for (String claimName : claims.getClaims().keySet()) {
Object value = claims.getClaim(claimName);
System.out.println("  " + claimName + ": " + value);
}
}
public static void main(String[] args) throws Exception {
String hmacKey = JWSHMACExample.generateHMACKey();
String jwt = createCustomJWT(hmacKey);
inspectJWT(jwt, hmacKey);
}
}

7. JWS with JWK (JSON Web Key) Integration

import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.RSASSASigner;
import com.nimbusds.jose.crypto.RSASSAVerifier;
import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.gen.RSAKeyGenerator;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import java.util.*;
/**
* JWS with JWK (JSON Web Key) integration
*/
public class JWSJWKExample {
public static class JWKManager {
private final Map<String, RSAKey> keyStore = new HashMap<>();
private final Map<String, RSAKey> publicKeys = new HashMap<>();
/**
* Generate and store a new key pair
*/
public RSAKey generateKey(String keyId) throws JOSEException {
RSAKey rsaKey = new RSAKeyGenerator(2048)
.keyID(keyId)
.generate();
keyStore.put(keyId, rsaKey);
publicKeys.put(keyId, rsaKey.toPublicJWK());
return rsaKey;
}
/**
* Get JWK set with all public keys
*/
public JWKSet getPublicJWKSet() {
return new JWKSet(new ArrayList<>(publicKeys.values()));
}
/**
* Get JWK set as JSON string
*/
public String getPublicJWKSetJSON() {
return getPublicJWKSet().toJSONObject().toJSONString();
}
/**
* Sign a JWT with a specific key
*/
public String signJWT(JWTClaimsSet claims, String keyId) throws JOSEException {
RSAKey signingKey = keyStore.get(keyId);
if (signingKey == null) {
throw new IllegalArgumentException("Key not found: " + keyId);
}
JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.RS256)
.keyID(keyId)
.build();
SignedJWT signedJWT = new SignedJWT(header, claims);
RSASSASigner signer = new RSASSASigner(signingKey);
signedJWT.sign(signer);
return signedJWT.serialize();
}
/**
* Verify a JWT using the appropriate key from JWK set
*/
public JWTClaimsSet verifyJWT(String jwtString) throws Exception {
SignedJWT signedJWT = SignedJWT.parse(jwtString);
// Get key ID from header
String keyId = signedJWT.getHeader().getKeyID();
if (keyId == null) {
throw new SecurityException("No key ID in JWT header");
}
// Get public key from store
RSAKey publicKey = publicKeys.get(keyId);
if (publicKey == null) {
throw new SecurityException("No public key found for key ID: " + keyId);
}
// Verify signature
RSASSAVerifier verifier = new RSASSAVerifier(publicKey);
if (!signedJWT.verify(verifier)) {
throw new SecurityException("Signature verification failed");
}
return signedJWT.getJWTClaimsSet();
}
}
public static void main(String[] args) throws Exception {
JWKManager jwkManager = new JWKManager();
// Generate multiple keys
System.out.println("Generating keys...");
jwkManager.generateKey("key-2024-01");
jwkManager.generateKey("key-2024-02");
jwkManager.generateKey("key-2024-03");
// Display JWK set
System.out.println("\nJWK Set (public keys):");
System.out.println(jwkManager.getPublicJWKSetJSON());
// Create claims
JWTClaimsSet claims = new JWTClaimsSet.Builder()
.subject("user-12345")
.issuer("https://auth.example.com")
.issueTime(new Date())
.expirationTime(new Date(System.currentTimeMillis() + 3600_000))
.claim("name", "John Doe")
.build();
// Sign with different keys
System.out.println("\nSigning JWTs with different keys:");
for (String keyId : Arrays.asList("key-2024-01", "key-2024-02", "key-2024-03")) {
String jwt = jwkManager.signJWT(claims, keyId);
System.out.println("  Key " + keyId + ": " + jwt.substring(0, 50) + "...");
// Verify
JWTClaimsSet verified = jwkManager.verifyJWT(jwt);
System.out.println("  Verified subject: " + verified.getSubject());
}
// Attempt with unknown key
try {
jwkManager.signJWT(claims, "unknown-key");
System.out.println("ERROR: Unknown key accepted!");
} catch (IllegalArgumentException e) {
System.out.println("\n✓ Unknown key rejected: " + e.getMessage());
}
}
}

8. Spring Security Integration

import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.proc.JWSVerificationKeySelector;
import com.nimbusds.jose.proc.SecurityContext;
import com.nimbusds.jwt.proc.DefaultJWTProcessor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.web.SecurityFilterChain;
/**
* Spring Security integration with JWT
*/
@Configuration
@EnableWebSecurity
public class JWTSpringSecurityConfig {
private final RSAKey rsaKey;
public JWTSpringSecurityConfig() throws Exception {
// Generate or load RSA key
this.rsaKey = new RSAKeyGenerator(2048)
.keyID("spring-key")
.generate();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authz -> authz
.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/api/user/**").hasAuthority("SCOPE_user")
.requestMatchers("/api/admin/**").hasAuthority("SCOPE_admin")
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(jwt -> jwt
.decoder(jwtDecoder())
)
)
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
);
return http.build();
}
@Bean
public JwtDecoder jwtDecoder() {
// Create JWK set with public key
JWKSet jwkSet = new JWKSet(rsaKey.toPublicJWK());
// Create JWT processor
DefaultJWTProcessor<SecurityContext> jwtProcessor = new DefaultJWTProcessor<>();
jwtProcessor.setJWSKeySelector(new JWSVerificationKeySelector<>(
JWSAlgorithm.RS256, 
new ImmutableJWKSet<>(jwkSet)
));
return new NimbusJwtDecoder(jwtProcessor);
}
// REST controller for JWT issuance
@RestController
@RequestMapping("/api/auth")
public static class AuthController {
private final RSAKey rsaKey;
public AuthController(RSAKey rsaKey) {
this.rsaKey = rsaKey;
}
@PostMapping("/token")
public Map<String, String> generateToken(@RequestBody AuthRequest request) 
throws JOSEException {
// Create claims
JWTClaimsSet claims = new JWTClaimsSet.Builder()
.subject(request.username)
.issuer("https://auth.example.com")
.issueTime(new Date())
.expirationTime(new Date(System.currentTimeMillis() + 3600_000))
.claim("scope", request.roles)
.build();
// Sign JWT
JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.RS256)
.keyID(rsaKey.getKeyID())
.build();
SignedJWT signedJWT = new SignedJWT(header, claims);
RSASSASigner signer = new RSASSASigner(rsaKey);
signedJWT.sign(signer);
return Map.of("access_token", signedJWT.serialize());
}
@GetMapping("/.well-known/jwks.json")
public Map<String, Object> getJWKS() {
JWKSet jwkSet = new JWKSet(rsaKey.toPublicJWK());
return jwkSet.toJSONObject();
}
}
public static class AuthRequest {
public String username;
public List<String> roles;
}
}

JWS Algorithm Comparison

AlgorithmTypeKey SizeSignature SizePerformanceSecurity Level
HS256HMAC256 bits256 bitsVery FastHigh
HS384HMAC384 bits384 bitsFastHigher
HS512HMAC512 bits512 bitsFastHighest
RS256RSA2048 bits2048 bitsSlowHigh
RS384RSA3072 bits3072 bitsSlowHigher
RS512RSA4096 bits4096 bitsSlowHighest
ES256ECDSA256 bits512 bitsFastHigh
ES384ECDSA384 bits768 bitsFastHigher
ES512ECDSA521 bits1056 bitsFastHighest
PS256RSA-PSS2048 bits2048 bitsSlowHigh
EdDSAEdwards256 bits512 bitsFastHigh

Security Best Practices

PracticeRecommendation
Algorithm SelectionUse ES256 or EdDSA for new applications; avoid "none" algorithm
Key ProtectionStore signing keys securely (HSM, vault, secure key store)
Key RotationRotate keys regularly, support multiple active keys
ExpirationAlways set reasonable expiration times
Audience ValidationValidate the aud claim for your specific API
Critical HeadersValidate the crit header for required extensions
Algorithm ConfusionExplicitly validate algorithm to prevent algorithm switching attacks

Performance Benchmarks

public class JWSPerformanceBenchmark {
public static void main(String[] args) throws Exception {
String payload = "test-payload";
int iterations = 1000;
// Test different algorithms
benchmarkAlgorithm("HS256", () -> {
String key = JWSHMACExample.generateHMACKey();
return JWSHMACExample.createSignedJWT("test", "test-issuer", key);
}, iterations);
benchmarkAlgorithm("RS256", () -> {
RSAKey key = new RSAKeyGenerator(2048).generate();
return JWSRSAExample.createSignedJWT("test", key);
}, iterations);
benchmarkAlgorithm("ES256", () -> {
ECKey key = new ECKeyGenerator(Curve.P_256).generate();
return JWSECDSExample.createSignedJWT("test", key, JWSAlgorithm.ES256);
}, iterations);
}
private static void benchmarkAlgorithm(String name, Supplier<String> supplier, 
int iterations) {
// Warmup
for (int i = 0; i < 100; i++) {
supplier.get();
}
// Benchmark
long start = System.nanoTime();
for (int i = 0; i < iterations; i++) {
supplier.get();
}
long duration = System.nanoTime() - start;
System.out.printf("%s: %.2f ms per operation%n", 
name, duration / (iterations * 1_000_000.0));
}
}

Common Vulnerabilities and Mitigations

VulnerabilityDescriptionMitigation
Algorithm ConfusionAttacker changes algorithm to "none" or weak algorithmExplicitly validate allowed algorithms
Key ConfusionRSA key used for HMAC verificationValidate key type matches algorithm
Timing AttacksVariable-time signature verificationUse constant-time comparison
Replay AttacksReusing valid tokensInclude nonce or JWT ID, validate timestamps
Key InjectionAttacker provides own keyUse trusted key sources (JWKS)

Conclusion

JSON Web Signatures provide a robust, standards-based foundation for secure data exchange in modern Java applications. Whether you're building microservices, implementing OAuth2, or creating stateless authentication systems, JWS offers:

  1. Flexibility: Support for multiple algorithms and key types
  2. Interoperability: Works across different platforms and languages
  3. Security: Strong cryptographic guarantees for authenticity and integrity
  4. Standardization: IETF RFC with broad industry adoption

The implementations provided demonstrate the full spectrum of JWS capabilities:

  • HMAC for simple, symmetric signing
  • RSA for asymmetric signatures with public key distribution
  • ECDSA for smaller signatures and better performance
  • Multi-signature for complex scenarios requiring multiple parties
  • JWK integration for dynamic key management
  • Spring Security for production-ready authentication

For Java developers, mastering JWS is essential for building secure, modern applications. The combination of strong cryptography, flexible serialization, and broad ecosystem support makes JWS an indispensable tool in the security toolkit.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper