Skip to content

Commit 9ed4bde

Browse files
authored
Merge pull request #88 from scalecube/github-actions
Enhanced jwks-provider tests
2 parents a78abfa + 254b338 commit 9ed4bde

File tree

7 files changed

+176
-161
lines changed

7 files changed

+176
-161
lines changed

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

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

3+
import static io.scalecube.security.tokens.jwt.Utils.toRsaPublicKey;
4+
35
import com.fasterxml.jackson.annotation.JsonAutoDetect;
46
import com.fasterxml.jackson.annotation.JsonInclude;
57
import com.fasterxml.jackson.annotation.PropertyAccessor;
@@ -24,9 +26,9 @@ public final class JwksKeyProvider implements KeyProvider {
2426

2527
private static final Logger LOGGER = LoggerFactory.getLogger(JwksKeyProvider.class);
2628

27-
private final Scheduler scheduler = Schedulers.newSingle("jwks-key-provider", true);
29+
private static final ObjectMapper OBJECT_MAPPER = newObjectMapper();
2830

29-
private final ObjectMapper mapper;
31+
private final Scheduler scheduler;
3032
private final String jwksUri;
3133
private final long connectTimeoutMillis;
3234
private final long readTimeoutMillis;
@@ -37,22 +39,21 @@ public final class JwksKeyProvider implements KeyProvider {
3739
* @param jwksUri jwksUri
3840
*/
3941
public JwksKeyProvider(String jwksUri) {
40-
this.jwksUri = jwksUri;
41-
this.mapper = initMapper();
42-
this.connectTimeoutMillis = Duration.ofSeconds(10).toMillis();
43-
this.readTimeoutMillis = Duration.ofSeconds(10).toMillis();
42+
this(jwksUri, newScheduler(), Duration.ofSeconds(10), Duration.ofSeconds(10));
4443
}
4544

4645
/**
4746
* Constructor.
4847
*
4948
* @param jwksUri jwksUri
49+
* @param scheduler scheduler
5050
* @param connectTimeout connectTimeout
5151
* @param readTimeout readTimeout
5252
*/
53-
public JwksKeyProvider(String jwksUri, Duration connectTimeout, Duration readTimeout) {
53+
public JwksKeyProvider(
54+
String jwksUri, Scheduler scheduler, Duration connectTimeout, Duration readTimeout) {
5455
this.jwksUri = jwksUri;
55-
this.mapper = initMapper();
56+
this.scheduler = scheduler;
5657
this.connectTimeoutMillis = connectTimeout.toMillis();
5758
this.readTimeoutMillis = readTimeout.toMillis();
5859
}
@@ -87,7 +88,7 @@ private Mono<InputStream> callJwksUri() {
8788

8889
private JwkInfoList toKeyList(InputStream stream) {
8990
try (InputStream inputStream = new BufferedInputStream(stream)) {
90-
return mapper.readValue(inputStream, JwkInfoList.class);
91+
return OBJECT_MAPPER.readValue(inputStream, JwkInfoList.class);
9192
} catch (IOException e) {
9293
LOGGER.error("[toKeyList] Exception occurred: {}", e.toString());
9394
throw new KeyProviderException(e);
@@ -98,10 +99,10 @@ private Optional<Key> findRsaKey(JwkInfoList list, String kid) {
9899
return list.keys().stream()
99100
.filter(k -> kid.equals(k.kid()))
100101
.findFirst()
101-
.map(info -> Utils.getRsaPublicKey(info.modulus(), info.exponent()));
102+
.map(info -> toRsaPublicKey(info.modulus(), info.exponent()));
102103
}
103104

104-
private static ObjectMapper initMapper() {
105+
private static ObjectMapper newObjectMapper() {
105106
ObjectMapper mapper = new ObjectMapper();
106107
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
107108
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
@@ -111,4 +112,8 @@ private static ObjectMapper initMapper() {
111112
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
112113
return mapper;
113114
}
115+
116+
private static Scheduler newScheduler() {
117+
return Schedulers.newElastic("jwks-key-provider", 60, true);
118+
}
114119
}

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

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

3+
import io.scalecube.security.tokens.jwt.jsonwebtoken.JsonwebtokenParserFactory;
34
import java.security.Key;
5+
import java.time.Duration;
46
import java.util.Map;
57
import java.util.Objects;
68
import java.util.concurrent.ConcurrentHashMap;
@@ -18,37 +20,36 @@ public final class JwtTokenResolverImpl implements JwtTokenResolver {
1820

1921
private final KeyProvider keyProvider;
2022
private final JwtTokenParserFactory tokenParserFactory;
21-
private final int cleanupIntervalSec;
2223
private final Scheduler scheduler;
24+
private final Duration cleanupInterval;
2325

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

2628
/**
2729
* Constructor.
2830
*
2931
* @param keyProvider key provider
30-
* @param tokenParserFactory token parser factoty
3132
*/
32-
public JwtTokenResolverImpl(KeyProvider keyProvider, JwtTokenParserFactory tokenParserFactory) {
33-
this(keyProvider, tokenParserFactory, 3600, Schedulers.newSingle("caching-key-provider", true));
33+
public JwtTokenResolverImpl(KeyProvider keyProvider) {
34+
this(keyProvider, new JsonwebtokenParserFactory(), newScheduler(), Duration.ofSeconds(60));
3435
}
3536

3637
/**
3738
* Constructor.
3839
*
3940
* @param keyProvider key provider
4041
* @param tokenParserFactory token parser factoty
41-
* @param cleanupIntervalSec cleanup interval (in sec) for resolved cached keys
4242
* @param scheduler cleanup scheduler
43+
* @param cleanupInterval cleanup interval for resolved cached keys
4344
*/
4445
public JwtTokenResolverImpl(
4546
KeyProvider keyProvider,
4647
JwtTokenParserFactory tokenParserFactory,
47-
int cleanupIntervalSec,
48-
Scheduler scheduler) {
48+
Scheduler scheduler,
49+
Duration cleanupInterval) {
4950
this.keyProvider = keyProvider;
5051
this.tokenParserFactory = tokenParserFactory;
51-
this.cleanupIntervalSec = cleanupIntervalSec;
52+
this.cleanupInterval = cleanupInterval;
5253
this.scheduler = scheduler;
5354
}
5455

@@ -107,12 +108,16 @@ private Mono<Key> findKey(String kid, AtomicReference<Mono<Key>> computedValueHo
107108

108109
private void scheduleCleanup(String kid, AtomicReference<Mono<Key>> computedValueHolder) {
109110
scheduler.schedule(
110-
() -> cleanup(kid, computedValueHolder), cleanupIntervalSec, TimeUnit.SECONDS);
111+
() -> cleanup(kid, computedValueHolder), cleanupInterval.toMillis(), TimeUnit.MILLISECONDS);
111112
}
112113

113114
private void cleanup(String kid, AtomicReference<Mono<Key>> computedValueHolder) {
114115
if (computedValueHolder.get() != null) {
115116
keyResolutions.remove(kid, computedValueHolder.get());
116117
}
117118
}
119+
120+
private static Scheduler newScheduler() {
121+
return Schedulers.newElastic("token-resolver-cleaner", 60, true);
122+
}
118123
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ private Utils() {
2222
* @param e exponent (b64 url encoded)
2323
* @return RSA public key instance
2424
*/
25-
public static Key getRsaPublicKey(String n, String e) {
25+
public static Key toRsaPublicKey(String n, String e) {
2626
Decoder b64Decoder = Base64.getUrlDecoder();
2727
BigInteger modulus = new BigInteger(1, b64Decoder.decode(n));
2828
BigInteger exponent = new BigInteger(1, b64Decoder.decode(e));

tokens/src/test/java/io/scalecube/security/tokens/jwt/BaseTest.java

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

tokens/src/test/java/io/scalecube/security/tokens/jwt/JwtTokenResolverTests.java

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

3-
import java.io.IOException;
3+
import static io.scalecube.security.tokens.jwt.Utils.toRsaPublicKey;
4+
45
import java.security.Key;
6+
import java.time.Duration;
57
import java.util.Collections;
68
import java.util.Map;
79
import java.util.Properties;
@@ -10,13 +12,14 @@
1012
import org.mockito.Mockito;
1113
import reactor.core.publisher.Mono;
1214
import reactor.test.StepVerifier;
15+
import reactor.test.scheduler.VirtualTimeScheduler;
1316

14-
class JwtTokenResolverTests extends BaseTest {
17+
class JwtTokenResolverTests {
1518

1619
private static final Map<String, Object> BODY = Collections.singletonMap("aud", "aud");
1720

1821
@Test
19-
void testTokenResolver() throws IOException {
22+
void testTokenResolver() throws Exception {
2023
TokenWithKey tokenWithKey = new TokenWithKey("token-and-pubkey.properties");
2124

2225
JwtTokenParser tokenParser = Mockito.mock(JwtTokenParser.class);
@@ -32,7 +35,9 @@ void testTokenResolver() throws IOException {
3235
KeyProvider keyProvider = Mockito.mock(KeyProvider.class);
3336
Mockito.when(keyProvider.findKey(tokenWithKey.kid)).thenReturn(Mono.just(tokenWithKey.key));
3437

35-
JwtTokenResolverImpl tokenResolver = new JwtTokenResolverImpl(keyProvider, tokenParserFactory);
38+
JwtTokenResolverImpl tokenResolver =
39+
new JwtTokenResolverImpl(
40+
keyProvider, tokenParserFactory, VirtualTimeScheduler.create(), Duration.ofSeconds(3));
3641

3742
// N times call resolve
3843
StepVerifier.create(tokenResolver.resolve(tokenWithKey.token).repeat(3))
@@ -45,7 +50,7 @@ void testTokenResolver() throws IOException {
4550
}
4651

4752
@Test
48-
void testTokenResolverWithRotatingKey() throws IOException {
53+
void testTokenResolverWithRotatingKey() throws Exception {
4954
TokenWithKey tokenWithKey = new TokenWithKey("token-and-pubkey.properties");
5055
TokenWithKey tokenWithKeyAfterRotation =
5156
new TokenWithKey("token-and-pubkey.after-rotation.properties");
@@ -70,7 +75,9 @@ void testTokenResolverWithRotatingKey() throws IOException {
7075
Mockito.when(keyProvider.findKey(tokenWithKeyAfterRotation.kid))
7176
.thenReturn(Mono.just(tokenWithKeyAfterRotation.key));
7277

73-
JwtTokenResolverImpl tokenResolver = new JwtTokenResolverImpl(keyProvider, tokenParserFactory);
78+
JwtTokenResolverImpl tokenResolver =
79+
new JwtTokenResolverImpl(
80+
keyProvider, tokenParserFactory, VirtualTimeScheduler.create(), Duration.ofSeconds(3));
7481

7582
// Call normal token first
7683
StepVerifier.create(tokenResolver.resolve(tokenWithKey.token))
@@ -90,7 +97,7 @@ void testTokenResolverWithRotatingKey() throws IOException {
9097
}
9198

9299
@Test
93-
void testTokenResolverWithWrongKey() throws IOException {
100+
void testTokenResolverWithWrongKey() throws Exception {
94101
TokenWithKey tokenWithWrongKey = new TokenWithKey("token-and-wrong-pubkey.properties");
95102

96103
JwtTokenParser tokenParser = Mockito.mock(JwtTokenParser.class);
@@ -106,7 +113,9 @@ void testTokenResolverWithWrongKey() throws IOException {
106113
Mockito.when(keyProvider.findKey(tokenWithWrongKey.kid))
107114
.thenReturn(Mono.just(tokenWithWrongKey.key));
108115

109-
JwtTokenResolverImpl tokenResolver = new JwtTokenResolverImpl(keyProvider, tokenParserFactory);
116+
JwtTokenResolverImpl tokenResolver =
117+
new JwtTokenResolverImpl(
118+
keyProvider, tokenParserFactory, VirtualTimeScheduler.create(), Duration.ofSeconds(3));
110119

111120
// Must fail (retry N times)
112121
StepVerifier.create(tokenResolver.resolve(tokenWithWrongKey.token).retry(1))
@@ -118,7 +127,7 @@ void testTokenResolverWithWrongKey() throws IOException {
118127
}
119128

120129
@Test
121-
void testTokenResolverWhenKeyProviderFailing() throws IOException {
130+
void testTokenResolverWhenKeyProviderFailing() throws Exception {
122131
TokenWithKey tokenWithKey = new TokenWithKey("token-and-pubkey.properties");
123132

124133
JwtTokenParser tokenParser = Mockito.mock(JwtTokenParser.class);
@@ -134,7 +143,9 @@ void testTokenResolverWhenKeyProviderFailing() throws IOException {
134143
KeyProvider keyProvider = Mockito.mock(KeyProvider.class);
135144
Mockito.when(keyProvider.findKey(tokenWithKey.kid)).thenThrow(RuntimeException.class);
136145

137-
JwtTokenResolverImpl tokenResolver = new JwtTokenResolverImpl(keyProvider, tokenParserFactory);
146+
JwtTokenResolverImpl tokenResolver =
147+
new JwtTokenResolverImpl(
148+
keyProvider, tokenParserFactory, VirtualTimeScheduler.create(), Duration.ofSeconds(3));
138149

139150
// Must fail with "hola" (retry N times)
140151
StepVerifier.create(tokenResolver.resolve(tokenWithKey.token).retry(1)).expectError().verify();
@@ -149,13 +160,13 @@ static class TokenWithKey {
149160
final Key key;
150161
final String kid;
151162

152-
TokenWithKey(String s) throws IOException {
163+
TokenWithKey(String s) throws Exception {
153164
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
154165
Properties props = new Properties();
155166
props.load(classLoader.getResourceAsStream(s));
156167
this.token = props.getProperty("token");
157168
this.kid = props.getProperty("kid");
158-
this.key = Utils.getRsaPublicKey(props.getProperty("n"), props.getProperty("e"));
169+
this.key = toRsaPublicKey(props.getProperty("n"), props.getProperty("e"));
159170
}
160171
}
161172
}

0 commit comments

Comments
 (0)