Skip to content

Commit d0d8b63

Browse files
authored
Get rid of jjwt, use auth0-java-jwt instead (#118)
1 parent 2027306 commit d0d8b63

File tree

15 files changed

+143
-117
lines changed

15 files changed

+143
-117
lines changed

jwt/pom.xml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
7+
<parent>
8+
<groupId>io.scalecube</groupId>
9+
<artifactId>scalecube-security-parent</artifactId>
10+
<version>1.1.8-SNAPSHOT</version>
11+
</parent>
12+
13+
<artifactId>scalecube-security-jwt</artifactId>
14+
<name>${project.artifactId}</name>
15+
16+
<dependencies>
17+
<dependency>
18+
<groupId>com.auth0</groupId>
19+
<artifactId>java-jwt</artifactId>
20+
</dependency>
21+
<dependency>
22+
<groupId>org.slf4j</groupId>
23+
<artifactId>slf4j-api</artifactId>
24+
</dependency>
25+
</dependencies>
26+
27+
</project>

tokens/src/main/java/io/scalecube/security/tokens/jwt/JwkInfo.java renamed to jwt/src/main/java/io/scalecube/security/jwt/JwkInfo.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package io.scalecube.security.tokens.jwt;
1+
package io.scalecube.security.jwt;
22

33
import com.fasterxml.jackson.annotation.JsonProperty;
44
import java.util.StringJoiner;

tokens/src/main/java/io/scalecube/security/tokens/jwt/JwkInfoList.java renamed to jwt/src/main/java/io/scalecube/security/jwt/JwkInfoList.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package io.scalecube.security.tokens.jwt;
1+
package io.scalecube.security.jwt;
22

33
import java.util.ArrayList;
44
import java.util.List;

tokens/src/main/java/io/scalecube/security/tokens/jwt/JwksKeyLocator.java renamed to jwt/src/main/java/io/scalecube/security/jwt/JwksKeyProvider.java

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
1-
package io.scalecube.security.tokens.jwt;
1+
package io.scalecube.security.jwt;
22

33
import com.fasterxml.jackson.annotation.JsonAutoDetect;
44
import com.fasterxml.jackson.annotation.JsonInclude;
55
import com.fasterxml.jackson.annotation.PropertyAccessor;
66
import com.fasterxml.jackson.databind.DeserializationFeature;
77
import com.fasterxml.jackson.databind.ObjectMapper;
88
import com.fasterxml.jackson.databind.SerializationFeature;
9-
import io.jsonwebtoken.JwsHeader;
10-
import io.jsonwebtoken.LocatorAdapter;
119
import java.io.BufferedInputStream;
1210
import java.io.IOException;
1311
import java.io.InputStream;
@@ -29,7 +27,11 @@
2927
import java.util.concurrent.ConcurrentHashMap;
3028
import java.util.concurrent.locks.ReentrantLock;
3129

32-
public class JwksKeyLocator extends LocatorAdapter<Key> {
30+
/**
31+
* Provides public keys from a remote JWKS endpoint and caches them temporarily. Keys are fetched on
32+
* demand by their {@code kid} and automatically removed when expired.
33+
*/
34+
public class JwksKeyProvider {
3335

3436
private static final ObjectMapper OBJECT_MAPPER = newObjectMapper();
3537

@@ -42,28 +44,38 @@ public class JwksKeyLocator extends LocatorAdapter<Key> {
4244
private final Map<String, CachedKey> keyResolutions = new ConcurrentHashMap<>();
4345
private final ReentrantLock cleanupLock = new ReentrantLock();
4446

45-
private JwksKeyLocator(Builder builder) {
47+
private JwksKeyProvider(Builder builder) {
4648
this.jwksUri = Objects.requireNonNull(builder.jwksUri, "jwksUri");
4749
this.connectTimeout = Objects.requireNonNull(builder.connectTimeout, "connectTimeout");
4850
this.requestTimeout = Objects.requireNonNull(builder.requestTimeout, "requestTimeout");
4951
this.keyTtl = builder.keyTtl;
50-
this.httpClient = HttpClient.newBuilder().connectTimeout(connectTimeout).build();
52+
this.httpClient =
53+
builder.httpClient != null
54+
? builder.httpClient
55+
: HttpClient.newBuilder().connectTimeout(connectTimeout).build();
5156
}
5257

5358
public static Builder builder() {
5459
return new Builder();
5560
}
5661

57-
@Override
58-
protected Key locate(JwsHeader header) {
62+
/**
63+
* Returns the public key for the given {@code kid}. If not cached, the key is fetched from the
64+
* JWKS endpoint and cached for future use.
65+
*
66+
* @param kid key id of the public key to retrieve
67+
* @return {@link Key} object associated with given {@code kid}
68+
* @throws JwtUnavailableException if key cannot be found or JWKS cannot be retrieved
69+
*/
70+
public Key getKey(String kid) {
5971
try {
6072
return keyResolutions
6173
.computeIfAbsent(
62-
header.getKeyId(),
63-
kid -> {
64-
final var key = findKeyById(computeKeyList(), kid);
74+
kid,
75+
id -> {
76+
final var key = findKeyById(computeKeyList(), id);
6577
if (key == null) {
66-
throw new JwtUnavailableException("Cannot find key by kid: " + kid);
78+
throw new JwtUnavailableException("Cannot find key by kid: " + id);
6779
}
6880
return new CachedKey(key, System.currentTimeMillis() + keyTtl);
6981
})
@@ -163,6 +175,7 @@ public static class Builder {
163175
private Duration connectTimeout = Duration.ofSeconds(10);
164176
private Duration requestTimeout = Duration.ofSeconds(10);
165177
private int keyTtl = 60 * 1000;
178+
private HttpClient httpClient;
166179

167180
private Builder() {}
168181

@@ -214,8 +227,19 @@ public Builder keyTtl(int keyTtl) {
214227
return this;
215228
}
216229

217-
public JwksKeyLocator build() {
218-
return new JwksKeyLocator(this);
230+
/**
231+
* Setter for optional {@link HttpClient}.
232+
*
233+
* @param httpClient httpClient
234+
* @return this
235+
*/
236+
public Builder httpClient(HttpClient httpClient) {
237+
this.httpClient = httpClient;
238+
return this;
239+
}
240+
241+
public JwksKeyProvider build() {
242+
return new JwksKeyProvider(this);
219243
}
220244
}
221245
}
Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,36 @@
1-
package io.scalecube.security.tokens.jwt;
1+
package io.scalecube.security.jwt;
22

3-
import io.jsonwebtoken.JwtParser;
4-
import io.jsonwebtoken.Jwts;
5-
import io.jsonwebtoken.Locator;
6-
import java.security.Key;
3+
import com.auth0.jwt.JWT;
4+
import com.auth0.jwt.algorithms.Algorithm;
5+
import java.security.interfaces.RSAPublicKey;
76
import java.util.concurrent.CompletableFuture;
87
import org.slf4j.Logger;
98
import org.slf4j.LoggerFactory;
109

11-
public class JsonwebtokenResolver implements JwtTokenResolver {
10+
/**
11+
* Resolves and verifies JWT tokens using public keys provided by {@link JwksKeyProvider}. Tokens
12+
* are validated asynchronously and parsed into {@link JwtToken} instances.
13+
*/
14+
public class JwksTokenResolver implements JwtTokenResolver {
1215

13-
private static final Logger LOGGER = LoggerFactory.getLogger(JsonwebtokenResolver.class);
16+
private static final Logger LOGGER = LoggerFactory.getLogger(JwksTokenResolver.class);
1417

15-
private final JwtParser jwtParser;
18+
private final JwksKeyProvider keyProvider;
1619

17-
public JsonwebtokenResolver(Locator<Key> keyLocator) {
18-
jwtParser = Jwts.parser().keyLocator(keyLocator).build();
20+
public JwksTokenResolver(JwksKeyProvider keyProvider) {
21+
this.keyProvider = keyProvider;
1922
}
2023

2124
@Override
2225
public CompletableFuture<JwtToken> resolveToken(String token) {
2326
return CompletableFuture.supplyAsync(
2427
() -> {
25-
final var claimsJws = jwtParser.parseSignedClaims(token);
26-
return new JwtToken(claimsJws.getHeader(), claimsJws.getPayload());
28+
final var rawToken = JWT.decode(token);
29+
final var kid = rawToken.getKeyId();
30+
final var publicKey = (RSAPublicKey) keyProvider.getKey(kid);
31+
final var verifier = JWT.require(Algorithm.RSA256(publicKey, null)).build();
32+
verifier.verify(token);
33+
return JwtToken.parseToken(token);
2734
})
2835
.handle(
2936
(jwtToken, ex) -> {

tokens/src/main/java/io/scalecube/security/tokens/jwt/JwtToken.java renamed to jwt/src/main/java/io/scalecube/security/jwt/JwtToken.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,24 @@
1-
package io.scalecube.security.tokens.jwt;
1+
package io.scalecube.security.jwt;
22

33
import com.fasterxml.jackson.databind.ObjectMapper;
44
import java.io.IOException;
55
import java.nio.charset.StandardCharsets;
66
import java.util.Base64;
77
import java.util.Map;
88

9+
/**
10+
* Represents parsed JWT (JSON Web Token), including its header and payload claims.
11+
*
12+
* @param header JWT header as map of key-value pairs
13+
* @param payload JWT payload (claims) as map of key-value pairs
14+
*/
915
public record JwtToken(Map<String, Object> header, Map<String, Object> payload) {
1016

1117
/**
1218
* Parses given JWT without verifying its signature.
1319
*
1420
* @param token jwt token
15-
* @return parsed token
21+
* @return {@link JwtToken} object, or {@link JwtTokenException} will be thrown
1622
*/
1723
public static JwtToken parseToken(String token) {
1824
String[] parts = token.split("\\.");

tokens/src/main/java/io/scalecube/security/tokens/jwt/JwtTokenException.java renamed to jwt/src/main/java/io/scalecube/security/jwt/JwtTokenException.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package io.scalecube.security.tokens.jwt;
1+
package io.scalecube.security.jwt;
22

33
import java.util.StringJoiner;
44

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package io.scalecube.security.jwt;
2+
3+
import java.util.concurrent.CompletableFuture;
4+
5+
/**
6+
* Resolves and verifies JWT tokens asynchronously. Implementations parse the token, validate its
7+
* signature, and extract claims.
8+
*/
9+
public interface JwtTokenResolver {
10+
11+
/**
12+
* Verifies given JWT and parses its header and claims.
13+
*
14+
* @param token jwt token
15+
* @return async result completing with {@link JwtToken}, or completing exceptionally with {@link
16+
* JwtTokenException} on failure
17+
*/
18+
CompletableFuture<JwtToken> resolveToken(String token);
19+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package io.scalecube.security.tokens.jwt;
1+
package io.scalecube.security.jwt;
22

33
/**
44
* Special JWT exception type indicating transient error during token resolution. For example such

pom.xml

Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
35
<modelVersion>4.0.0</modelVersion>
46

57
<parent>
@@ -33,7 +35,7 @@
3335
</scm>
3436

3537
<modules>
36-
<module>tokens</module>
38+
<module>jwt</module>
3739
<module>vault</module>
3840
<module>tests</module>
3941
</modules>
@@ -42,9 +44,10 @@
4244
<vault-java-driver.version>5.1.0</vault-java-driver.version>
4345
<jackson.version>2.19.2</jackson.version>
4446
<slf4j.version>1.7.36</slf4j.version>
45-
<jjwt.version>0.12.6</jjwt.version>
47+
<auth0.java-jwt.version>4.5.0</auth0.java-jwt.version>
4648

47-
<mockito-junit.version>4.6.1</mockito-junit.version>
49+
<mockito-junit.version>5.20.0</mockito-junit.version>
50+
<mockito-inline.version>5.2.0</mockito-inline.version>
4851
<junit-jupiter.version>5.8.2</junit-jupiter.version>
4952
<hamcrest.version>1.3</hamcrest.version>
5053
<log4j.version>2.17.2</log4j.version>
@@ -69,21 +72,11 @@
6972
<artifactId>slf4j-api</artifactId>
7073
<version>${slf4j.version}</version>
7174
</dependency>
72-
<!-- Jsonwebtoken -->
75+
<!-- Auth0/JWT -->
7376
<dependency>
74-
<groupId>io.jsonwebtoken</groupId>
75-
<artifactId>jjwt-api</artifactId>
76-
<version>${jjwt.version}</version>
77-
</dependency>
78-
<dependency>
79-
<groupId>io.jsonwebtoken</groupId>
80-
<artifactId>jjwt-impl</artifactId>
81-
<version>${jjwt.version}</version>
82-
</dependency>
83-
<dependency>
84-
<groupId>io.jsonwebtoken</groupId>
85-
<artifactId>jjwt-jackson</artifactId>
86-
<version>${jjwt.version}</version>
77+
<groupId>com.auth0</groupId>
78+
<artifactId>java-jwt</artifactId>
79+
<version>${auth0.java-jwt.version}</version>
8780
</dependency>
8881
<!-- Jackson -->
8982
<dependency>
@@ -135,7 +128,7 @@
135128
<dependency>
136129
<groupId>org.mockito</groupId>
137130
<artifactId>mockito-inline</artifactId>
138-
<version>${mockito-junit.version}</version>
131+
<version>${mockito-inline.version}</version>
139132
<scope>test</scope>
140133
</dependency>
141134
<dependency>

0 commit comments

Comments
 (0)