Skip to content

Commit

Permalink
Add smallrye.jwt.new-token.override-matching-claims property (#431)
Browse files Browse the repository at this point in the history
  • Loading branch information
sberyozkin authored Mar 1, 2021
1 parent 1efa854 commit dcff2aa
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 7 deletions.
1 change: 1 addition & 0 deletions doc/modules/ROOT/pages/generate-jwt.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -145,4 +145,5 @@ Smallrye JWT supports the following properties which can be used to customize th
|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.
|smallrye.jwt.new-token.audience|none|Token audience which can be used to set an `aud` (audience) claim value if this claim has not already been set.
|smallrye.jwt.new-token.override-matching-claims|false|Override the existing `iss` or `aud` claim values if `smallrye.jwt.new-token.issuer` or `smallrye.jwt.new-token.audience` properties are set.
|===
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@
* The 'aud' (audience) claim must be set if it has not already been set and the 'smallrye.jwt.new-token.audience' property is
* set.
* <p>
* Note that 'smallrye.jwt.new-token.issuer' and 'smallrye.jwt.new-token.audience' property values, if set, will override
* the existing `iss` and `aud` claim values if the 'smallrye.jwt.new-token.override-matching-claims' is set to 'true'.
* For example, it can be useful when propagating a JWT token whose 'issuer' and/or `audience` properties have to be updated
* without using this interface.
* <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
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
* JWT Token Build Utilities
*/
public class JwtBuildUtils {
private static final String NEW_TOKEN_ISSUER = "smallrye.jwt.new-token.issuer";
private static final String NEW_TOKEN_AUDIENCE = "smallrye.jwt.new-token.audience";
private static final String NEW_TOKEN_OVERRIDE_CLAIMS = "smallrye.jwt.new-token.override-matching-claims";
private static final String NEW_TOKEN_LIFESPAN = "smallrye.jwt.new-token.lifespan";

private JwtBuildUtils() {
// no-op: utility class
Expand All @@ -29,14 +33,15 @@ static void setDefaultJwtClaims(JwtClaims claims, Long tokenLifespan) {
if (!claims.hasClaim(Claims.jti.name())) {
claims.setClaim(Claims.jti.name(), UUID.randomUUID().toString());
}
if (!claims.hasClaim(Claims.iss.name())) {
String issuer = getConfigProperty("smallrye.jwt.new-token.issuer", String.class);
Boolean overrideMatchingClaims = getConfigProperty(NEW_TOKEN_OVERRIDE_CLAIMS, Boolean.class);
if (Boolean.TRUE.equals(overrideMatchingClaims) || !claims.hasClaim(Claims.iss.name())) {
String issuer = getConfigProperty(NEW_TOKEN_ISSUER, String.class);
if (issuer != null) {
claims.setIssuer(issuer);
}
}
if (!claims.hasClaim(Claims.aud.name())) {
String audience = getConfigProperty("smallrye.jwt.new-token.audience", String.class);
if (Boolean.TRUE.equals(overrideMatchingClaims) || !claims.hasClaim(Claims.aud.name())) {
String audience = getConfigProperty(NEW_TOKEN_AUDIENCE, String.class);
if (audience != null) {
claims.setAudience(audience);
}
Expand Down Expand Up @@ -88,7 +93,7 @@ private static void setExpiryClaim(JwtClaims claims, Long tokenLifespan) {
Long issuedAt = (value instanceof NumericDate) ? ((NumericDate) value).getValue() : (Long) value;
Long lifespan = tokenLifespan;
if (lifespan == null) {
lifespan = getConfigProperty("smallrye.jwt.new-token.lifespan", Long.class, 300L);
lifespan = getConfigProperty(NEW_TOKEN_LIFESPAN, Long.class, 300L);
}

claims.setExpirationTime(NumericDate.fromSeconds(issuedAt + lifespan));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public class JwtBuildConfigSource implements ConfigSource {
private static final String ENC_KEY_LOCATION_PROPERTY = "smallrye.jwt.encrypt.key.location";

boolean signingKeyAvailable = true;
boolean overrideMatchingClaims;
boolean lifespanPropertyRequired;
boolean issuerPropertyRequired;
boolean audiencePropertyRequired;
Expand All @@ -37,6 +38,9 @@ public Map<String, String> getProperties() {
if (audiencePropertyRequired) {
map.put("smallrye.jwt.new-token.audience", "https://custom-audience");
}
if (overrideMatchingClaims) {
map.put("smallrye.jwt.new-token.override-matching-claims", String.valueOf(overrideMatchingClaims));
}
return map;
}

Expand Down Expand Up @@ -80,6 +84,11 @@ public Set<String> getPropertyNames() {
"smallrye.jwt.encrypt.key-location",
"smallrye.jwt.new-token.lifespan",
"smallrye.jwt.new-token.issuer",
"smallrye.jwt.new-token.audience"));
"smallrye.jwt.new-token.audience",
"smallrye.jwt.new-token.override-matching-claims"));
}

public void setOverrideMatchingClaims(boolean override) {
overrideMatchingClaims = override;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@

import org.eclipse.microprofile.config.ConfigProvider;
import org.eclipse.microprofile.config.spi.ConfigSource;
import org.eclipse.microprofile.jwt.Claims;
import org.eclipse.microprofile.jwt.JsonWebToken;
import org.jose4j.base64url.Base64Url;
import org.jose4j.json.JsonUtil;
Expand All @@ -49,13 +50,13 @@
import org.jose4j.jwk.JsonWebKey;
import org.jose4j.jws.JsonWebSignature;
import org.jose4j.jwt.JwtClaims;
import org.jose4j.jwt.MalformedClaimException;
import org.jose4j.jwt.NumericDate;
import org.jose4j.keys.EllipticCurves;
import org.junit.Assert;
import org.junit.Test;

import io.smallrye.jwt.algorithm.SignatureAlgorithm;
// import io.smallrye.jwt.auth.principal.DefaultJWTCallerPrincipal;
import io.smallrye.jwt.util.KeyUtils;

public class JwtSignTest {
Expand Down Expand Up @@ -99,6 +100,40 @@ public void testEnhanceAndResignToken() throws Exception {
Assert.assertEquals("https://localhost:8081", claims.getAudience().get(0));
}

@Test
public void testEnhanceAndResignTokenWithConfiguredIssuerAndAudUsed() throws Exception {
JsonWebToken token = new TestJsonWebToken(signAndVerifyClaims());

Assert.assertEquals("https://default-issuer", token.getIssuer());
Assert.assertEquals(1, token.getAudience().size());
Assert.assertEquals("https://localhost:8081", token.getAudience().iterator().next());

JwtBuildConfigSource configSource = getConfigSource();
configSource.setIssuerPropertyRequired(true);
configSource.setAudiencePropertyRequired(true);
configSource.setOverrideMatchingClaims(true);

try {
String jwt = Jwt.claims(token).claim("newClaim", "new-value").sign();

// verify
JsonWebSignature jws = getVerifiedJws(jwt);
JwtClaims claims = JwtClaims.parse(jws.getPayload());
Assert.assertEquals(7, 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://custom-issuer", claims.getIssuer());
Assert.assertEquals(1, claims.getAudience().size());
Assert.assertEquals("https://custom-audience", claims.getAudience().get(0));
} finally {
configSource.setIssuerPropertyRequired(false);
configSource.setAudiencePropertyRequired(false);
configSource.setOverrideMatchingClaims(false);
}
}

private JwtClaims signAndVerifyClaims() throws Exception {
return signAndVerifyClaims(null, null, null);
}
Expand Down Expand Up @@ -623,6 +658,13 @@ public Set<String> getClaimNames() {
@SuppressWarnings("unchecked")
@Override
public <T> T getClaim(String claimName) {
if (Claims.aud.name().equals(claimName)) {
try {
return (T) new HashSet<>(claims.getAudience());
} catch (MalformedClaimException ex) {
throw new RuntimeException(ex);
}
}
return (T) claims.getClaimValue(claimName);
}

Expand Down

0 comments on commit dcff2aa

Please sign in to comment.