From 05e2835fefac517100388c36c6e1c527bee0bd55 Mon Sep 17 00:00:00 2001 From: artem-v Date: Sun, 26 Oct 2025 15:15:20 +0200 Subject: [PATCH 1/4] Get rid of jjwt, use auth0.java-jwt instead --- pom.xml | 29 +++++++------------ .../tokens/jwt/JsonwebtokenResolverTests.java | 6 ++-- tokens/pom.xml | 16 ++++------ .../tokens/jwt/JsonwebtokenResolver.java | 21 ++++++++------ .../security/tokens/jwt/JwksKeyLocator.java | 15 ++++------ 5 files changed, 36 insertions(+), 51 deletions(-) diff --git a/pom.xml b/pom.xml index 01eac55..f56d7bf 100644 --- a/pom.xml +++ b/pom.xml @@ -1,5 +1,7 @@ - + 4.0.0 @@ -42,9 +44,10 @@ 5.1.0 2.19.2 1.7.36 - 0.12.6 + 4.5.0 - 4.6.1 + 5.20.0 + 5.2.0 5.8.2 1.3 2.17.2 @@ -69,21 +72,11 @@ slf4j-api ${slf4j.version} - + - io.jsonwebtoken - jjwt-api - ${jjwt.version} - - - io.jsonwebtoken - jjwt-impl - ${jjwt.version} - - - io.jsonwebtoken - jjwt-jackson - ${jjwt.version} + com.auth0 + java-jwt + ${auth0.java-jwt.version} @@ -135,7 +128,7 @@ org.mockito mockito-inline - ${mockito-junit.version} + ${mockito-inline.version} test diff --git a/tests/src/test/java/io/scalecube/security/tokens/jwt/JsonwebtokenResolverTests.java b/tests/src/test/java/io/scalecube/security/tokens/jwt/JsonwebtokenResolverTests.java index bc9e8de..b5b69ca 100644 --- a/tests/src/test/java/io/scalecube/security/tokens/jwt/JsonwebtokenResolverTests.java +++ b/tests/src/test/java/io/scalecube/security/tokens/jwt/JsonwebtokenResolverTests.java @@ -11,10 +11,8 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import io.jsonwebtoken.Locator; import io.scalecube.security.environment.IntegrationEnvironmentFixture; import io.scalecube.security.environment.VaultEnvironment; -import java.security.Key; import java.time.Duration; import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.Test; @@ -58,7 +56,7 @@ void testParseTokenSuccessfully(VaultEnvironment vaultEnvironment) { void testJwksKeyLocatorThrowsError(VaultEnvironment vaultEnvironment) { final var token = vaultEnvironment.newServiceToken(); - Locator keyLocator = mock(Locator.class); + final var keyLocator = mock(JwksKeyLocator.class); when(keyLocator.locate(any())).thenThrow(new RuntimeException("Cannot get key")); try { @@ -75,7 +73,7 @@ void testJwksKeyLocatorThrowsError(VaultEnvironment vaultEnvironment) { void testJwksKeyLocatorThrowsRetryableError(VaultEnvironment vaultEnvironment) { final var token = vaultEnvironment.newServiceToken(); - Locator keyLocator = mock(Locator.class); + final var keyLocator = mock(JwksKeyLocator.class); when(keyLocator.locate(any())).thenThrow(new JwtUnavailableException("JWKS timeout")); try { diff --git a/tokens/pom.xml b/tokens/pom.xml index eab756f..507f641 100644 --- a/tokens/pom.xml +++ b/tokens/pom.xml @@ -1,5 +1,7 @@ - + 4.0.0 @@ -13,16 +15,8 @@ - io.jsonwebtoken - jjwt-api - - - io.jsonwebtoken - jjwt-impl - - - io.jsonwebtoken - jjwt-jackson + com.auth0 + java-jwt org.slf4j diff --git a/tokens/src/main/java/io/scalecube/security/tokens/jwt/JsonwebtokenResolver.java b/tokens/src/main/java/io/scalecube/security/tokens/jwt/JsonwebtokenResolver.java index f3ac565..a06c594 100644 --- a/tokens/src/main/java/io/scalecube/security/tokens/jwt/JsonwebtokenResolver.java +++ b/tokens/src/main/java/io/scalecube/security/tokens/jwt/JsonwebtokenResolver.java @@ -1,9 +1,8 @@ package io.scalecube.security.tokens.jwt; -import io.jsonwebtoken.JwtParser; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.Locator; -import java.security.Key; +import com.auth0.jwt.JWT; +import com.auth0.jwt.algorithms.Algorithm; +import java.security.interfaces.RSAPublicKey; import java.util.concurrent.CompletableFuture; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -12,18 +11,22 @@ public class JsonwebtokenResolver implements JwtTokenResolver { private static final Logger LOGGER = LoggerFactory.getLogger(JsonwebtokenResolver.class); - private final JwtParser jwtParser; + private final JwksKeyLocator keyLocator; - public JsonwebtokenResolver(Locator keyLocator) { - jwtParser = Jwts.parser().keyLocator(keyLocator).build(); + public JsonwebtokenResolver(JwksKeyLocator keyLocator) { + this.keyLocator = keyLocator; } @Override public CompletableFuture resolveToken(String token) { return CompletableFuture.supplyAsync( () -> { - final var claimsJws = jwtParser.parseSignedClaims(token); - return new JwtToken(claimsJws.getHeader(), claimsJws.getPayload()); + final var rawToken = JWT.decode(token); + final var kid = rawToken.getKeyId(); + final var publicKey = (RSAPublicKey) keyLocator.locate(kid); + final var verifier = JWT.require(Algorithm.RSA256(publicKey, null)).build(); + verifier.verify(token); + return JwtToken.parseToken(token); }) .handle( (jwtToken, ex) -> { diff --git a/tokens/src/main/java/io/scalecube/security/tokens/jwt/JwksKeyLocator.java b/tokens/src/main/java/io/scalecube/security/tokens/jwt/JwksKeyLocator.java index d0c41ca..83a5039 100644 --- a/tokens/src/main/java/io/scalecube/security/tokens/jwt/JwksKeyLocator.java +++ b/tokens/src/main/java/io/scalecube/security/tokens/jwt/JwksKeyLocator.java @@ -6,8 +6,6 @@ import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; -import io.jsonwebtoken.JwsHeader; -import io.jsonwebtoken.LocatorAdapter; import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; @@ -29,7 +27,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.ReentrantLock; -public class JwksKeyLocator extends LocatorAdapter { +public class JwksKeyLocator { private static final ObjectMapper OBJECT_MAPPER = newObjectMapper(); @@ -54,16 +52,15 @@ public static Builder builder() { return new Builder(); } - @Override - protected Key locate(JwsHeader header) { + public Key locate(String kid) { try { return keyResolutions .computeIfAbsent( - header.getKeyId(), - kid -> { - final var key = findKeyById(computeKeyList(), kid); + kid, + id -> { + final var key = findKeyById(computeKeyList(), id); if (key == null) { - throw new JwtUnavailableException("Cannot find key by kid: " + kid); + throw new JwtUnavailableException("Cannot find key by kid: " + id); } return new CachedKey(key, System.currentTimeMillis() + keyTtl); }) From b8ff2570fb1a6f828f1d413b0ee7a0ce7cce58ec Mon Sep 17 00:00:00 2001 From: artem-v Date: Tue, 28 Oct 2025 14:03:17 +0200 Subject: [PATCH 2/4] WIP --- .../security/tokens/jwt/JwksKeyLocator.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/tokens/src/main/java/io/scalecube/security/tokens/jwt/JwksKeyLocator.java b/tokens/src/main/java/io/scalecube/security/tokens/jwt/JwksKeyLocator.java index 83a5039..c3bfb7a 100644 --- a/tokens/src/main/java/io/scalecube/security/tokens/jwt/JwksKeyLocator.java +++ b/tokens/src/main/java/io/scalecube/security/tokens/jwt/JwksKeyLocator.java @@ -45,7 +45,10 @@ private JwksKeyLocator(Builder builder) { this.connectTimeout = Objects.requireNonNull(builder.connectTimeout, "connectTimeout"); this.requestTimeout = Objects.requireNonNull(builder.requestTimeout, "requestTimeout"); this.keyTtl = builder.keyTtl; - this.httpClient = HttpClient.newBuilder().connectTimeout(connectTimeout).build(); + this.httpClient = + builder.httpClient != null + ? builder.httpClient + : HttpClient.newBuilder().connectTimeout(connectTimeout).build(); } public static Builder builder() { @@ -160,6 +163,7 @@ public static class Builder { private Duration connectTimeout = Duration.ofSeconds(10); private Duration requestTimeout = Duration.ofSeconds(10); private int keyTtl = 60 * 1000; + private HttpClient httpClient; private Builder() {} @@ -211,6 +215,17 @@ public Builder keyTtl(int keyTtl) { return this; } + /** + * Setter for optional {@link HttpClient}. + * + * @param httpClient httpClient + * @return this + */ + public Builder httpClient(HttpClient httpClient) { + this.httpClient = httpClient; + return this; + } + public JwksKeyLocator build() { return new JwksKeyLocator(this); } From 458dd333709aa785b0168837d5008158b4794036 Mon Sep 17 00:00:00 2001 From: artem-v Date: Fri, 31 Oct 2025 08:14:59 +0200 Subject: [PATCH 3/4] Renamed/repackaged 'tokens' module --- {tokens => jwt}/pom.xml | 2 +- .../java/io/scalecube/security}/jwt/JsonwebtokenResolver.java | 2 +- .../src/main/java/io/scalecube/security}/jwt/JwkInfo.java | 2 +- .../src/main/java/io/scalecube/security}/jwt/JwkInfoList.java | 2 +- .../main/java/io/scalecube/security}/jwt/JwksKeyLocator.java | 2 +- .../src/main/java/io/scalecube/security}/jwt/JwtToken.java | 2 +- .../java/io/scalecube/security}/jwt/JwtTokenException.java | 2 +- .../java/io/scalecube/security}/jwt/JwtTokenResolver.java | 2 +- .../io/scalecube/security}/jwt/JwtUnavailableException.java | 2 +- pom.xml | 2 +- tests/pom.xml | 2 +- .../security/{tokens => }/jwt/JsonwebtokenResolverTests.java | 2 +- .../io/scalecube/security/vault/VaultServiceTokenTests.java | 4 ++-- 13 files changed, 14 insertions(+), 14 deletions(-) rename {tokens => jwt}/pom.xml (93%) rename {tokens/src/main/java/io/scalecube/security/tokens => jwt/src/main/java/io/scalecube/security}/jwt/JsonwebtokenResolver.java (97%) rename {tokens/src/main/java/io/scalecube/security/tokens => jwt/src/main/java/io/scalecube/security}/jwt/JwkInfo.java (97%) rename {tokens/src/main/java/io/scalecube/security/tokens => jwt/src/main/java/io/scalecube/security}/jwt/JwkInfoList.java (93%) rename {tokens/src/main/java/io/scalecube/security/tokens => jwt/src/main/java/io/scalecube/security}/jwt/JwksKeyLocator.java (99%) rename {tokens/src/main/java/io/scalecube/security/tokens => jwt/src/main/java/io/scalecube/security}/jwt/JwtToken.java (96%) rename {tokens/src/main/java/io/scalecube/security/tokens => jwt/src/main/java/io/scalecube/security}/jwt/JwtTokenException.java (94%) rename {tokens/src/main/java/io/scalecube/security/tokens => jwt/src/main/java/io/scalecube/security}/jwt/JwtTokenResolver.java (87%) rename {tokens/src/main/java/io/scalecube/security/tokens => jwt/src/main/java/io/scalecube/security}/jwt/JwtUnavailableException.java (95%) rename tests/src/test/java/io/scalecube/security/{tokens => }/jwt/JsonwebtokenResolverTests.java (98%) diff --git a/tokens/pom.xml b/jwt/pom.xml similarity index 93% rename from tokens/pom.xml rename to jwt/pom.xml index 507f641..3df4fd1 100644 --- a/tokens/pom.xml +++ b/jwt/pom.xml @@ -10,7 +10,7 @@ 1.1.8-SNAPSHOT - scalecube-security-tokens + scalecube-security-jwt ${project.artifactId} diff --git a/tokens/src/main/java/io/scalecube/security/tokens/jwt/JsonwebtokenResolver.java b/jwt/src/main/java/io/scalecube/security/jwt/JsonwebtokenResolver.java similarity index 97% rename from tokens/src/main/java/io/scalecube/security/tokens/jwt/JsonwebtokenResolver.java rename to jwt/src/main/java/io/scalecube/security/jwt/JsonwebtokenResolver.java index a06c594..1c0de11 100644 --- a/tokens/src/main/java/io/scalecube/security/tokens/jwt/JsonwebtokenResolver.java +++ b/jwt/src/main/java/io/scalecube/security/jwt/JsonwebtokenResolver.java @@ -1,4 +1,4 @@ -package io.scalecube.security.tokens.jwt; +package io.scalecube.security.jwt; import com.auth0.jwt.JWT; import com.auth0.jwt.algorithms.Algorithm; diff --git a/tokens/src/main/java/io/scalecube/security/tokens/jwt/JwkInfo.java b/jwt/src/main/java/io/scalecube/security/jwt/JwkInfo.java similarity index 97% rename from tokens/src/main/java/io/scalecube/security/tokens/jwt/JwkInfo.java rename to jwt/src/main/java/io/scalecube/security/jwt/JwkInfo.java index 25cb6c8..0098621 100644 --- a/tokens/src/main/java/io/scalecube/security/tokens/jwt/JwkInfo.java +++ b/jwt/src/main/java/io/scalecube/security/jwt/JwkInfo.java @@ -1,4 +1,4 @@ -package io.scalecube.security.tokens.jwt; +package io.scalecube.security.jwt; import com.fasterxml.jackson.annotation.JsonProperty; import java.util.StringJoiner; diff --git a/tokens/src/main/java/io/scalecube/security/tokens/jwt/JwkInfoList.java b/jwt/src/main/java/io/scalecube/security/jwt/JwkInfoList.java similarity index 93% rename from tokens/src/main/java/io/scalecube/security/tokens/jwt/JwkInfoList.java rename to jwt/src/main/java/io/scalecube/security/jwt/JwkInfoList.java index 3ccee0a..f3f8f61 100644 --- a/tokens/src/main/java/io/scalecube/security/tokens/jwt/JwkInfoList.java +++ b/jwt/src/main/java/io/scalecube/security/jwt/JwkInfoList.java @@ -1,4 +1,4 @@ -package io.scalecube.security.tokens.jwt; +package io.scalecube.security.jwt; import java.util.ArrayList; import java.util.List; diff --git a/tokens/src/main/java/io/scalecube/security/tokens/jwt/JwksKeyLocator.java b/jwt/src/main/java/io/scalecube/security/jwt/JwksKeyLocator.java similarity index 99% rename from tokens/src/main/java/io/scalecube/security/tokens/jwt/JwksKeyLocator.java rename to jwt/src/main/java/io/scalecube/security/jwt/JwksKeyLocator.java index c3bfb7a..f74abe0 100644 --- a/tokens/src/main/java/io/scalecube/security/tokens/jwt/JwksKeyLocator.java +++ b/jwt/src/main/java/io/scalecube/security/jwt/JwksKeyLocator.java @@ -1,4 +1,4 @@ -package io.scalecube.security.tokens.jwt; +package io.scalecube.security.jwt; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonInclude; diff --git a/tokens/src/main/java/io/scalecube/security/tokens/jwt/JwtToken.java b/jwt/src/main/java/io/scalecube/security/jwt/JwtToken.java similarity index 96% rename from tokens/src/main/java/io/scalecube/security/tokens/jwt/JwtToken.java rename to jwt/src/main/java/io/scalecube/security/jwt/JwtToken.java index 828164e..3fbda56 100644 --- a/tokens/src/main/java/io/scalecube/security/tokens/jwt/JwtToken.java +++ b/jwt/src/main/java/io/scalecube/security/jwt/JwtToken.java @@ -1,4 +1,4 @@ -package io.scalecube.security.tokens.jwt; +package io.scalecube.security.jwt; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException; diff --git a/tokens/src/main/java/io/scalecube/security/tokens/jwt/JwtTokenException.java b/jwt/src/main/java/io/scalecube/security/jwt/JwtTokenException.java similarity index 94% rename from tokens/src/main/java/io/scalecube/security/tokens/jwt/JwtTokenException.java rename to jwt/src/main/java/io/scalecube/security/jwt/JwtTokenException.java index 5ea597c..c36fdf7 100644 --- a/tokens/src/main/java/io/scalecube/security/tokens/jwt/JwtTokenException.java +++ b/jwt/src/main/java/io/scalecube/security/jwt/JwtTokenException.java @@ -1,4 +1,4 @@ -package io.scalecube.security.tokens.jwt; +package io.scalecube.security.jwt; import java.util.StringJoiner; diff --git a/tokens/src/main/java/io/scalecube/security/tokens/jwt/JwtTokenResolver.java b/jwt/src/main/java/io/scalecube/security/jwt/JwtTokenResolver.java similarity index 87% rename from tokens/src/main/java/io/scalecube/security/tokens/jwt/JwtTokenResolver.java rename to jwt/src/main/java/io/scalecube/security/jwt/JwtTokenResolver.java index a85a276..d1b045d 100644 --- a/tokens/src/main/java/io/scalecube/security/tokens/jwt/JwtTokenResolver.java +++ b/jwt/src/main/java/io/scalecube/security/jwt/JwtTokenResolver.java @@ -1,4 +1,4 @@ -package io.scalecube.security.tokens.jwt; +package io.scalecube.security.jwt; import java.util.concurrent.CompletableFuture; diff --git a/tokens/src/main/java/io/scalecube/security/tokens/jwt/JwtUnavailableException.java b/jwt/src/main/java/io/scalecube/security/jwt/JwtUnavailableException.java similarity index 95% rename from tokens/src/main/java/io/scalecube/security/tokens/jwt/JwtUnavailableException.java rename to jwt/src/main/java/io/scalecube/security/jwt/JwtUnavailableException.java index adb00a4..c6cdb7b 100644 --- a/tokens/src/main/java/io/scalecube/security/tokens/jwt/JwtUnavailableException.java +++ b/jwt/src/main/java/io/scalecube/security/jwt/JwtUnavailableException.java @@ -1,4 +1,4 @@ -package io.scalecube.security.tokens.jwt; +package io.scalecube.security.jwt; /** * Special JWT exception type indicating transient error during token resolution. For example such diff --git a/pom.xml b/pom.xml index f56d7bf..b7283ed 100644 --- a/pom.xml +++ b/pom.xml @@ -35,7 +35,7 @@ - tokens + jwt vault tests diff --git a/tests/pom.xml b/tests/pom.xml index 100287e..7ecde6a 100644 --- a/tests/pom.xml +++ b/tests/pom.xml @@ -14,7 +14,7 @@ io.scalecube - scalecube-security-tokens + scalecube-security-jwt ${project.parent.version} diff --git a/tests/src/test/java/io/scalecube/security/tokens/jwt/JsonwebtokenResolverTests.java b/tests/src/test/java/io/scalecube/security/jwt/JsonwebtokenResolverTests.java similarity index 98% rename from tests/src/test/java/io/scalecube/security/tokens/jwt/JsonwebtokenResolverTests.java rename to tests/src/test/java/io/scalecube/security/jwt/JsonwebtokenResolverTests.java index b5b69ca..893ed76 100644 --- a/tests/src/test/java/io/scalecube/security/tokens/jwt/JsonwebtokenResolverTests.java +++ b/tests/src/test/java/io/scalecube/security/jwt/JsonwebtokenResolverTests.java @@ -1,4 +1,4 @@ -package io.scalecube.security.tokens.jwt; +package io.scalecube.security.jwt; import static io.scalecube.security.environment.VaultEnvironment.getRootCause; import static org.hamcrest.CoreMatchers.instanceOf; diff --git a/tests/src/test/java/io/scalecube/security/vault/VaultServiceTokenTests.java b/tests/src/test/java/io/scalecube/security/vault/VaultServiceTokenTests.java index 80f3430..b4adbdf 100644 --- a/tests/src/test/java/io/scalecube/security/vault/VaultServiceTokenTests.java +++ b/tests/src/test/java/io/scalecube/security/vault/VaultServiceTokenTests.java @@ -11,8 +11,8 @@ import io.scalecube.security.environment.IntegrationEnvironmentFixture; import io.scalecube.security.environment.VaultEnvironment; -import io.scalecube.security.tokens.jwt.JsonwebtokenResolver; -import io.scalecube.security.tokens.jwt.JwksKeyLocator; +import io.scalecube.security.jwt.JsonwebtokenResolver; +import io.scalecube.security.jwt.JwksKeyLocator; import io.scalecube.security.vault.VaultServiceRolesInstaller.ServiceRoles; import io.scalecube.security.vault.VaultServiceRolesInstaller.ServiceRoles.Role; import java.util.Collections; From 808eaf52a55e33f8afd3a1efe62fc4347ff97a96 Mon Sep 17 00:00:00 2001 From: artem-v Date: Fri, 31 Oct 2025 09:22:09 +0200 Subject: [PATCH 4/4] Renaming, + Updated documentation --- ...ksKeyLocator.java => JwksKeyProvider.java} | 22 ++++++++++++++----- ...enResolver.java => JwksTokenResolver.java} | 16 +++++++++----- .../io/scalecube/security/jwt/JwtToken.java | 8 ++++++- .../security/jwt/JwtTokenResolver.java | 7 +++++- ...Tests.java => JwksTokenResolverTests.java} | 22 +++++++++---------- .../vault/VaultServiceTokenTests.java | 7 +++--- 6 files changed, 54 insertions(+), 28 deletions(-) rename jwt/src/main/java/io/scalecube/security/jwt/{JwksKeyLocator.java => JwksKeyProvider.java} (90%) rename jwt/src/main/java/io/scalecube/security/jwt/{JsonwebtokenResolver.java => JwksTokenResolver.java} (76%) rename tests/src/test/java/io/scalecube/security/jwt/{JsonwebtokenResolverTests.java => JwksTokenResolverTests.java} (78%) diff --git a/jwt/src/main/java/io/scalecube/security/jwt/JwksKeyLocator.java b/jwt/src/main/java/io/scalecube/security/jwt/JwksKeyProvider.java similarity index 90% rename from jwt/src/main/java/io/scalecube/security/jwt/JwksKeyLocator.java rename to jwt/src/main/java/io/scalecube/security/jwt/JwksKeyProvider.java index f74abe0..1321589 100644 --- a/jwt/src/main/java/io/scalecube/security/jwt/JwksKeyLocator.java +++ b/jwt/src/main/java/io/scalecube/security/jwt/JwksKeyProvider.java @@ -27,7 +27,11 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.ReentrantLock; -public class JwksKeyLocator { +/** + * Provides public keys from a remote JWKS endpoint and caches them temporarily. Keys are fetched on + * demand by their {@code kid} and automatically removed when expired. + */ +public class JwksKeyProvider { private static final ObjectMapper OBJECT_MAPPER = newObjectMapper(); @@ -40,7 +44,7 @@ public class JwksKeyLocator { private final Map keyResolutions = new ConcurrentHashMap<>(); private final ReentrantLock cleanupLock = new ReentrantLock(); - private JwksKeyLocator(Builder builder) { + private JwksKeyProvider(Builder builder) { this.jwksUri = Objects.requireNonNull(builder.jwksUri, "jwksUri"); this.connectTimeout = Objects.requireNonNull(builder.connectTimeout, "connectTimeout"); this.requestTimeout = Objects.requireNonNull(builder.requestTimeout, "requestTimeout"); @@ -55,7 +59,15 @@ public static Builder builder() { return new Builder(); } - public Key locate(String kid) { + /** + * Returns the public key for the given {@code kid}. If not cached, the key is fetched from the + * JWKS endpoint and cached for future use. + * + * @param kid key id of the public key to retrieve + * @return {@link Key} object associated with given {@code kid} + * @throws JwtUnavailableException if key cannot be found or JWKS cannot be retrieved + */ + public Key getKey(String kid) { try { return keyResolutions .computeIfAbsent( @@ -226,8 +238,8 @@ public Builder httpClient(HttpClient httpClient) { return this; } - public JwksKeyLocator build() { - return new JwksKeyLocator(this); + public JwksKeyProvider build() { + return new JwksKeyProvider(this); } } } diff --git a/jwt/src/main/java/io/scalecube/security/jwt/JsonwebtokenResolver.java b/jwt/src/main/java/io/scalecube/security/jwt/JwksTokenResolver.java similarity index 76% rename from jwt/src/main/java/io/scalecube/security/jwt/JsonwebtokenResolver.java rename to jwt/src/main/java/io/scalecube/security/jwt/JwksTokenResolver.java index 1c0de11..5ceab1d 100644 --- a/jwt/src/main/java/io/scalecube/security/jwt/JsonwebtokenResolver.java +++ b/jwt/src/main/java/io/scalecube/security/jwt/JwksTokenResolver.java @@ -7,14 +7,18 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class JsonwebtokenResolver implements JwtTokenResolver { +/** + * Resolves and verifies JWT tokens using public keys provided by {@link JwksKeyProvider}. Tokens + * are validated asynchronously and parsed into {@link JwtToken} instances. + */ +public class JwksTokenResolver implements JwtTokenResolver { - private static final Logger LOGGER = LoggerFactory.getLogger(JsonwebtokenResolver.class); + private static final Logger LOGGER = LoggerFactory.getLogger(JwksTokenResolver.class); - private final JwksKeyLocator keyLocator; + private final JwksKeyProvider keyProvider; - public JsonwebtokenResolver(JwksKeyLocator keyLocator) { - this.keyLocator = keyLocator; + public JwksTokenResolver(JwksKeyProvider keyProvider) { + this.keyProvider = keyProvider; } @Override @@ -23,7 +27,7 @@ public CompletableFuture resolveToken(String token) { () -> { final var rawToken = JWT.decode(token); final var kid = rawToken.getKeyId(); - final var publicKey = (RSAPublicKey) keyLocator.locate(kid); + final var publicKey = (RSAPublicKey) keyProvider.getKey(kid); final var verifier = JWT.require(Algorithm.RSA256(publicKey, null)).build(); verifier.verify(token); return JwtToken.parseToken(token); diff --git a/jwt/src/main/java/io/scalecube/security/jwt/JwtToken.java b/jwt/src/main/java/io/scalecube/security/jwt/JwtToken.java index 3fbda56..35e3468 100644 --- a/jwt/src/main/java/io/scalecube/security/jwt/JwtToken.java +++ b/jwt/src/main/java/io/scalecube/security/jwt/JwtToken.java @@ -6,13 +6,19 @@ import java.util.Base64; import java.util.Map; +/** + * Represents parsed JWT (JSON Web Token), including its header and payload claims. + * + * @param header JWT header as map of key-value pairs + * @param payload JWT payload (claims) as map of key-value pairs + */ public record JwtToken(Map header, Map payload) { /** * Parses given JWT without verifying its signature. * * @param token jwt token - * @return parsed token + * @return {@link JwtToken} object, or {@link JwtTokenException} will be thrown */ public static JwtToken parseToken(String token) { String[] parts = token.split("\\."); diff --git a/jwt/src/main/java/io/scalecube/security/jwt/JwtTokenResolver.java b/jwt/src/main/java/io/scalecube/security/jwt/JwtTokenResolver.java index d1b045d..e5c3bd7 100644 --- a/jwt/src/main/java/io/scalecube/security/jwt/JwtTokenResolver.java +++ b/jwt/src/main/java/io/scalecube/security/jwt/JwtTokenResolver.java @@ -2,13 +2,18 @@ import java.util.concurrent.CompletableFuture; +/** + * Resolves and verifies JWT tokens asynchronously. Implementations parse the token, validate its + * signature, and extract claims. + */ public interface JwtTokenResolver { /** * Verifies given JWT and parses its header and claims. * * @param token jwt token - * @return async result with {@link JwtToken}, or error + * @return async result completing with {@link JwtToken}, or completing exceptionally with {@link + * JwtTokenException} on failure */ CompletableFuture resolveToken(String token); } diff --git a/tests/src/test/java/io/scalecube/security/jwt/JsonwebtokenResolverTests.java b/tests/src/test/java/io/scalecube/security/jwt/JwksTokenResolverTests.java similarity index 78% rename from tests/src/test/java/io/scalecube/security/jwt/JsonwebtokenResolverTests.java rename to tests/src/test/java/io/scalecube/security/jwt/JwksTokenResolverTests.java index 893ed76..d7b1a58 100644 --- a/tests/src/test/java/io/scalecube/security/jwt/JsonwebtokenResolverTests.java +++ b/tests/src/test/java/io/scalecube/security/jwt/JwksTokenResolverTests.java @@ -19,15 +19,15 @@ import org.junit.jupiter.api.extension.ExtendWith; @ExtendWith(IntegrationEnvironmentFixture.class) -public class JsonwebtokenResolverTests { +public class JwksTokenResolverTests { @Test void testResolveTokenTokenSuccessfully(VaultEnvironment vaultEnvironment) throws Exception { final var token = vaultEnvironment.newServiceToken(); final var jwtToken = - new JsonwebtokenResolver( - JwksKeyLocator.builder() + new JwksTokenResolver( + JwksKeyProvider.builder() .jwksUri(vaultEnvironment.jwksUri()) .connectTimeout(Duration.ofSeconds(3)) .requestTimeout(Duration.ofSeconds(3)) @@ -53,14 +53,14 @@ void testParseTokenSuccessfully(VaultEnvironment vaultEnvironment) { } @Test - void testJwksKeyLocatorThrowsError(VaultEnvironment vaultEnvironment) { + void testJwksKeyProviderThrowsError(VaultEnvironment vaultEnvironment) { final var token = vaultEnvironment.newServiceToken(); - final var keyLocator = mock(JwksKeyLocator.class); - when(keyLocator.locate(any())).thenThrow(new RuntimeException("Cannot get key")); + final var keyProvider = mock(JwksKeyProvider.class); + when(keyProvider.getKey(any())).thenThrow(new RuntimeException("Cannot get key")); try { - new JsonwebtokenResolver(keyLocator).resolveToken(token).get(3, TimeUnit.SECONDS); + new JwksTokenResolver(keyProvider).resolveToken(token).get(3, TimeUnit.SECONDS); fail("Expected exception"); } catch (Exception e) { final var ex = getRootCause(e); @@ -70,14 +70,14 @@ void testJwksKeyLocatorThrowsError(VaultEnvironment vaultEnvironment) { } @Test - void testJwksKeyLocatorThrowsRetryableError(VaultEnvironment vaultEnvironment) { + void testJwksKeyProviderThrowsRetryableError(VaultEnvironment vaultEnvironment) { final var token = vaultEnvironment.newServiceToken(); - final var keyLocator = mock(JwksKeyLocator.class); - when(keyLocator.locate(any())).thenThrow(new JwtUnavailableException("JWKS timeout")); + final var keyProvider = mock(JwksKeyProvider.class); + when(keyProvider.getKey(any())).thenThrow(new JwtUnavailableException("JWKS timeout")); try { - new JsonwebtokenResolver(keyLocator).resolveToken(token).get(3, TimeUnit.SECONDS); + new JwksTokenResolver(keyProvider).resolveToken(token).get(3, TimeUnit.SECONDS); fail("Expected exception"); } catch (Exception e) { final var ex = getRootCause(e); diff --git a/tests/src/test/java/io/scalecube/security/vault/VaultServiceTokenTests.java b/tests/src/test/java/io/scalecube/security/vault/VaultServiceTokenTests.java index b4adbdf..0be7e5b 100644 --- a/tests/src/test/java/io/scalecube/security/vault/VaultServiceTokenTests.java +++ b/tests/src/test/java/io/scalecube/security/vault/VaultServiceTokenTests.java @@ -11,8 +11,8 @@ import io.scalecube.security.environment.IntegrationEnvironmentFixture; import io.scalecube.security.environment.VaultEnvironment; -import io.scalecube.security.jwt.JsonwebtokenResolver; -import io.scalecube.security.jwt.JwksKeyLocator; +import io.scalecube.security.jwt.JwksKeyProvider; +import io.scalecube.security.jwt.JwksTokenResolver; import io.scalecube.security.vault.VaultServiceRolesInstaller.ServiceRoles; import io.scalecube.security.vault.VaultServiceRolesInstaller.ServiceRoles.Role; import java.util.Collections; @@ -141,8 +141,7 @@ void testGetServiceTokenSuccessfully(VaultEnvironment vaultEnvironment) throws E // Verify serviceToken final var jwtToken = - new JsonwebtokenResolver( - JwksKeyLocator.builder().jwksUri(vaultEnvironment.jwksUri()).build()) + new JwksTokenResolver(JwksKeyProvider.builder().jwksUri(vaultEnvironment.jwksUri()).build()) .resolveToken(serviceToken) .get(3, TimeUnit.SECONDS);