From b848ef3e3cecee3e5b9f5a3823f0ca5686319dfc Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Fri, 22 May 2020 16:10:20 +0100 Subject: [PATCH] Minor JWT build improvements --- doc/modules/ROOT/pages/generate-jwt.adoc | 22 ++++++-- .../smallrye/jwt/build/JwtClaimsBuilder.java | 9 ++- .../jwt/build/JwtEncryptionBuilder.java | 54 +++++++++++++++++- .../jwt/build/JwtSignatureBuilder.java | 41 ++++++++++++-- .../jwt/build/impl/JwtClaimsBuilderImpl.java | 6 +- .../jwt/build/impl/JwtEncryptionImpl.java | 10 ++-- .../jwt/build/impl/JwtSigningUtils.java | 33 ++++++++--- .../jwt/build/JwtBuildConfigSource.java | 16 ++++++ .../io/smallrye/jwt/build/JwtEncryptTest.java | 12 ++-- .../jwt/build/JwtSignEncryptTest.java | 10 ++-- .../io/smallrye/jwt/build/JwtSignTest.java | 56 ++++++++++++++----- 11 files changed, 216 insertions(+), 53 deletions(-) diff --git a/doc/modules/ROOT/pages/generate-jwt.adoc b/doc/modules/ROOT/pages/generate-jwt.adoc index bffda908..c4837c01 100644 --- a/doc/modules/ROOT/pages/generate-jwt.adoc +++ b/doc/modules/ROOT/pages/generate-jwt.adoc @@ -34,11 +34,21 @@ JwtClaimsBuilder builder2 = Jwt.claims("/tokenClaims.json"); // Builder created from a map of claims JwtClaimsBuilder builder3 = Jwt.claims(Collections.singletonMap("customClaim", "custom-value")); + +// Builder created from JsonObject +JsonObject userName = Json.createObjectBuilder().add("username", "Alice").build(); +JsonObject userAddress = Json.createObjectBuilder().add("city", "someCity").add("street", "someStreet").build(); +JsonObject json = Json.createObjectBuilder(userName).add("address", userAddress).build(); +JwtClaimsBuilder builder4 = Jwt.claims(json); + +// Builder created from JsonWebToken +@Inject JsonWebToken token; +JwtClaimsBuilder builder5 = Jwt.claims(token); ---- The API is fluent so the builder initialization can be done as part of the fluent API sequence. The builder will also -set `iat (issued at) to the current time, `exp`(expires at) to 5 minutes away from the current time and `jti` -(unique token identifier) claims if they have not already been set, so one can skip setting them when possible. +set `iat (issued at) claim to the current time, `exp`(expires at) claim to a sum of the `iat` claim and `smallrye.jwt.new-token.lifespan` +property values and `jti` (unique token identifier) claim if they have not already been set, so one can skip setting them when possible. The next step is to decide how to secure the claims. @@ -56,7 +66,7 @@ import io.smallrye.jwt.build.Jwt; String jwt1 = Jwt.claims("/tokenClaims.json").sign(); // Set the headers and sign the claims with an RSA private key loaded in the code (the implementation of this method is omitted). Note a 'jws()' transition to a 'JwtSignatureBuilder'. -String jwt2 = Jwt.claims("/tokenClaims.json").jws().signatureKeyId("kid1").header("custom-header", "custom-value").sign(getPrivateKey()); +String jwt2 = Jwt.claims("/tokenClaims.json").jws().keyId("kid1").header("custom-header", "custom-value").sign(getPrivateKey()); ---- Note the `alg` (algorithm) header is set to `RS256` by default. @@ -102,6 +112,8 @@ Smallrye JWT supports the following properties which can be used to customize th [cols=" + * By default the 'iat' claim is set to the current time in seconds and the 'exp' claim is set by adding a default token + * lifespan value of 5 minutes to the 'iat' claim value. The 'smallrye.jwt.new-token.lifespan' property can be used to + * customize a new token lifespan and its 'exp' claim values. + *

+ * JwtClaimsBuilder implementations must set the 'iss' (issuer) claim if has not already been set and + * the 'smallrye.jwt.new-token.issuer' property is set. + *

* Note that JwtClaimsBuilder implementations are not expected to be thread-safe. * * @see RFC7515 @@ -63,7 +70,7 @@ public interface JwtClaimsBuilder extends JwtSignature { /** * Set an expiry 'exp' claim * - * @param expiredAt the expiry time + * @param expiredAt the expiry time in seconds * @return JwtClaimsBuilder */ JwtClaimsBuilder expiresAt(long expiredAt); diff --git a/implementation/src/main/java/io/smallrye/jwt/build/JwtEncryptionBuilder.java b/implementation/src/main/java/io/smallrye/jwt/build/JwtEncryptionBuilder.java index 9e10553c..14b76bc8 100644 --- a/implementation/src/main/java/io/smallrye/jwt/build/JwtEncryptionBuilder.java +++ b/implementation/src/main/java/io/smallrye/jwt/build/JwtEncryptionBuilder.java @@ -22,27 +22,75 @@ public interface JwtEncryptionBuilder extends JwtEncryption { * Note that only 'RSA-OAEP-256' (default), 'ECDH-ES+A256KW' and 'A256KW' algorithms must be supported. * A key of size 2048 bits or larger MUST be used with 'RSA-OAEP-256' algorithm. * + * @since 2.1.3 + * * @param algorithm the key encryption algorithm * @return JwtEncryptionBuilder */ - JwtEncryptionBuilder keyEncryptionAlgorithm(KeyEncryptionAlgorithm algorithm); + JwtEncryptionBuilder keyAlgorithm(KeyEncryptionAlgorithm algorithm); + + /** + * Set an 'alg' key encryption algorithm. + * Note that only 'RSA-OAEP-256' (default), 'ECDH-ES+A256KW' and 'A256KW' algorithms must be supported. + * A key of size 2048 bits or larger MUST be used with 'RSA-OAEP-256' algorithm. + * + * @deprecated Use {@link #keyAlgorithm} + * + * @param algorithm the key encryption algorithm + * @return JwtEncryptionBuilder + */ + @Deprecated + default JwtEncryptionBuilder keyEncryptionAlgorithm(KeyEncryptionAlgorithm algorithm) { + return keyAlgorithm(algorithm); + } /** * Set an 'enc' content encryption algorithm. * Note that only 'A256GCM' (default) and 'A128CBC-HS256' algorithms must be supported. + * + * @since 2.1.3 + * + * @param algorithm the content encryption algorithm + * @return JwtEncryptionBuilder + */ + JwtEncryptionBuilder contentAlgorithm(ContentEncryptionAlgorithm algorithm); + + /** + * Set an 'enc' content encryption algorithm. + * Note that only 'A256GCM' (default) and 'A128CBC-HS256' algorithms must be supported. + * + * @deprecated Use {@link #contentAlgorithm} * * @param algorithm the content encryption algorithm * @return JwtEncryptionBuilder */ - JwtEncryptionBuilder contentEncryptionAlgorithm(ContentEncryptionAlgorithm algorithm); + @Deprecated + default JwtEncryptionBuilder contentEncryptionAlgorithm(ContentEncryptionAlgorithm algorithm) { + return contentAlgorithm(algorithm); + } + + /** + * Set a 'kid' key encryption key id. + * + * @since 2.1.3 + * + * @param keyId the key id + * @return JwtEncryptionBuilder + */ + JwtEncryptionBuilder keyId(String keyId); /** * Set a 'kid' key encryption key id. + * + * @deprecated Use {@link #keyId} * * @param keyId the key id * @return JwtEncryptionBuilder */ - JwtEncryptionBuilder keyEncryptionKeyId(String keyId); + @Deprecated + default JwtEncryptionBuilder keyEncryptionKeyId(String keyId) { + return keyId(keyId); + } /** * Custom JWT encryption header. diff --git a/implementation/src/main/java/io/smallrye/jwt/build/JwtSignatureBuilder.java b/implementation/src/main/java/io/smallrye/jwt/build/JwtSignatureBuilder.java index bac9931a..2d0ff830 100644 --- a/implementation/src/main/java/io/smallrye/jwt/build/JwtSignatureBuilder.java +++ b/implementation/src/main/java/io/smallrye/jwt/build/JwtSignatureBuilder.java @@ -22,19 +22,50 @@ public interface JwtSignatureBuilder extends JwtSignature { /** * Set a signature algorithm. * Note that only 'RS256' (default), 'ES256' and 'HS256' algorithms must be supported. - * + * + * @since 2.1.3 + * * @param algorithm the signature algorithm * @return JwtSignatureBuilder */ - JwtSignatureBuilder signatureAlgorithm(SignatureAlgorithm algorithm); + JwtSignatureBuilder algorithm(SignatureAlgorithm algorithm); /** - * Set a 'kid' signature key id + * Set a signature algorithm. + * Note that only 'RS256' (default), 'ES256' and 'HS256' algorithms must be supported. + * + * @deprecated Use {@link #algorithm} * + * @param algorithm the signature algorithm + * @return JwtSignatureBuilder + */ + @Deprecated + default JwtSignatureBuilder signatureAlgorithm(SignatureAlgorithm algorithm) { + return algorithm(algorithm); + } + + /** + * Set a 'kid' signature key id. + * + * @since 2.1.3 + * + * @param keyId the key id + * @return JwtSignatureBuilder + */ + JwtSignatureBuilder keyId(String keyId); + + /** + * Set a 'kid' signature key id. + * + * @deprecated Use {@link #keyId} + * * @param keyId the key id * @return JwtSignatureBuilder */ - JwtSignatureBuilder signatureKeyId(String keyId); + @Deprecated + default JwtSignatureBuilder signatureKeyId(String keyId) { + return keyId(keyId); + } /** * Custom JWT signature header. @@ -72,6 +103,8 @@ public interface JwtSignatureBuilder extends JwtSignature { * * If no "smallrye.jwt.sign.key-location" property is set then an insecure inner JWT with a "none" algorithm * has to be created before being encrypted. + * + * Note: Support for the 'none' algorithm is deprecated. * * @return JwtEncryptionBuilder * @throws JwtSignatureException the exception if the inner JWT signing operation has failed diff --git a/implementation/src/main/java/io/smallrye/jwt/build/impl/JwtClaimsBuilderImpl.java b/implementation/src/main/java/io/smallrye/jwt/build/impl/JwtClaimsBuilderImpl.java index bddf975b..361987b6 100644 --- a/implementation/src/main/java/io/smallrye/jwt/build/impl/JwtClaimsBuilderImpl.java +++ b/implementation/src/main/java/io/smallrye/jwt/build/impl/JwtClaimsBuilderImpl.java @@ -162,7 +162,7 @@ public JwtSignatureBuilder jws() { @Override public JwtSignatureBuilder header(String name, Object value) { if ("alg".equals(name)) { - return signatureAlgorithm(toSignatureAlgorithm((String) value)); + return algorithm(toSignatureAlgorithm((String) value)); } else { headers.put(name, value); return this; @@ -173,7 +173,7 @@ public JwtSignatureBuilder header(String name, Object value) { * {@inheritDoc} */ @Override - public JwtSignatureBuilder signatureAlgorithm(SignatureAlgorithm algorithm) { + public JwtSignatureBuilder algorithm(SignatureAlgorithm algorithm) { headers.put("alg", algorithm.name()); return this; } @@ -182,7 +182,7 @@ public JwtSignatureBuilder signatureAlgorithm(SignatureAlgorithm algorithm) { * {@inheritDoc} */ @Override - public JwtSignatureBuilder signatureKeyId(String keyId) { + public JwtSignatureBuilder keyId(String keyId) { headers.put("kid", keyId); return this; } diff --git a/implementation/src/main/java/io/smallrye/jwt/build/impl/JwtEncryptionImpl.java b/implementation/src/main/java/io/smallrye/jwt/build/impl/JwtEncryptionImpl.java index c51897fa..c58f3ff5 100644 --- a/implementation/src/main/java/io/smallrye/jwt/build/impl/JwtEncryptionImpl.java +++ b/implementation/src/main/java/io/smallrye/jwt/build/impl/JwtEncryptionImpl.java @@ -81,9 +81,9 @@ public String encrypt() throws JwtSignatureException { @Override public JwtEncryptionBuilder header(String name, Object value) { if ("alg".equals(name)) { - return keyEncryptionAlgorithm(toKeyEncryptionAlgorithm((String) value)); + return keyAlgorithm(toKeyEncryptionAlgorithm((String) value)); } else if ("enc".equals(name)) { - return contentEncryptionAlgorithm(toContentEncryptionAlgorithm((String) value)); + return contentAlgorithm(toContentEncryptionAlgorithm((String) value)); } else { headers.put(name, value); return this; @@ -94,7 +94,7 @@ public JwtEncryptionBuilder header(String name, Object value) { * {@inheritDoc} */ @Override - public JwtEncryptionBuilder keyEncryptionAlgorithm(KeyEncryptionAlgorithm algorithm) { + public JwtEncryptionBuilder keyAlgorithm(KeyEncryptionAlgorithm algorithm) { headers.put("alg", algorithm.getAlgorithm()); return this; } @@ -103,7 +103,7 @@ public JwtEncryptionBuilder keyEncryptionAlgorithm(KeyEncryptionAlgorithm algori * {@inheritDoc} */ @Override - public JwtEncryptionBuilder contentEncryptionAlgorithm(ContentEncryptionAlgorithm algorithm) { + public JwtEncryptionBuilder contentAlgorithm(ContentEncryptionAlgorithm algorithm) { headers.put("enc", algorithm.getAlgorithm()); return this; } @@ -112,7 +112,7 @@ public JwtEncryptionBuilder contentEncryptionAlgorithm(ContentEncryptionAlgorith * {@inheritDoc} */ @Override - public JwtEncryptionBuilder keyEncryptionKeyId(String keyId) { + public JwtEncryptionBuilder keyId(String keyId) { headers.put("kid", keyId); return this; } diff --git a/implementation/src/main/java/io/smallrye/jwt/build/impl/JwtSigningUtils.java b/implementation/src/main/java/io/smallrye/jwt/build/impl/JwtSigningUtils.java index 66de1eb3..17cd861e 100644 --- a/implementation/src/main/java/io/smallrye/jwt/build/impl/JwtSigningUtils.java +++ b/implementation/src/main/java/io/smallrye/jwt/build/impl/JwtSigningUtils.java @@ -13,7 +13,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; -import java.util.NoSuchElementException; import java.util.stream.Collectors; import javax.crypto.SecretKey; @@ -208,12 +207,24 @@ static void setDefaultJwtClaims(JwtClaims claims) { if (!claims.hasClaim(Claims.iat.name())) { claims.setIssuedAt(NumericDate.fromSeconds(currentTimeInSecs)); } - if (!claims.hasClaim(Claims.exp.name())) { - claims.setExpirationTime(NumericDate.fromSeconds(currentTimeInSecs() + 300)); - } + setExpiryClaim(claims); if (!claims.hasClaim(Claims.jti.name())) { claims.setGeneratedJwtId(); } + if (!claims.hasClaim(Claims.iss.name())) { + String issuer = getConfigProperty("smallrye.jwt.new-token.issuer", String.class); + if (issuer != null) { + claims.setIssuer(issuer); + } + } + } + + static T getConfigProperty(String name, Class cls) { + return getConfigProperty(name, cls, null); + } + + static T getConfigProperty(String name, Class cls, T defaultValue) { + return ConfigProvider.getConfig().getOptionalValue(name, cls).orElse(defaultValue); } static String readClaimsAndSignWithJwk(JsonWebKey jwk, Map headers, String jwtLocation) { @@ -353,19 +364,25 @@ static Map kidToMap(String keyId) { } static Key getSigningKeyFromConfig(String kid) { - try { - String keyLocation = ConfigProvider.getConfig().getValue("smallrye.jwt.sign.key-location", String.class); + String keyLocation = getConfigProperty("smallrye.jwt.sign.key-location", String.class); + if (keyLocation != null) { try { return KeyUtils.readSigningKey(keyLocation, kid); } catch (Exception ex) { throw ImplMessages.msg.signingKeyCanNotBeLoadedFromLocation(keyLocation); - // TODO: try JWK(S) as well } - } catch (NoSuchElementException ex) { + } else { throw ImplMessages.msg.signKeyLocationNotConfigured(); } } + static void setExpiryClaim(JwtClaims claims) { + if (!claims.hasClaim(Claims.exp.name())) { + Long lifespan = getConfigProperty("smallrye.jwt.new-token.lifespan", Long.class, 300L); + claims.setExpirationTime(NumericDate.fromSeconds(currentTimeInSecs() + lifespan)); + } + } + static JwtClaims parseJwtClaims(String jwtLocation) { try { return JwtClaims.parse(readJsonContent(jwtLocation)); diff --git a/implementation/src/test/java/io/smallrye/jwt/build/JwtBuildConfigSource.java b/implementation/src/test/java/io/smallrye/jwt/build/JwtBuildConfigSource.java index 0ea8110a..c6a0556d 100644 --- a/implementation/src/test/java/io/smallrye/jwt/build/JwtBuildConfigSource.java +++ b/implementation/src/test/java/io/smallrye/jwt/build/JwtBuildConfigSource.java @@ -8,6 +8,8 @@ public class JwtBuildConfigSource implements ConfigSource { boolean signingKeyAvailable = true; + boolean lifespanPropertyRequired; + boolean issuerPropertyRequired; String encryptionKeyLocation = "/publicKey.pem"; String signingKeyLocation = "/privateKey.pem"; @@ -18,6 +20,12 @@ public Map getProperties() { map.put("smallrye.jwt.sign.key-location", signingKeyLocation); } map.put("smallrye.jwt.encrypt.key-location", encryptionKeyLocation); + if (lifespanPropertyRequired) { + map.put("smallrye.jwt.new-token.lifespan", "2000"); + } + if (issuerPropertyRequired) { + map.put("smallrye.jwt.new-token.issuer", "https://custom-issuer"); + } return map; } @@ -42,4 +50,12 @@ public void setEncryptionKeyLocation(String location) { public void setSigningKeyLocation(String location) { this.signingKeyLocation = location; } + + void setLifespanPropertyRequired(boolean lifespanPropertyRequired) { + this.lifespanPropertyRequired = lifespanPropertyRequired; + } + + public void setIssuerPropertyRequired(boolean issuerPropertyRequired) { + this.issuerPropertyRequired = issuerPropertyRequired; + } } diff --git a/implementation/src/test/java/io/smallrye/jwt/build/JwtEncryptTest.java b/implementation/src/test/java/io/smallrye/jwt/build/JwtEncryptTest.java index 2b31253c..8474ffd1 100644 --- a/implementation/src/test/java/io/smallrye/jwt/build/JwtEncryptTest.java +++ b/implementation/src/test/java/io/smallrye/jwt/build/JwtEncryptTest.java @@ -45,7 +45,7 @@ public void testEncryptWithRsaPublicKey() throws Exception { String jweCompact = Jwt.claims() .claim("customClaim", "custom-value") .jwe() - .keyEncryptionKeyId("key-enc-key-id") + .keyId("key-enc-key-id") .encrypt(); checkJweHeaders(jweCompact); @@ -61,7 +61,7 @@ public void testEncryptWithRsaPublicKeyLocation() throws Exception { String jweCompact = Jwt.claims() .claim("customClaim", "custom-value") .jwe() - .keyEncryptionKeyId("key-enc-key-id") + .keyId("key-enc-key-id") .encrypt("publicKey.pem"); checkJweHeaders(jweCompact); @@ -92,7 +92,7 @@ public void testEncryptWithEcKey() throws Exception { String jweCompact = Jwt.claims() .claim("customClaim", "custom-value") .jwe() - .keyEncryptionKeyId("key-enc-key-id") + .keyId("key-enc-key-id") .encrypt(jwk.getECPublicKey()); checkJweHeaders(jweCompact, "ECDH-ES+A256KW", 4); @@ -109,8 +109,8 @@ public void testEncryptWithEcKeyAndA128CBCHS256() throws Exception { String jweCompact = Jwt.claims() .claim("customClaim", "custom-value") .jwe() - .keyEncryptionKeyId("key-enc-key-id") - .contentEncryptionAlgorithm(ContentEncryptionAlgorithm.A128CBC_HS256) + .keyId("key-enc-key-id") + .contentAlgorithm(ContentEncryptionAlgorithm.A128CBC_HS256) .encrypt(jwk.getECPublicKey()); checkJweHeaders(jweCompact, "ECDH-ES+A256KW", "A128CBC-HS256", 4); @@ -126,7 +126,7 @@ public void testEncryptWithSecretKey() throws Exception { String jweCompact = Jwt.claims() .claim("customClaim", "custom-value") .jwe() - .keyEncryptionKeyId("key-enc-key-id") + .keyId("key-enc-key-id") .encrypt(createSecretKey()); checkJweHeaders(jweCompact, "A256KW", 3); diff --git a/implementation/src/test/java/io/smallrye/jwt/build/JwtSignEncryptTest.java b/implementation/src/test/java/io/smallrye/jwt/build/JwtSignEncryptTest.java index 8a475fb7..1657f6cc 100644 --- a/implementation/src/test/java/io/smallrye/jwt/build/JwtSignEncryptTest.java +++ b/implementation/src/test/java/io/smallrye/jwt/build/JwtSignEncryptTest.java @@ -63,9 +63,9 @@ public void testInnerSignAndEncryptWithPemRsaPublicKeyWithHeaders() throws Excep String jweCompact = Jwt.claims() .claim("customClaim", "custom-value") .jws() - .signatureKeyId("sign-key-id") + .keyId("sign-key-id") .innerSign() - .keyEncryptionKeyId("key-enc-key-id") + .keyId("key-enc-key-id") .encrypt(); checkJweHeaders(jweCompact, "RSA-OAEP-256", "key-enc-key-id"); @@ -93,7 +93,7 @@ public void testInnerSignNoneAndEncryptWithPemRsaPublicKey() throws Exception { .claim("customClaim", "custom-value") .jws() .innerSign() - .keyEncryptionKeyId("key-enc-key-id") + .keyId("key-enc-key-id") .encrypt(); } finally { configSource.setSigningKeyAvailability(true); @@ -123,9 +123,9 @@ public void testInnerSignAndEncryptWithJwkRsaPublicKey() throws Exception { jweCompact = Jwt.claims() .claim("customClaim", "custom-value") .jws() - .signatureKeyId("sign-key-id") + .keyId("sign-key-id") .innerSign() - .keyEncryptionKeyId("key1") + .keyId("key1") .encrypt(); } finally { configSource.setEncryptionKeyLocation("/publicKey.pem"); diff --git a/implementation/src/test/java/io/smallrye/jwt/build/JwtSignTest.java b/implementation/src/test/java/io/smallrye/jwt/build/JwtSignTest.java index 2ba2e3b2..903a5bbc 100644 --- a/implementation/src/test/java/io/smallrye/jwt/build/JwtSignTest.java +++ b/implementation/src/test/java/io/smallrye/jwt/build/JwtSignTest.java @@ -40,6 +40,7 @@ import org.jose4j.jwk.JsonWebKey; import org.jose4j.jws.JsonWebSignature; import org.jose4j.jwt.JwtClaims; +import org.jose4j.jwt.NumericDate; import org.jose4j.keys.EllipticCurves; import org.junit.Assert; import org.junit.Test; @@ -54,6 +55,19 @@ public void testSignClaims() throws Exception { signAndVerifyClaims(); } + @Test + public void testSignClaimsCustomExpAndIssuer() throws Exception { + JwtBuildConfigSource configSource = getConfigSource(); + try { + configSource.setLifespanPropertyRequired(true); + configSource.setIssuerPropertyRequired(true); + signAndVerifyClaims(true, "https://custom-issuer"); + } finally { + configSource.setLifespanPropertyRequired(false); + configSource.setIssuerPropertyRequired(false); + } + } + @Test public void testEnhanceAndResignToken() throws Exception { JsonWebToken token = new DefaultJWTCallerPrincipal(signAndVerifyClaims()); @@ -63,15 +77,23 @@ public void testEnhanceAndResignToken() throws Exception { // verify JsonWebSignature jws = getVerifiedJws(jwt); JwtClaims claims = JwtClaims.parse(jws.getPayload()); - Assert.assertEquals(5, claims.getClaimsMap().size()); + Assert.assertEquals(6, claims.getClaimsMap().size()); checkDefaultClaimsAndHeaders(getJwsHeaders(jwt, 2), claims); Assert.assertEquals("custom-value", claims.getClaimValue("customClaim")); Assert.assertEquals("new-value", claims.getClaimValue("newClaim")); + Assert.assertEquals("https://default-issuer", claims.getIssuer()); } private JwtClaims signAndVerifyClaims() throws Exception { + return signAndVerifyClaims(false, null); + } + + private JwtClaims signAndVerifyClaims(boolean lifeSpanRequired, String issuer) throws Exception { JwtClaimsBuilder builder = Jwt.claims().claim("customClaim", "custom-value"); + if (issuer == null) { + builder.issuer("https://default-issuer"); + } String jsonBeforeSign = builder.json(); String jwt = builder.sign(getPrivateKey()); String jsonAfterSign = builder.json(); @@ -79,10 +101,13 @@ private JwtClaims signAndVerifyClaims() throws Exception { JsonWebSignature jws = getVerifiedJws(jwt); Assert.assertEquals(jsonAfterSign, jws.getPayload()); JwtClaims claims = JwtClaims.parse(jws.getPayload()); - Assert.assertEquals(4, claims.getClaimsMap().size()); - checkDefaultClaimsAndHeaders(getJwsHeaders(jwt, 2), claims); + Assert.assertEquals(5, claims.getClaimsMap().size()); + checkDefaultClaimsAndHeaders(getJwsHeaders(jwt, 2), claims, "RS256", lifeSpanRequired ? 2000 : 300); Assert.assertEquals("custom-value", claims.getClaimValue("customClaim")); + if (issuer != null) { + Assert.assertEquals((issuer == null ? "https://default-issuer" : issuer), claims.getIssuer()); + } return claims; } @@ -170,7 +195,7 @@ public void testSignClaimsAndHeaders() throws Exception { .issuer("https://issuer.com") .jws() .header("customHeader", "custom-header-value") - .signatureKeyId("key-id") + .keyId("key-id") .sign(getPrivateKey()); JsonWebSignature jws = getVerifiedJws(jwt); @@ -205,13 +230,18 @@ private static JsonWebSignature getVerifiedJws(String jwt, Key key) throws Excep } private static void checkDefaultClaimsAndHeaders(Map headers, JwtClaims claims) throws Exception { - checkDefaultClaimsAndHeaders(headers, claims, "RS256"); + checkDefaultClaimsAndHeaders(headers, claims, "RS256", 300); } - private static void checkDefaultClaimsAndHeaders(Map headers, JwtClaims claims, String algo) + private static void checkDefaultClaimsAndHeaders(Map headers, JwtClaims claims, String algo, + long expectedLifespan) throws Exception { - Assert.assertNotNull(claims.getIssuedAt()); - Assert.assertNotNull(claims.getExpirationTime()); + NumericDate iat = claims.getIssuedAt(); + Assert.assertNotNull(iat); + NumericDate exp = claims.getExpirationTime(); + Assert.assertNotNull(exp); + long tokenLifespan = exp.getValue() - iat.getValue(); + Assert.assertTrue(tokenLifespan >= expectedLifespan && tokenLifespan <= expectedLifespan + 2); Assert.assertNotNull(claims.getJwtId()); Assert.assertEquals(algo, headers.get("alg")); Assert.assertEquals("JWT", headers.get("typ")); @@ -294,7 +324,7 @@ private void doTestSignExistingClaims(String jsonResName) throws Exception { JwtClaims claims = JwtClaims.parse(jws.getPayload()); Assert.assertEquals(9, claims.getClaimsMap().size()); - checkDefaultClaimsAndHeaders(getJwsHeaders(jwt, 2), claims); + checkDefaultClaimsAndHeaders(getJwsHeaders(jwt, 2), claims, "RS256", 1000); Assert.assertEquals("https://server.example.com", claims.getIssuer()); Assert.assertEquals("a-123", claims.getClaimValue("jti")); @@ -319,7 +349,7 @@ public void testSignClaimsEllipticCurve() throws Exception { JwtClaims claims = JwtClaims.parse(jws.getPayload()); Assert.assertEquals(4, claims.getClaimsMap().size()); - checkDefaultClaimsAndHeaders(getJwsHeaders(jwt, 2), claims, "ES256"); + checkDefaultClaimsAndHeaders(getJwsHeaders(jwt, 2), claims, "ES256", 300); Assert.assertEquals("custom-value", claims.getClaimValue("customClaim")); } @@ -340,7 +370,7 @@ public void testSignClaimsSymmetricKey() throws Exception { JwtClaims claims = JwtClaims.parse(jws.getPayload()); Assert.assertEquals(4, claims.getClaimsMap().size()); - checkDefaultClaimsAndHeaders(getJwsHeaders(jwt, 2), claims, "HS256"); + checkDefaultClaimsAndHeaders(getJwsHeaders(jwt, 2), claims, "HS256", 300); Assert.assertEquals("custom-value", claims.getClaimValue("customClaim")); } @@ -354,7 +384,7 @@ public void testSignClaimsJwkSymmetricKey() throws Exception { jwt = Jwt.claims() .claim("customClaim", "custom-value") .jws() - .signatureKeyId("secretkey1") + .keyId("secretkey1") .sign(); } finally { configSource.setSigningKeyLocation("/privateKey.pem"); @@ -366,7 +396,7 @@ public void testSignClaimsJwkSymmetricKey() throws Exception { Assert.assertEquals(4, claims.getClaimsMap().size()); Map headers = getJwsHeaders(jwt, 3); - checkDefaultClaimsAndHeaders(headers, claims, "HS256"); + checkDefaultClaimsAndHeaders(headers, claims, "HS256", 300); Assert.assertEquals("secretkey1", headers.get("kid")); Assert.assertEquals("custom-value", claims.getClaimValue("customClaim")); }