Skip to content

Commit cef72de

Browse files
committed
Fixed configuration approach for JwksKeyProvider and JwtTokenResolverImpl
1 parent f32a35c commit cef72de

File tree

8 files changed

+146
-110
lines changed

8 files changed

+146
-110
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
**/target/
99
*.iml
1010
**/logs/*.log
11+
**/logs/*.log.*
1112
*.db
1213
*.csv
1314
*.log

requirements.txt

Lines changed: 0 additions & 1 deletion
This file was deleted.

tokens/src/main/java/io/scalecube/security/tokens/jwt/JwksKeyProvider.java

Lines changed: 58 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
package io.scalecube.security.tokens.jwt;
22

3-
import static io.scalecube.security.tokens.jwt.Utils.toRsaPublicKey;
4-
53
import com.fasterxml.jackson.annotation.JsonAutoDetect;
64
import com.fasterxml.jackson.annotation.JsonInclude;
75
import com.fasterxml.jackson.annotation.PropertyAccessor;
@@ -11,63 +9,84 @@
119
import java.io.BufferedInputStream;
1210
import java.io.IOException;
1311
import java.io.InputStream;
12+
import java.math.BigInteger;
1413
import java.net.HttpURLConnection;
1514
import java.net.URL;
1615
import java.security.Key;
16+
import java.security.KeyFactory;
17+
import java.security.spec.KeySpec;
18+
import java.security.spec.RSAPublicKeySpec;
1719
import java.time.Duration;
20+
import java.util.Base64;
21+
import java.util.Base64.Decoder;
1822
import java.util.Optional;
1923
import org.slf4j.Logger;
2024
import org.slf4j.LoggerFactory;
2125
import reactor.core.Exceptions;
2226
import reactor.core.publisher.Mono;
23-
import reactor.core.scheduler.Scheduler;
2427
import reactor.core.scheduler.Schedulers;
2528

2629
public final class JwksKeyProvider implements KeyProvider {
2730

2831
private static final Logger LOGGER = LoggerFactory.getLogger(JwksKeyProvider.class);
2932

30-
private static final Duration CONNECT_TIMEOUT = Duration.ofSeconds(10);
31-
private static final Duration READ_TIMEOUT = Duration.ofSeconds(10);
32-
3333
private static final ObjectMapper OBJECT_MAPPER = newObjectMapper();
3434

35-
private final Scheduler scheduler;
36-
private final String jwksUri;
37-
private final long connectTimeoutMillis;
38-
private final long readTimeoutMillis;
35+
private String jwksUri;
36+
private Duration connectTimeout = Duration.ofSeconds(10);
37+
private Duration readTimeout = Duration.ofSeconds(10);
38+
39+
public JwksKeyProvider() {}
40+
41+
private JwksKeyProvider(JwksKeyProvider other) {
42+
this.jwksUri = other.jwksUri;
43+
this.connectTimeout = other.connectTimeout;
44+
this.readTimeout = other.readTimeout;
45+
}
3946

4047
/**
41-
* Constructor.
48+
* Setter for jwksUri.
4249
*
4350
* @param jwksUri jwksUri
51+
* @return new instance with applied setting
4452
*/
45-
public JwksKeyProvider(String jwksUri) {
46-
this(jwksUri, newScheduler(), CONNECT_TIMEOUT, READ_TIMEOUT);
53+
public JwksKeyProvider jwksUri(String jwksUri) {
54+
final JwksKeyProvider c = copy();
55+
c.jwksUri = jwksUri;
56+
return c;
4757
}
4858

4959
/**
50-
* Constructor.
60+
* Setter for connectTimeout.
5161
*
52-
* @param jwksUri jwksUri
53-
* @param scheduler scheduler
5462
* @param connectTimeout connectTimeout
63+
* @return new instance with applied setting
64+
*/
65+
public JwksKeyProvider connectTimeout(Duration connectTimeout) {
66+
final JwksKeyProvider c = copy();
67+
c.connectTimeout = connectTimeout;
68+
return c;
69+
}
70+
71+
/**
72+
* Setter for readTimeout.
73+
*
5574
* @param readTimeout readTimeout
75+
* @return new instance with applied setting
5676
*/
57-
public JwksKeyProvider(
58-
String jwksUri, Scheduler scheduler, Duration connectTimeout, Duration readTimeout) {
59-
this.jwksUri = jwksUri;
60-
this.scheduler = scheduler;
61-
this.connectTimeoutMillis = connectTimeout.toMillis();
62-
this.readTimeoutMillis = readTimeout.toMillis();
77+
public JwksKeyProvider readTimeout(Duration readTimeout) {
78+
final JwksKeyProvider c = copy();
79+
c.readTimeout = readTimeout;
80+
return c;
6381
}
6482

6583
@Override
6684
public Mono<Key> findKey(String kid) {
6785
return computeKey(kid)
6886
.switchIfEmpty(Mono.error(new KeyNotFoundException("Key was not found, kid: " + kid)))
6987
.doOnSubscribe(s -> LOGGER.debug("[findKey] Looking up key in jwks, kid: {}", kid))
70-
.subscribeOn(scheduler);
88+
.subscribeOn(Schedulers.boundedElastic())
89+
.publishOn(Schedulers.boundedElastic());
7190
}
7291

7392
private Mono<Key> computeKey(String kid) {
@@ -78,8 +97,8 @@ private Mono<Key> computeKey(String kid) {
7897

7998
private JwkInfoList computeKeyList() throws IOException {
8099
HttpURLConnection httpClient = (HttpURLConnection) new URL(jwksUri).openConnection();
81-
httpClient.setConnectTimeout((int) connectTimeoutMillis);
82-
httpClient.setReadTimeout((int) readTimeoutMillis);
100+
httpClient.setConnectTimeout((int) connectTimeout.toMillis());
101+
httpClient.setReadTimeout((int) readTimeout.toMillis());
83102

84103
int responseCode = httpClient.getResponseCode();
85104
if (responseCode != 200) {
@@ -106,6 +125,18 @@ private Optional<Key> findRsaKey(JwkInfoList list, String kid) {
106125
.map(info -> toRsaPublicKey(info.modulus(), info.exponent()));
107126
}
108127

128+
static Key toRsaPublicKey(String n, String e) {
129+
Decoder b64Decoder = Base64.getUrlDecoder();
130+
BigInteger modulus = new BigInteger(1, b64Decoder.decode(n));
131+
BigInteger exponent = new BigInteger(1, b64Decoder.decode(e));
132+
KeySpec keySpec = new RSAPublicKeySpec(modulus, exponent);
133+
try {
134+
return KeyFactory.getInstance("RSA").generatePublic(keySpec);
135+
} catch (Exception ex) {
136+
throw Exceptions.propagate(ex);
137+
}
138+
}
139+
109140
private static ObjectMapper newObjectMapper() {
110141
ObjectMapper mapper = new ObjectMapper();
111142
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
@@ -117,7 +148,7 @@ private static ObjectMapper newObjectMapper() {
117148
return mapper;
118149
}
119150

120-
private static Scheduler newScheduler() {
121-
return Schedulers.newElastic("jwks-key-provider", 60, true);
151+
private JwksKeyProvider copy() {
152+
return new JwksKeyProvider(this);
122153
}
123154
}

tokens/src/main/java/io/scalecube/security/tokens/jwt/JwtTokenResolverImpl.java

Lines changed: 63 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package io.scalecube.security.tokens.jwt;
22

3-
import io.scalecube.security.tokens.jwt.jsonwebtoken.JsonwebtokenParserFactory;
43
import java.security.Key;
54
import java.time.Duration;
65
import java.util.Map;
@@ -18,41 +17,68 @@ public final class JwtTokenResolverImpl implements JwtTokenResolver {
1817

1918
private static final Logger LOGGER = LoggerFactory.getLogger(JwtTokenResolver.class);
2019

21-
private static final Duration CLEANUP_INTERVAL = Duration.ofSeconds(60);
22-
23-
private final KeyProvider keyProvider;
24-
private final JwtTokenParserFactory tokenParserFactory;
25-
private final Scheduler scheduler;
26-
private final Duration cleanupInterval;
20+
private KeyProvider keyProvider;
21+
private JwtTokenParserFactory tokenParserFactory;
22+
private Scheduler scheduler = Schedulers.boundedElastic();
23+
private Duration cleanupInterval = Duration.ofSeconds(60);
2724

2825
private final Map<String, Mono<Key>> keyResolutions = new ConcurrentHashMap<>();
2926

27+
public JwtTokenResolverImpl() {}
28+
29+
private JwtTokenResolverImpl(JwtTokenResolverImpl other) {
30+
this.keyProvider = other.keyProvider;
31+
this.tokenParserFactory = other.tokenParserFactory;
32+
this.scheduler = other.scheduler;
33+
this.cleanupInterval = other.cleanupInterval;
34+
}
35+
36+
/**
37+
* Setter for keyProvider.
38+
*
39+
* @param keyProvider keyProvider
40+
* @return new instance with applied setting
41+
*/
42+
public JwtTokenResolverImpl keyProvider(KeyProvider keyProvider) {
43+
final JwtTokenResolverImpl c = copy();
44+
c.keyProvider = keyProvider;
45+
return c;
46+
}
47+
3048
/**
31-
* Constructor.
49+
* Setter for tokenParserFactory.
3250
*
33-
* @param keyProvider key provider
51+
* @param tokenParserFactory tokenParserFactory
52+
* @return new instance with applied setting
3453
*/
35-
public JwtTokenResolverImpl(KeyProvider keyProvider) {
36-
this(keyProvider, new JsonwebtokenParserFactory(), newScheduler(), CLEANUP_INTERVAL);
54+
public JwtTokenResolverImpl tokenParserFactory(JwtTokenParserFactory tokenParserFactory) {
55+
final JwtTokenResolverImpl c = copy();
56+
c.tokenParserFactory = tokenParserFactory;
57+
return c;
3758
}
3859

3960
/**
40-
* Constructor.
61+
* Setter for scheduler.
4162
*
42-
* @param keyProvider key provider
43-
* @param tokenParserFactory token parser factoty
44-
* @param scheduler cleanup scheduler
45-
* @param cleanupInterval cleanup interval for resolved cached keys
63+
* @param scheduler scheduler
64+
* @return new instance with applied setting
4665
*/
47-
public JwtTokenResolverImpl(
48-
KeyProvider keyProvider,
49-
JwtTokenParserFactory tokenParserFactory,
50-
Scheduler scheduler,
51-
Duration cleanupInterval) {
52-
this.keyProvider = keyProvider;
53-
this.tokenParserFactory = tokenParserFactory;
54-
this.scheduler = scheduler;
55-
this.cleanupInterval = cleanupInterval;
66+
public JwtTokenResolverImpl scheduler(Scheduler scheduler) {
67+
final JwtTokenResolverImpl c = copy();
68+
c.scheduler = scheduler;
69+
return c;
70+
}
71+
72+
/**
73+
* Setter for cleanupInterval.
74+
*
75+
* @param cleanupInterval cleanupInterval
76+
* @return new instance with applied setting
77+
*/
78+
public JwtTokenResolverImpl cleanupInterval(Duration cleanupInterval) {
79+
final JwtTokenResolverImpl c = copy();
80+
c.cleanupInterval = cleanupInterval;
81+
return c;
5682
}
5783

5884
@Override
@@ -69,8 +95,7 @@ public Mono<Map<String, Object>> resolve(String token) {
6995
Map<String, Object> body = jwtToken.body();
7096
String aud = (String) body.get("aud"); // optional
7197

72-
LOGGER.debug(
73-
"[resolveToken][aud:{}][kid:{}] Resolving token {}", aud, kid, Utils.mask(token));
98+
LOGGER.debug("[resolveToken][aud:{}][kid:{}] Resolving token {}", aud, kid, mask(token));
7499

75100
// workaround to remove safely on errors
76101
AtomicReference<Mono<Key>> computedValueHolder = new AtomicReference<>();
@@ -84,15 +109,15 @@ public Mono<Map<String, Object>> resolve(String token) {
84109
"[resolveToken][aud:{}][kid:{}][{}] Exception occurred: {}",
85110
aud,
86111
kid,
87-
Utils.mask(token),
112+
mask(token),
88113
throwable.toString()))
89114
.doOnSuccess(
90115
s ->
91116
LOGGER.debug(
92117
"[resolveToken][aud:{}][kid:{}] Resolved token {}",
93118
aud,
94119
kid,
95-
Utils.mask(token)));
120+
mask(token)));
96121
});
97122
}
98123

@@ -122,7 +147,14 @@ private void cleanup(String kid, AtomicReference<Mono<Key>> computedValueHolder)
122147
}
123148
}
124149

125-
private static Scheduler newScheduler() {
126-
return Schedulers.newElastic("token-resolver-cleaner", 60, true);
150+
private static String mask(String data) {
151+
if (data == null || data.isEmpty() || data.length() < 5) {
152+
return "*****";
153+
}
154+
return data.replace(data.substring(2, data.length() - 2), "***");
155+
}
156+
157+
private JwtTokenResolverImpl copy() {
158+
return new JwtTokenResolverImpl(this);
127159
}
128160
}

tokens/src/main/java/io/scalecube/security/tokens/jwt/Utils.java

Lines changed: 0 additions & 37 deletions
This file was deleted.

0 commit comments

Comments
 (0)