Skip to content

Commit

Permalink
Minor JWT build improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
sberyozkin committed May 25, 2020
1 parent bbf4fde commit b848ef3
Show file tree
Hide file tree
Showing 11 changed files with 216 additions and 53 deletions.
22 changes: 17 additions & 5 deletions doc/modules/ROOT/pages/generate-jwt.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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.
Expand Down Expand Up @@ -102,6 +112,8 @@ Smallrye JWT supports the following properties which can be used to customize th
[cols="<m,<m,<2",options="header"]
|===
|Property Name|Default|Description
|smallrye.jwt.encrypt.key-location|none|Config property allows the location of a key to be specified which will be used to encrypt the claims or inner JWT.
|smallrye.jwt.sign.key-location|none|Config property allows the location of a private key to be specified which will be used to sign the claims of a JWT.
|smallrye.jwt.encrypt.key-location|none|Location of a public key which will be used to encrypt the claims or inner JWT when a no-argument encrypt() method is called.
|smallrye.jwt.sign.key-location|none|Location of a private key which will be used to sign the claims when either a no-argument sign() or innerSign() method is called.
|smallrye.jwt.new-token.lifespan|300|Token lifespan in seconds which will be used to calculate an `exp` (expiry) claim value if this claim has not already been set.
|smallrye.jwt.new-token.issuer|none|Token issuer which can be used to set an `iss` (issuer) claim value if this claim has not already been set.
|===
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@
* JwtClaimsBuilder implementations must set the 'iat' (issued at time), 'exp' (expiration time)
* and 'jit' (unique token identifier) claims unless they have already been set.
* <p>
* 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.
* <p>
* JwtClaimsBuilder implementations must set the 'iss' (issuer) claim if has not already been set and
* the 'smallrye.jwt.new-token.issuer' property is set.
* <p>
* Note that JwtClaimsBuilder implementations are not expected to be thread-safe.
*
* @see <a href="https://tools.ietf.org/html/rfc7519">RFC7515</a>
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
}
Expand All @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
}
Expand All @@ -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;
}
Expand All @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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> T getConfigProperty(String name, Class<T> cls) {
return getConfigProperty(name, cls, null);
}

static <T> T getConfigProperty(String name, Class<T> cls, T defaultValue) {
return ConfigProvider.getConfig().getOptionalValue(name, cls).orElse(defaultValue);
}

static String readClaimsAndSignWithJwk(JsonWebKey jwk, Map<String, Object> headers, String jwtLocation) {
Expand Down Expand Up @@ -353,19 +364,25 @@ static Map<String, Object> 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));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
public class JwtBuildConfigSource implements ConfigSource {

boolean signingKeyAvailable = true;
boolean lifespanPropertyRequired;
boolean issuerPropertyRequired;
String encryptionKeyLocation = "/publicKey.pem";
String signingKeyLocation = "/privateKey.pem";

Expand All @@ -18,6 +20,12 @@ public Map<String, String> 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;
}

Expand All @@ -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;
}
}
Loading

0 comments on commit b848ef3

Please sign in to comment.