1
1
package io .scalecube .security .tokens .jwt ;
2
2
3
- import static io .scalecube .security .tokens .jwt .Utils .toRsaPublicKey ;
4
-
5
3
import com .fasterxml .jackson .annotation .JsonAutoDetect ;
6
4
import com .fasterxml .jackson .annotation .JsonInclude ;
7
5
import com .fasterxml .jackson .annotation .PropertyAccessor ;
11
9
import java .io .BufferedInputStream ;
12
10
import java .io .IOException ;
13
11
import java .io .InputStream ;
12
+ import java .math .BigInteger ;
14
13
import java .net .HttpURLConnection ;
15
14
import java .net .URL ;
16
15
import java .security .Key ;
16
+ import java .security .KeyFactory ;
17
+ import java .security .spec .KeySpec ;
18
+ import java .security .spec .RSAPublicKeySpec ;
17
19
import java .time .Duration ;
20
+ import java .util .Base64 ;
21
+ import java .util .Base64 .Decoder ;
18
22
import java .util .Optional ;
19
23
import org .slf4j .Logger ;
20
24
import org .slf4j .LoggerFactory ;
21
25
import reactor .core .Exceptions ;
22
26
import reactor .core .publisher .Mono ;
23
- import reactor .core .scheduler .Scheduler ;
24
27
import reactor .core .scheduler .Schedulers ;
25
28
26
29
public final class JwksKeyProvider implements KeyProvider {
27
30
28
31
private static final Logger LOGGER = LoggerFactory .getLogger (JwksKeyProvider .class );
29
32
30
- private static final Duration CONNECT_TIMEOUT = Duration .ofSeconds (10 );
31
- private static final Duration READ_TIMEOUT = Duration .ofSeconds (10 );
32
-
33
33
private static final ObjectMapper OBJECT_MAPPER = newObjectMapper ();
34
34
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
+ }
39
46
40
47
/**
41
- * Constructor .
48
+ * Setter for jwksUri .
42
49
*
43
50
* @param jwksUri jwksUri
51
+ * @return new instance with applied setting
44
52
*/
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 ;
47
57
}
48
58
49
59
/**
50
- * Constructor .
60
+ * Setter for connectTimeout .
51
61
*
52
- * @param jwksUri jwksUri
53
- * @param scheduler scheduler
54
62
* @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
+ *
55
74
* @param readTimeout readTimeout
75
+ * @return new instance with applied setting
56
76
*/
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 ;
63
81
}
64
82
65
83
@ Override
66
84
public Mono <Key > findKey (String kid ) {
67
85
return computeKey (kid )
68
86
.switchIfEmpty (Mono .error (new KeyNotFoundException ("Key was not found, kid: " + kid )))
69
87
.doOnSubscribe (s -> LOGGER .debug ("[findKey] Looking up key in jwks, kid: {}" , kid ))
70
- .subscribeOn (scheduler );
88
+ .subscribeOn (Schedulers .boundedElastic ())
89
+ .publishOn (Schedulers .boundedElastic ());
71
90
}
72
91
73
92
private Mono <Key > computeKey (String kid ) {
@@ -78,8 +97,8 @@ private Mono<Key> computeKey(String kid) {
78
97
79
98
private JwkInfoList computeKeyList () throws IOException {
80
99
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 () );
83
102
84
103
int responseCode = httpClient .getResponseCode ();
85
104
if (responseCode != 200 ) {
@@ -106,6 +125,18 @@ private Optional<Key> findRsaKey(JwkInfoList list, String kid) {
106
125
.map (info -> toRsaPublicKey (info .modulus (), info .exponent ()));
107
126
}
108
127
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
+
109
140
private static ObjectMapper newObjectMapper () {
110
141
ObjectMapper mapper = new ObjectMapper ();
111
142
mapper .configure (DeserializationFeature .FAIL_ON_UNKNOWN_PROPERTIES , false );
@@ -117,7 +148,7 @@ private static ObjectMapper newObjectMapper() {
117
148
return mapper ;
118
149
}
119
150
120
- private static Scheduler newScheduler () {
121
- return Schedulers . newElastic ( "jwks-key-provider" , 60 , true );
151
+ private JwksKeyProvider copy () {
152
+ return new JwksKeyProvider ( this );
122
153
}
123
154
}
0 commit comments