In modern distributed systems, the need for secure, interoperable token formats has never been greater. JSON Object Signing and Encryption (JOSE) is a suite of specifications that provides a standardized way to represent claims, sign them, and encrypt them using JSON data structures. For Java developers building microservices, authentication systems, or any application requiring secure token exchange, understanding and correctly implementing JOSE—including JWT, JWS, and JWE—is essential.
What is JOSE?
JOSE (JSON Object Signing and Encryption) is a framework of specifications for securely transferring claims between parties. It consists of several interrelated standards:
| Specification | Purpose | RFC |
|---|---|---|
| JWT (JSON Web Token) | Represents claims as a JSON object | RFC 7519 |
| JWS (JSON Web Signature) | Signs content (JWT or arbitrary data) | RFC 7515 |
| JWE (JSON Web Encryption) | Encrypts content (JWT or arbitrary data) | RFC 7516 |
| JWK (JSON Web Key) | Represents cryptographic keys in JSON | RFC 7517 |
| JWA (JSON Web Algorithms) | Defines cryptographic algorithms | RFC 7518 |
Why JOSE is Essential for Java Applications
- Interoperability: Standardized format works across different platforms and languages.
- Stateless Authentication: JWT enables distributed authentication without server-side session storage.
- Flexible Security: Choose between signed only (JWS) or encrypted (JWE) tokens.
- Standard Algorithms: Well-defined cryptographic algorithms with clear security properties.
- Claims-Based: Rich claim structure for expressing user attributes and permissions.
- Microservices Ready: Perfect for service-to-service authentication and authorization.
Java Libraries for JOSE
1. Maven Dependencies
<dependencies> <!-- Nimbus JOSE + JWT (Most popular, actively maintained) --> <dependency> <groupId>com.nimbusds</groupId> <artifactId>nimbus-jose-jwt</artifactId> <version>9.37.3</version> </dependency> <!-- JJWT (Simple, opinionated, Java JWT library) --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.12.5</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> <version>0.12.5</version> <scope>runtime</scope> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <version>0.12.5</version> <scope>runtime</scope> </dependency> <!-- Spring Security OAuth2 Resource Server --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-oauth2-resource-server</artifactId> <version>3.2.0</version> </dependency> <!-- Bouncy Castle for advanced algorithms --> <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk18on</artifactId> <version>1.78</version> </dependency> <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcpkix-jdk18on</artifactId> <version>1.78</version> </dependency> </dependencies>
JSON Web Tokens (JWT) Structure
A JWT consists of three parts separated by dots:
header.payload.signature
Header:
{ "alg": "HS256", "typ": "JWT" } Payload (Claims):
{ "sub": "1234567890", "name": "John Doe", "iat": 1516239022, "exp": 1516242622, "roles": ["admin", "user"] } Signature: Cryptographic signature of header + payload
JSON Web Signature (JWS) Implementation
1. Basic JWT Signing and Verification with Nimbus JOSE
import com.nimbusds.jose.*; import com.nimbusds.jose.crypto.*; import com.nimbusds.jwt.*; import java.security.SecureRandom; import java.util.*; import java.time.Instant; public class JWSExample { public static void main(String[] args) throws Exception { System.out.println("=== JWS (JSON Web Signature) Examples ===\n"); // 1. HMAC SHA-256 (Symmetric) hmacExample(); // 2. RSA SHA-256 (Asymmetric) rsaExample(); // 3. ECDSA (Elliptic Curve) ecdsaExample(); // 4. Ed25519 (Edwards-curve Digital Signature Algorithm) ed25519Example(); } public static void hmacExample() throws Exception { System.out.println("\n--- HMAC SHA-256 (HS256) ---"); // Generate a secure secret key (at least 256 bits) byte[] secret = new byte[32]; new SecureRandom().nextBytes(secret); // Create signer JWSSigner signer = new MACSigner(secret); // Build JWT claims JWTClaimsSet claimsSet = new JWTClaimsSet.Builder() .subject("alice") .issuer("https://issuer.example.com") .claim("name", "Alice Smith") .claim("roles", Arrays.asList("user", "admin")) .issueTime(new Date()) .expirationTime(Date.from(Instant.now().plusSeconds(3600))) .jwtID(UUID.randomUUID().toString()) .build(); // Create JWS object SignedJWT signedJWT = new SignedJWT( new JWSHeader(JWSAlgorithm.HS256), claimsSet ); // Sign signedJWT.sign(signer); // Serialize String token = signedJWT.serialize(); System.out.println("JWT Token: " + token); System.out.println("Token length: " + token.length() + " characters"); // Verify JWSVerifier verifier = new MACVerifier(secret); boolean verified = signedJWT.verify(verifier); System.out.println("Signature verified: " + verified); // Extract claims JWTClaimsSet extracted = signedJWT.getJWTClaimsSet(); System.out.println("Subject: " + extracted.getSubject()); System.out.println("Issuer: " + extracted.getIssuer()); System.out.println("Roles: " + extracted.getStringListClaim("roles")); } public static void rsaExample() throws Exception { System.out.println("\n--- RSA SHA-256 (RS256) ---"); // Generate RSA key pair java.security.KeyPairGenerator keyGen = java.security.KeyPairGenerator.getInstance("RSA"); keyGen.initialize(2048); java.security.KeyPair keyPair = keyGen.generateKeyPair(); // Create signer with private key JWSSigner signer = new RSASSASigner(keyPair.getPrivate()); // Build claims JWTClaimsSet claimsSet = new JWTClaimsSet.Builder() .subject("bob") .issuer("https://auth.example.com") .claim("email", "[email protected]") .issueTime(new Date()) .build(); // Sign SignedJWT signedJWT = new SignedJWT( new JWSHeader.Builder(JWSAlgorithm.RS256) .keyID("rsa-key-1") .build(), claimsSet ); signedJWT.sign(signer); String token = signedJWT.serialize(); System.out.println("RSA JWT: " + token.substring(0, 50) + "..."); // Verify with public key JWSVerifier verifier = new RSASSAVerifier((java.security.interfaces.RSAPublicKey) keyPair.getPublic()); boolean verified = signedJWT.verify(verifier); System.out.println("RSA signature verified: " + verified); } public static void ecdsaExample() throws Exception { System.out.println("\n--- ECDSA (ES256) ---"); // Generate EC key pair (P-256 curve) java.security.KeyPairGenerator keyGen = java.security.KeyPairGenerator.getInstance("EC"); keyGen.initialize(new java.security.spec.ECGenParameterSpec("secp256r1")); java.security.KeyPair keyPair = keyGen.generateKeyPair(); // Create signer JWSSigner signer = new ECDSASigner(keyPair.getPrivate()); // Build and sign JWTClaimsSet claimsSet = new JWTClaimsSet.Builder() .subject("charlie") .claim("scope", "read write") .build(); SignedJWT signedJWT = new SignedJWT( new JWSHeader(JWSAlgorithm.ES256), claimsSet ); signedJWT.sign(signer); String token = signedJWT.serialize(); System.out.println("ECDSA JWT: " + token.substring(0, 50) + "..."); // Verify JWSVerifier verifier = new ECDSAVerifier( (java.security.interfaces.ECPublicKey) keyPair.getPublic()); boolean verified = signedJWT.verify(verifier); System.out.println("ECDSA verified: " + verified); } public static void ed25519Example() throws Exception { System.out.println("\n--- Ed25519 (EdDSA) ---"); // Note: Requires Bouncy Castle java.security.Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); try { java.security.KeyPairGenerator keyGen = java.security.KeyPairGenerator.getInstance("Ed25519", "BC"); java.security.KeyPair keyPair = keyGen.generateKeyPair(); // Create signer JWSSigner signer = new Ed25519Signer(keyPair.getPrivate()); JWTClaimsSet claimsSet = new JWTClaimsSet.Builder() .subject("david") .build(); SignedJWT signedJWT = new SignedJWT( new JWSHeader(JWSAlgorithm.EdDSA), claimsSet ); signedJWT.sign(signer); String token = signedJWT.serialize(); System.out.println("Ed25519 JWT: " + token.substring(0, 50) + "..."); // Verify JWSVerifier verifier = new Ed25519Verifier( (java.security.PublicKey) keyPair.getPublic()); boolean verified = signedJWT.verify(verifier); System.out.println("Ed25519 verified: " + verified); } catch (Exception e) { System.out.println("Ed25519 not available: " + e.getMessage()); } } } 2. JWT with JJWT Library (Simpler API)
import io.jsonwebtoken.*; import io.jsonwebtoken.security.Keys; import javax.crypto.SecretKey; import java.security.Key; import java.util.Date; import java.util.Map; import java.util.HashMap; import java.util.concurrent.TimeUnit; public class JJWTExample { public static void main(String[] args) { System.out.println("=== JJWT Library Examples ===\n"); // 1. Symmetric signing (HS256) symmetricExample(); // 2. Asymmetric signing (RS256) asymmetricExample(); // 3. JWT parsing and validation parsingExample(); // 4. JWT with custom claims customClaimsExample(); } public static void symmetricExample() { System.out.println("\n--- HMAC Symmetric (HS256) ---"); // Generate a secure key for HS256 SecretKey key = Keys.secretKeyFor(SignatureAlgorithm.HS256); // Create JWT String jws = Jwts.builder() .subject("alice") .issuer("https://myapp.com") .issuedAt(new Date()) .expiration(new Date(System.currentTimeMillis() + 3600000)) // 1 hour .claim("name", "Alice Wonderland") .claim("roles", new String[]{"user", "admin"}) .signWith(key) .compact(); System.out.println("JWT: " + jws); System.out.println("Parts: " + jws.split("\\.").length); // Parse and verify Jws<Claims> jwsClaims = Jwts.parser() .verifyWith(key) .build() .parseSignedClaims(jws); Claims claims = jwsClaims.getPayload(); System.out.println("Subject: " + claims.getSubject()); System.out.println("Name: " + claims.get("name", String.class)); System.out.println("Roles: " + String.join(", ", claims.get("roles", String[].class))); } public static void asymmetricExample() { System.out.println("\n--- RSA Asymmetric (RS256) ---"); // Generate RSA key pair java.security.KeyPair keyPair = Keys.keyPairFor(SignatureAlgorithm.RS256); // Sign with private key String jws = Jwts.builder() .subject("bob") .issuer("auth-server") .claim("scope", "read:profile") .signWith(keyPair.getPrivate()) .compact(); System.out.println("RSA JWT: " + jws); // Verify with public key Jws<Claims> jwsClaims = Jwts.parser() .verifyWith(keyPair.getPublic()) .build() .parseSignedClaims(jws); boolean valid = jwsClaims != null; System.out.println("Signature valid: " + valid); } public static void parsingExample() { System.out.println("\n--- JWT Parsing and Validation ---"); SecretKey key = Keys.secretKeyFor(SignatureAlgorithm.HS256); // Create expired token String expiredJwt = Jwts.builder() .subject("test") .expiration(new Date(System.currentTimeMillis() - 1000)) // Already expired .signWith(key) .compact(); // Create valid token String validJwt = Jwts.builder() .subject("test") .expiration(new Date(System.currentTimeMillis() + 3600000)) .signWith(key) .compact(); // Parse valid token try { Jws<Claims> valid = Jwts.parser() .verifyWith(key) .build() .parseSignedClaims(validJwt); System.out.println("Valid token accepted"); } catch (JwtException e) { System.out.println("Valid token rejected: " + e.getMessage()); } // Parse expired token try { Jwts.parser() .verifyWith(key) .build() .parseSignedClaims(expiredJwt); System.out.println("ERROR: Expired token accepted"); } catch (ExpiredJwtException e) { System.out.println("Expired token correctly rejected: " + e.getMessage()); } } public static void customClaimsExample() { System.out.println("\n--- Custom Claims ---"); SecretKey key = Keys.secretKeyFor(SignatureAlgorithm.HS256); // Create with custom claims Map<String, Object> customClaims = new HashMap<>(); customClaims.put("tenant", "acme-corp"); customClaims.put("region", "us-east"); customClaims.put("feature_flags", new String[]{"beta", "early-access"}); String jws = Jwts.builder() .subject("charlie") .claims(customClaims) .signWith(key) .compact(); // Parse and extract Jws<Claims> parsed = Jwts.parser() .verifyWith(key) .build() .parseSignedClaims(jws); Claims claims = parsed.getPayload(); System.out.println("Tenant: " + claims.get("tenant")); System.out.println("Region: " + claims.get("region")); System.out.println("Features: " + String.join(", ", claims.get("feature_flags", String[].class))); } } JSON Web Encryption (JWE) Implementation
JWE provides confidentiality by encrypting the payload, optionally with additional authenticated data.
1. Basic JWE with Nimbus
import com.nimbusds.jose.*; import com.nimbusds.jose.crypto.*; import com.nimbusds.jwt.*; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import java.security.KeyPair; import java.security.KeyPairGenerator; public class JWEExample { public static void main(String[] args) throws Exception { System.out.println("=== JWE (JSON Web Encryption) Examples ===\n"); // 1. Direct encryption with AES-GCM directEncryptionExample(); // 2. Key encryption with RSA-OAEP rsaKeyEncryptionExample(); // 3. Password-based encryption passwordBasedExample(); // 4. Nested JWS + JWE (Sign then Encrypt) nestedExample(); } public static void directEncryptionExample() throws Exception { System.out.println("\n--- Direct Encryption (A256GCM) ---"); // Generate AES key for content encryption KeyGenerator keyGen = KeyGenerator.getInstance("AES"); keyGen.init(256); SecretKey aesKey = keyGen.generateKey(); // Create JWE header JWEHeader header = new JWEHeader.Builder(JWEAlgorithm.DIR, EncryptionMethod.A256GCM) .contentType("JWT") // Nested JWT .build(); // Create claims JWTClaimsSet claimsSet = new JWTClaimsSet.Builder() .subject("sensitive-data") .claim("credit_card", "4111-1111-1111-1111") .claim("cvv", "123") .expirationTime(new Date(System.currentTimeMillis() + 300000)) // 5 minutes .build(); // Create JWE Payload payload = new Payload(claimsSet.toJSONObject()); JWEObject jweObject = new JWEObject(header, payload); // Encrypt jweObject.encrypt(new DirectEncrypter(aesKey)); // Serialize String jweString = jweObject.serialize(); System.out.println("JWE Token: " + jweString); System.out.println("JWE Parts: " + jweString.split("\\.").length); // Decrypt JWEObject parsed = JWEObject.parse(jweString); parsed.decrypt(new DirectDecrypter(aesKey)); JWTClaimsSet decrypted = JWTClaimsSet.parse(parsed.getPayload().toJSONObject()); System.out.println("Decrypted subject: " + decrypted.getSubject()); System.out.println("Decrypted credit card: " + decrypted.getClaim("credit_card")); } public static void rsaKeyEncryptionExample() throws Exception { System.out.println("\n--- RSA Key Encryption (RSA-OAEP + A256GCM) ---"); // Generate RSA key pair for key encryption KeyPairGenerator rsaGen = KeyPairGenerator.getInstance("RSA"); rsaGen.initialize(2048); KeyPair rsaKeyPair = rsaGen.generateKeyPair(); // Create JWE with RSA key encryption JWEHeader header = new JWEHeader.Builder( JWEAlgorithm.RSA_OAEP_256, EncryptionMethod.A256GCM) .keyID("rsa-encryption-key") .build(); JWTClaimsSet claimsSet = new JWTClaimsSet.Builder() .subject("encrypted-payload") .claim("secret", "TopSecretInformation") .build(); Payload payload = new Payload(claimsSet.toJSONObject()); JWEObject jweObject = new JWEObject(header, payload); // Encrypt with RSA public key jweObject.encrypt(new RSAEncrypter(rsaKeyPair.getPublic())); String encrypted = jweObject.serialize(); System.out.println("RSA Encrypted JWE: " + encrypted.substring(0, 60) + "..."); // Decrypt with RSA private key JWEObject parsed = JWEObject.parse(encrypted); parsed.decrypt(new RSADecrypter(rsaKeyPair.getPrivate())); JWTClaimsSet decrypted = JWTClaimsSet.parse(parsed.getPayload().toJSONObject()); System.out.println("Decrypted: " + decrypted.getClaim("secret")); } public static void passwordBasedExample() throws Exception { System.out.println("\n--- Password-Based Encryption (PBES2) ---"); String password = "user-password"; // Create header with PBES2 JWEHeader header = new JWEHeader.Builder( JWEAlgorithm.PBES2_HS256_A128KW, EncryptionMethod.A128GCM) .pbes2SaltInput(Base64URL.encode("salt".getBytes())) .pbes2Count(4096) // Iteration count .build(); JWTClaimsSet claimsSet = new JWTClaimsSet.Builder() .claim("encrypted-by", "password") .build(); Payload payload = new Payload(claimsSet.toJSONObject()); JWEObject jweObject = new JWEObject(header, payload); // Encrypt with password jweObject.encrypt(new PasswordBasedEncrypter(password, 4096)); String encrypted = jweObject.serialize(); System.out.println("Password encrypted: " + encrypted); // Decrypt with password JWEObject parsed = JWEObject.parse(encrypted); parsed.decrypt(new PasswordBasedDecrypter(password)); System.out.println("Successfully decrypted with password"); } public static void nestedExample() throws Exception { System.out.println("\n--- Nested JWS + JWE (Sign then Encrypt) ---"); // Setup keys KeyGenerator aesGen = KeyGenerator.getInstance("AES"); aesGen.init(256); SecretKey encryptionKey = aesGen.generateKey(); KeyPairGenerator rsaGen = KeyPairGenerator.getInstance("RSA"); rsaGen.initialize(2048); KeyPair signingKeyPair = rsaGen.generateKeyPair(); // Step 1: Create and sign inner JWT JWTClaimsSet claimsSet = new JWTClaimsSet.Builder() .subject("alice") .claim("data", "sensitive-information") .build(); SignedJWT signedJWT = new SignedJWT( new JWSHeader(JWSAlgorithm.RS256), claimsSet ); signedJWT.sign(new RSASSASigner(signingKeyPair.getPrivate())); // Step 2: Encrypt the signed JWT JWEHeader jweHeader = new JWEHeader.Builder( JWEAlgorithm.DIR, EncryptionMethod.A256GCM) .contentType("JWT") // Indicates nested JWT .build(); Payload payload = new Payload(signedJWT.serialize()); JWEObject jweObject = new JWEObject(jweHeader, payload); jweObject.encrypt(new DirectEncrypter(encryptionKey)); String nestedToken = jweObject.serialize(); System.out.println("Nested (Encrypted + Signed) token: " + nestedToken); // Step 3: Decrypt and verify JWEObject parsedJWE = JWEObject.parse(nestedToken); parsedJWE.decrypt(new DirectDecrypter(encryptionKey)); SignedJWT parsedSigned = SignedJWT.parse(parsedJWE.getPayload().toString()); parsedSigned.verify(new RSASSAVerifier((java.security.interfaces.RSAPublicKey) signingKeyPair.getPublic())); JWTClaimsSet extracted = parsedSigned.getJWTClaimsSet(); System.out.println("Decrypted and verified subject: " + extracted.getSubject()); } } JSON Web Keys (JWK) Management
1. JWK Generation and Serialization
import com.nimbusds.jose.jwk.*; import com.nimbusds.jose.jwk.gen.*; import com.nimbusds.jose.jwk.source.*; import com.nimbusds.jose.proc.SecurityContext; import java.util.*; public class JWKExample { public static void main(String[] args) throws Exception { System.out.println("=== JWK (JSON Web Key) Examples ===\n"); // 1. Generate RSA JWK rsaJWKExample(); // 2. Generate EC JWK ecJWKExample(); // 3. Generate Octet (Symmetric) JWK octetJWKExample(); // 4. JWK Set (JWKS) jwksExample(); // 5. JWK Rotation keyRotationExample(); } public static void rsaJWKExample() throws Exception { System.out.println("\n--- RSA JWK ---"); // Generate RSA key pair as JWK RSAKey rsaJWK = new RSAKeyGenerator(RSAKeyLength.LEN_2048) .keyID("rsa-1") .algorithm(JWSAlgorithm.RS256) .keyUse(KeyUse.SIGNATURE) .generate(); // Get public and private JWK RSAKey publicJWK = rsaJWK.toPublicJWK(); // Convert to JSON String jsonString = rsaJWK.toJSONString(); System.out.println("RSA JWK (private): " + jsonString); System.out.println("RSA JWK (public): " + publicJWK.toJSONString()); // Parse back from JSON RSAKey parsed = RSAKey.parse(jsonString); System.out.println("Parsed key ID: " + parsed.getKeyID()); } public static void ecJWKExample() throws Exception { System.out.println("\n--- EC JWK ---"); // Generate EC key pair (P-256) ECKey ecJWK = new ECKeyGenerator(Curve.P_256) .keyID("ec-1") .keyUse(KeyUse.SIGNATURE) .algorithm(JWSAlgorithm.ES256) .generate(); System.out.println("EC JWK: " + ecJWK.toJSONString()); System.out.println("Curve: " + ecJWK.getCurve()); // Get public key ECKey publicJWK = ecJWK.toPublicJWK(); System.out.println("Public EC JWK: " + publicJWK.toJSONString()); } public static void octetJWKExample() throws Exception { System.out.println("\n--- Octet (Symmetric) JWK ---"); // Generate symmetric key for HMAC OctetSequenceKey hmacKey = new OctetSequenceKeyGenerator(256) .keyID("hmac-1") .algorithm(JWSAlgorithm.HS256) .keyUse(KeyUse.SIGNATURE) .generate(); System.out.println("HMAC JWK: " + hmacKey.toJSONString()); // Generate symmetric key for AES encryption OctetSequenceKey aesKey = new OctetSequenceKeyGenerator(256) .keyID("aes-1") .algorithm(EncryptionMethod.A256GCM.getName()) .keyUse(KeyUse.ENCRYPTION) .generate(); System.out.println("AES JWK: " + aesKey.toJSONString()); } public static void jwksExample() throws Exception { System.out.println("\n--- JWK Set (JWKS) ---"); // Create multiple keys RSAKey rsaKey = new RSAKeyGenerator(RSAKeyLength.LEN_2048) .keyID("sig-1") .algorithm(JWSAlgorithm.RS256) .keyUse(KeyUse.SIGNATURE) .generate() .toPublicJWK(); ECKey ecKey = new ECKeyGenerator(Curve.P_256) .keyID("sig-2") .algorithm(JWSAlgorithm.ES256) .keyUse(KeyUse.SIGNATURE) .generate() .toPublicJWK(); OctetSequenceKey hmacKey = new OctetSequenceKeyGenerator(256) .keyID("sig-3") .algorithm(JWSAlgorithm.HS256) .keyUse(KeyUse.SIGNATURE) .generate() .toPublicJWK(); // Create JWK set JWKSet jwkSet = new JWKSet(Arrays.asList(rsaKey, ecKey, hmacKey)); String jwksJson = jwkSet.toJSONObject(true).toJSONString(); System.out.println("JWKS: " + jwksJson); // Parse back JWKSet parsed = JWKSet.parse(jwksJson); System.out.println("Parsed key count: " + parsed.getKeys().size()); // Get key by ID JWK key = parsed.getKeyByKeyId("sig-1"); System.out.println("Found key: " + (key != null)); } public static void keyRotationExample() throws Exception { System.out.println("\n--- Key Rotation ---"); // Current active key RSAKey activeKey = new RSAKeyGenerator(RSAKeyLength.LEN_2048) .keyID("key-2024-01") .issueTime(new Date()) .build(); // Generate new key for rotation RSAKey newKey = new RSAKeyGenerator(RSAKeyLength.LEN_2048) .keyID("key-2024-02") .issueTime(new Date()) .build(); // JWKS with both keys (clients can use either) JWKSet jwkSet = new JWKSet(Arrays.asList( activeKey.toPublicJWK(), newKey.toPublicJWK() )); System.out.println("Rotation JWKS: " + jwkSet.toJSONObject(true).toJSONString()); // Simulate token signed with old key JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.RS256) .keyID("key-2024-01") .build(); System.out.println("Token signed with old key (keyID: " + header.getKeyID() + ")"); // Find appropriate key for verification JWK matchingKey = jwkSet.getKeyByKeyId(header.getKeyID()); System.out.println("Found matching key: " + (matchingKey != null)); } } Spring Boot Integration
1. Resource Server Configuration
@Configuration @EnableWebSecurity public class ResourceServerConfig { @Value("${jwt.public-key}") private String publicKeyPem; @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(authz -> authz .requestMatchers("/public/**").permitAll() .requestMatchers("/api/user").hasAuthority("SCOPE_read") .requestMatchers("/api/admin").hasAuthority("SCOPE_admin") .anyRequest().authenticated() ) .oauth2ResourceServer(oauth2 -> oauth2 .jwt(jwt -> jwt .decoder(jwtDecoder()) ) ); return http.build(); } @Bean public JwtDecoder jwtDecoder() { // Parse PEM public key try { String pem = publicKeyPem .replace("-----BEGIN PUBLIC KEY-----", "") .replace("-----END PUBLIC KEY-----", "") .replaceAll("\\s", ""); byte[] encoded = Base64.getDecoder().decode(pem); X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encoded); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); PublicKey publicKey = keyFactory.generatePublic(keySpec); return NimbusJwtDecoder.withPublicKey((RSAPublicKey) publicKey).build(); } catch (Exception e) { throw new RuntimeException("Failed to configure JWT decoder", e); } } } 2. Token Service Implementation
@Service public class TokenService { private final JWKSource<SecurityContext> jwkSource; private final JwtEncoder jwtEncoder; public TokenService() throws Exception { // Generate RSA key pair for signing RSAKey rsaKey = new RSAKeyGenerator(2048) .keyID(UUID.randomUUID().toString()) .algorithm(JWSAlgorithm.RS256) .generate(); this.jwkSource = new ImmutableJWKSet<>(new JWKSet(rsaKey)); this.jwtEncoder = new NimbusJwtEncoder(jwkSource); } public String generateToken(String subject, List<String> roles, Duration ttl) { Instant now = Instant.now(); JwtClaimsSet claims = JwtClaimsSet.builder() .issuer("https://auth.example.com") .subject(subject) .issuedAt(now) .expiresAt(now.plus(ttl)) .claim("roles", roles) .claim("scope", String.join(" ", roles)) .build(); JwsHeader header = JwsHeader.with(JwsAlgorithm.RS256) .keyId(jwkSource.get(new JWKSelector(new JWKMatcher.Builder() .keyType(KeyType.RSA) .keyUse(KeyUse.SIGNATURE) .build()), null) .get(0).getKeyID()) .build(); return this.jwtEncoder.encode(JwtEncoderParameters.from(header, claims)) .getTokenValue(); } @RestController @RequestMapping("/api") public static class TokenController { @Autowired private TokenService tokenService; @PostMapping("/auth/login") public ResponseEntity<Map<String, String>> login(@RequestBody LoginRequest request) { // Authenticate user (simplified) if ("user".equals(request.username()) && "pass".equals(request.password())) { String token = tokenService.generateToken( request.username(), List.of("read"), Duration.ofHours(1) ); return ResponseEntity.ok(Map.of( "access_token", token, "token_type", "Bearer", "expires_in", "3600" )); } return ResponseEntity.status(401).build(); } @GetMapping("/user/profile") @PreAuthorize("hasAuthority('SCOPE_read')") public UserProfile getProfile(JwtAuthenticationToken token) { String username = token.getToken().getSubject(); return new UserProfile(username, "[email protected]"); } } record LoginRequest(String username, String password) {} record UserProfile(String username, String email) {} } 3. Custom JWT Validator
@Component public class CustomJwtValidator { private final String issuer; private final String audience; private final JWKSet jwkSet; public CustomJwtValidator(@Value("${jwt.issuer}") String issuer, @Value("${jwt.audience}") String audience, @Value("${jwt.jwks-url}") String jwksUrl) throws Exception { this.issuer = issuer; this.audience = audience; // Load JWKS from URL this.jwkSet = JWKSet.load(new URL(jwksUrl)); } public Claims validateToken(String token) throws Exception { SignedJWT signedJWT = SignedJWT.parse(token); // Validate signature JWK jwk = jwkSet.getKeyByKeyId(signedJWT.getHeader().getKeyID()); if (jwk == null) { throw new SecurityException("Key not found"); } // Create verifier based on key type JWSVerifier verifier; if (jwk instanceof RSAKey rsaKey) { verifier = new RSASSAVerifier(rsaKey.toRSAPublicKey()); } else if (jwk instanceof ECKey ecKey) { verifier = new ECDSAVerifier(ecKey.toECPublicKey()); } else { throw new SecurityException("Unsupported key type"); } if (!signedJWT.verify(verifier)) { throw new SecurityException("Invalid signature"); } // Validate claims JWTClaimsSet claims = signedJWT.getJWTClaimsSet(); // Check expiration Date now = new Date(); if (claims.getExpirationTime() == null || claims.getExpirationTime().before(now)) { throw new SecurityException("Token expired"); } // Check issuer if (!issuer.equals(claims.getIssuer())) { throw new SecurityException("Invalid issuer"); } // Check audience if (claims.getAudience() != null && !claims.getAudience().contains(audience)) { throw new SecurityException("Invalid audience"); } return new Claims(claims); } public static class Claims { private final JWTClaimsSet claims; Claims(JWTClaimsSet claims) { this.claims = claims; } public String getSubject() { return claims.getSubject(); } public List<String> getRoles() { return claims.getStringListClaim("roles"); } public Map<String, Object> getAll() { return claims.getClaims(); } } } Security Best Practices
- Algorithm Selection:
- Prefer RS256, ES256, or EdDSA over HS256 for distributed systems
- Avoid "none" algorithm entirely
- Use RSA-OAEP or ECDH-ES for key encryption in JWE
- Key Management:
- Rotate keys regularly
- Use appropriate key sizes (RSA 2048+, EC P-256+)
- Store private keys securely (HSM, KMS, secure vaults)
- Token Validation:
- Always validate signature
- Check expiration time
- Verify issuer and audience
- Validate not-before time
- Claims Protection:
- Don't store sensitive data in JWT unless encrypted
- Use JWE for confidential claims
- Keep token size reasonable
- Error Handling:
- Don't leak details about validation failures
- Use generic error messages
- Log security events appropriately
Conclusion
JOSE, encompassing JWT, JWS, and JWE, provides Java developers with a comprehensive, standards-based framework for secure token handling. Whether building authentication systems, microservice authorization, or secure data exchange, these specifications offer the flexibility and security required for modern applications.
By leveraging mature Java libraries like Nimbus JOSE + JWT and JJWT, developers can implement these standards correctly while focusing on their application's specific requirements. The combination of JWS for integrity and JWE for confidentiality, along with JWK for key management, creates a complete ecosystem for secure token-based communication that's interoperable across platforms and programming languages.
Java Programming Intermediate Topics – Modifiers, Loops, Math, Methods & Projects (Related to Java Programming)
Access Modifiers in Java:
Access modifiers control how classes, variables, and methods are accessed from different parts of a program. Java provides four main access levels—public, private, protected, and default—which help protect data and control visibility in object-oriented programming.
Read more: https://macronepal.com/blog/access-modifiers-in-java-a-complete-guide/
Static Variables in Java:
Static variables belong to the class rather than individual objects. They are shared among all instances of the class and are useful for storing values that remain common across multiple objects.
Read more: https://macronepal.com/blog/static-variables-in-java-a-complete-guide/
Method Parameters in Java:
Method parameters allow values to be passed into methods so that operations can be performed using supplied data. They help make methods flexible and reusable in different parts of a program.
Read more: https://macronepal.com/blog/method-parameters-in-java-a-complete-guide/
Random Numbers in Java:
This topic explains how to generate random numbers in Java for tasks such as simulations, games, and random selections. Random numbers help create unpredictable results in programs.
Read more: https://macronepal.com/blog/random-numbers-in-java-a-complete-guide/
Math Class in Java:
The Math class provides built-in methods for performing mathematical calculations such as powers, square roots, rounding, and other advanced calculations used in Java programs.
Read more: https://macronepal.com/blog/math-class-in-java-a-complete-guide/
Boolean Operations in Java:
Boolean operations use true and false values to perform logical comparisons. They are commonly used in conditions and decision-making statements to control program flow.
Read more: https://macronepal.com/blog/boolean-operations-in-java-a-complete-guide/
Nested Loops in Java:
Nested loops are loops placed inside other loops to perform repeated operations within repeated tasks. They are useful for pattern printing, tables, and working with multi-level data.
Read more: https://macronepal.com/blog/nested-loops-in-java-a-complete-guide/
Do-While Loop in Java:
The do-while loop allows a block of code to run at least once before checking the condition. It is useful when the program must execute a task before verifying whether it should continue.
Read more: https://macronepal.com/blog/do-while-loop-in-java-a-complete-guide/
Simple Calculator Project in Java:
This project demonstrates how to create a basic calculator program using Java. It combines input handling, arithmetic operations, and conditional logic to perform simple mathematical calculations.
Read more: https://macronepal.com/blog/simple-calculator-project-in-java/