Skip to content

Commit cb956ea

Browse files
committed
Added JwtUnavailableException
1 parent c61af09 commit cb956ea

File tree

5 files changed

+70
-23
lines changed

5 files changed

+70
-23
lines changed

tests/src/test/java/io/scalecube/security/tokens/jwt/JsonwebtokenResolverTests.java

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

33
import static io.scalecube.security.environment.VaultEnvironment.getRootCause;
4+
import static org.hamcrest.CoreMatchers.instanceOf;
5+
import static org.hamcrest.MatcherAssert.assertThat;
6+
import static org.hamcrest.core.StringStartsWith.startsWith;
47
import static org.junit.jupiter.api.Assertions.assertNotNull;
58
import static org.junit.jupiter.api.Assertions.assertTrue;
69
import static org.junit.jupiter.api.Assertions.fail;
@@ -14,7 +17,6 @@
1417
import java.time.Duration;
1518
import java.util.concurrent.TimeUnit;
1619
import org.junit.jupiter.api.AfterAll;
17-
import org.junit.jupiter.api.Assertions;
1820
import org.junit.jupiter.api.BeforeAll;
1921
import org.junit.jupiter.api.Test;
2022

@@ -50,8 +52,8 @@ void testResolveTokenSuccessfully() throws Exception {
5052
.get(3, TimeUnit.SECONDS);
5153

5254
assertNotNull(jwtToken, "jwtToken");
53-
Assertions.assertTrue(jwtToken.header().size() > 0, "jwtToken.header: " + jwtToken.header());
54-
Assertions.assertTrue(jwtToken.payload().size() > 0, "jwtToken.payload: " + jwtToken.payload());
55+
assertTrue(jwtToken.header().size() > 0, "jwtToken.header: " + jwtToken.header());
56+
assertTrue(jwtToken.payload().size() > 0, "jwtToken.payload: " + jwtToken.payload());
5557
}
5658

5759
@Test
@@ -66,9 +68,25 @@ void testJwksKeyLocatorThrowsError() {
6668
fail("Expected exception");
6769
} catch (Exception e) {
6870
final var ex = getRootCause(e);
69-
assertNotNull(ex);
70-
assertNotNull(ex.getMessage());
71-
assertTrue(ex.getMessage().startsWith("Cannot get key"), "Exception: " + ex);
71+
assertThat(ex, instanceOf(RuntimeException.class));
72+
assertThat(ex.getMessage(), startsWith("Cannot get key"));
73+
}
74+
}
75+
76+
@Test
77+
void testJwksKeyLocatorThrowsRetryableError() {
78+
final var token = generateToken();
79+
80+
Locator<Key> keyLocator = mock(Locator.class);
81+
when(keyLocator.locate(any())).thenThrow(new JwtUnavailableException("JWKS timeout"));
82+
83+
try {
84+
new JsonwebtokenResolver(keyLocator).resolve(token).get(3, TimeUnit.SECONDS);
85+
fail("Expected exception");
86+
} catch (Exception e) {
87+
final var ex = getRootCause(e);
88+
assertThat(ex, instanceOf(JwtUnavailableException.class));
89+
assertThat(ex.getMessage(), startsWith("JWKS timeout"));
7290
}
7391
}
7492

tests/src/test/java/io/scalecube/security/vault/VaultServiceTokenTests.java

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import static io.scalecube.security.environment.VaultEnvironment.getRootCause;
44
import static java.util.concurrent.CompletableFuture.completedFuture;
5+
import static org.hamcrest.MatcherAssert.assertThat;
6+
import static org.hamcrest.core.StringStartsWith.startsWith;
57
import static org.junit.jupiter.api.Assertions.assertNotNull;
68
import static org.junit.jupiter.api.Assertions.assertTrue;
79
import static org.junit.jupiter.api.Assertions.fail;
@@ -18,7 +20,6 @@
1820
import java.util.concurrent.ExecutionException;
1921
import java.util.concurrent.TimeUnit;
2022
import org.junit.jupiter.api.AfterAll;
21-
import org.junit.jupiter.api.Assertions;
2223
import org.junit.jupiter.api.BeforeAll;
2324
import org.junit.jupiter.api.Test;
2425

@@ -54,9 +55,7 @@ void testGetServiceTokenUsingWrongCredentials() throws Exception {
5455
} catch (ExecutionException e) {
5556
final var ex = getRootCause(e);
5657
assertNotNull(ex);
57-
assertNotNull(ex.getMessage());
58-
assertTrue(
59-
ex.getMessage().contains("Failed to get service token, status=403"), "Exception: " + ex);
58+
assertThat(ex.getMessage(), startsWith("Failed to get service token, status=403"));
6059
}
6160
}
6261

@@ -78,9 +77,7 @@ void testGetNonExistingServiceToken() throws Exception {
7877
} catch (ExecutionException e) {
7978
final var ex = getRootCause(e);
8079
assertNotNull(ex);
81-
assertNotNull(ex.getMessage());
82-
assertTrue(
83-
ex.getMessage().contains("Failed to get service token, status=400"), "Exception: " + ex);
80+
assertThat(ex.getMessage(), startsWith("Failed to get service token, status=400"));
8481
}
8582
}
8683

@@ -122,9 +119,7 @@ void testGetServiceTokenByWrongServiceRole() throws Exception {
122119
} catch (ExecutionException e) {
123120
final var ex = getRootCause(e);
124121
assertNotNull(ex);
125-
assertNotNull(ex.getMessage());
126-
assertTrue(
127-
ex.getMessage().contains("Failed to get service token, status=400"), "Exception: " + ex);
122+
assertThat(ex.getMessage(), startsWith("Failed to get service token, status=400"));
128123
}
129124
}
130125

@@ -164,8 +159,8 @@ void testGetServiceTokenSuccessfully() throws Exception {
164159
.get(3, TimeUnit.SECONDS);
165160

166161
assertNotNull(jwtToken, "jwtToken");
167-
Assertions.assertTrue(jwtToken.header().size() > 0, "jwtToken.header: " + jwtToken.header());
168-
Assertions.assertTrue(jwtToken.payload().size() > 0, "jwtToken.payload: " + jwtToken.payload());
162+
assertTrue(jwtToken.header().size() > 0, "jwtToken.header: " + jwtToken.header());
163+
assertTrue(jwtToken.payload().size() > 0, "jwtToken.payload: " + jwtToken.payload());
169164
}
170165

171166
private static String toQualifiedName(String role, Map<String, String> tags) {

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

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import java.net.http.HttpRequest;
1818
import java.net.http.HttpResponse;
1919
import java.net.http.HttpResponse.BodyHandlers;
20+
import java.net.http.HttpTimeoutException;
2021
import java.security.Key;
2122
import java.security.KeyFactory;
2223
import java.security.PublicKey;
@@ -55,13 +56,11 @@ protected Key locate(JwsHeader header) {
5556
kid -> {
5657
final var key = findKeyById(computeKeyList(), kid);
5758
if (key == null) {
58-
throw new RuntimeException("Cannot find key by kid: " + kid);
59+
throw new JwtUnavailableException("Cannot find key by kid: " + kid);
5960
}
6061
return new CachedKey(key, System.currentTimeMillis() + keyTtl);
6162
})
6263
.key();
63-
} catch (Exception ex) {
64-
throw new JwtTokenException(ex);
6564
} finally {
6665
tryCleanup();
6766
}
@@ -77,8 +76,13 @@ private JwkInfoList computeKeyList() {
7776
.send(
7877
HttpRequest.newBuilder(jwksUri).GET().timeout(requestTimeout).build(),
7978
BodyHandlers.ofInputStream());
80-
} catch (Exception e) {
81-
throw new RuntimeException("Failed to retrive jwk keys", e);
79+
} catch (HttpTimeoutException e) {
80+
throw new JwtUnavailableException("Failed to retrive jwk keys", e);
81+
} catch (IOException e) {
82+
throw new RuntimeException(e);
83+
} catch (InterruptedException e) {
84+
Thread.currentThread().interrupt();
85+
throw new RuntimeException(e);
8286
}
8387

8488
final var statusCode = httpResponse.statusCode();

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
import java.util.StringJoiner;
44

5+
/**
6+
* Generic exception type for JWT token resolution errors. Used as part {@link JwtTokenResolver}
7+
* mechanism, and responsible to abstract token resolution problems.
8+
*/
59
public class JwtTokenException extends RuntimeException {
610

711
public JwtTokenException(String message) {
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package io.scalecube.security.tokens.jwt;
2+
3+
/**
4+
* Special JWT exception type indicating transient error during token resolution. For example such
5+
* transient errors are:
6+
*
7+
* <ul>
8+
* <li>Key Rotation: JWKS endpoints often implement key rotation policies where keys are
9+
* periodically changed for security reasons. If the JWT was issued with a "kid" that
10+
* corresponds to a key that has since been rotated out, that key won't be available in the
11+
* JWKS anymore.
12+
* <li>Network or Server Issues: if the JWKS URI is temporarily down, inaccessible, or
13+
* experiencing issues, cleint might not be able to retrieve the keys, or the list of keys
14+
* might be incomplete or outdated.
15+
* </ul>
16+
*/
17+
public class JwtUnavailableException extends JwtTokenException {
18+
19+
public JwtUnavailableException(String message) {
20+
super(message);
21+
}
22+
23+
public JwtUnavailableException(String message, Throwable cause) {
24+
super(message, cause);
25+
}
26+
}

0 commit comments

Comments
 (0)