Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add smallrye.jwt.new-token.override-matching-claims property #431

Merged
merged 2 commits into from
Mar 1, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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