@@ -27,6 +27,7 @@ import com.nimbusds.jose.util.Resource
27
27
import com.nimbusds.jwt.JWTClaimsSet
28
28
import com.nimbusds.jwt.JWTParser
29
29
import com.nimbusds.jwt.proc.DefaultJWTProcessor
30
+ import com.nimbusds.jwt.proc.JWTProcessor
30
31
import io.ktor.client.HttpClient
31
32
import io.ktor.client.request.get
32
33
import io.ktor.client.statement.bodyAsText
@@ -46,7 +47,6 @@ import java.util.Base64
46
47
import java.util.Date
47
48
import java.util.UUID
48
49
import javax.crypto.spec.SecretKeySpec
49
- import kotlin.String
50
50
51
51
class ModelixJWTUtil {
52
52
private var hmacKeys = LinkedHashMap <JWSAlgorithm , ByteArray >()
@@ -57,28 +57,73 @@ class ModelixJWTUtil {
57
57
private var ktorClient: HttpClient ? = null
58
58
var accessControlDataProvider: IAccessControlDataProvider = EmptyAccessControlDataProvider ()
59
59
60
+ private var jwtProcessor: JWTProcessor <SecurityContext >? = null
61
+
62
+ @Synchronized
63
+ private fun getOrCreateJwtProcessor (): JWTProcessor <SecurityContext > {
64
+ return jwtProcessor ? : DefaultJWTProcessor <SecurityContext >().also { processor ->
65
+ val keySelectors: List <JWSKeySelector <SecurityContext >> = hmacKeys.map { it.toPair() }.map {
66
+ SingleKeyJWSKeySelector <SecurityContext >(it.first, SecretKeySpec (it.second, it.first.name))
67
+ } + jwksUrls.map {
68
+ val client = this .ktorClient
69
+ if (client == null ) {
70
+ JWSAlgorithmFamilyJWSKeySelector .fromJWKSetURL<SecurityContext >(it)
71
+ } else {
72
+ JWSAlgorithmFamilyJWSKeySelector .fromJWKSource<SecurityContext >(RemoteJWKSet (it, KtorResourceRetriever (client)))
73
+ }
74
+ } + rsaPublicKeys.map {
75
+ JWSAlgorithmFamilyJWSKeySelector .fromJWKSource<SecurityContext >(ImmutableJWKSet (JWKSet (it.toPublicJWK())))
76
+ }
77
+
78
+ processor.jwsKeySelector = if (keySelectors.size == 1 ) keySelectors.single() else CompositeJWSKeySelector (keySelectors)
79
+
80
+ val expectedKeyId = this .expectedKeyId
81
+ if (expectedKeyId != null ) {
82
+ processor.jwsVerifierFactory = object : DefaultJWSVerifierFactory () {
83
+ override fun createJWSVerifier (header : JWSHeader , key : Key ): JWSVerifier {
84
+ if (header.keyID != expectedKeyId) {
85
+ throw BadJOSEException (" Invalid key ID. [expected=$expectedKeyId , actual=${header.keyID} ]" )
86
+ }
87
+ return super .createJWSVerifier(header, key)
88
+ }
89
+ }
90
+ }
91
+ }.also { jwtProcessor = it }
92
+ }
93
+
94
+ private fun resetJwtProcess () {
95
+ jwtProcessor = null
96
+ }
97
+
98
+ @Synchronized
60
99
fun canVerifyTokens (): Boolean {
61
100
return hmacKeys.isNotEmpty() || rsaPublicKeys.isNotEmpty() || jwksUrls.isNotEmpty()
62
101
}
63
102
64
103
/* *
65
104
* Tokens are only valid if they are signed with this key.
66
105
*/
106
+ @Synchronized
67
107
fun requireKeyId (id : String ) {
68
108
expectedKeyId = id
69
109
}
70
110
111
+ @Synchronized
71
112
fun useKtorClient (client : HttpClient ) {
113
+ resetJwtProcess()
72
114
this .ktorClient = client.config {
73
115
expectSuccess = true
74
116
}
75
117
}
76
118
119
+ @Synchronized
77
120
fun addJwksUrl (url : String ) {
78
121
addJwksUrl(URI (url).toURL())
79
122
}
80
123
124
+ @Synchronized
81
125
fun addJwksUrl (url : URL ) {
126
+ resetJwtProcess()
82
127
jwksUrls + = url
83
128
}
84
129
@@ -91,28 +136,37 @@ class ModelixJWTUtil {
91
136
addHmacKey(key.toByteArray().ensureMinSecretLength(algorithm), algorithm)
92
137
}
93
138
139
+ @Synchronized
94
140
fun addPublicKey (key : JWK ) {
95
141
requireNotNull(key.keyID) { " Key doesn't specify a key ID: $key " }
96
142
requireNotNull(key.algorithm) { " Key doesn't specify an algorithm: $key " }
143
+ resetJwtProcess()
97
144
rsaPublicKeys.add(key)
98
145
}
99
146
147
+ @Synchronized
100
148
fun setRSAPrivateKey (key : JWK ) {
101
149
requireNotNull(key.keyID) { " Key doesn't specify a key ID: $key " }
102
150
requireNotNull(key.algorithm) { " Key doesn't specify an algorithm: $key " }
151
+ resetJwtProcess()
103
152
this .rsaPrivateKey = key
104
153
addPublicKey(key.toPublicJWK())
105
154
}
106
155
156
+ @Synchronized
107
157
private fun addHmacKey (key : ByteArray , algorithm : JWSAlgorithm ) {
158
+ resetJwtProcess()
108
159
hmacKeys[algorithm] = key
109
160
}
110
161
162
+ @Synchronized
111
163
fun getPublicJWKS (): JWKSet {
112
164
return JWKSet (listOfNotNull(rsaPrivateKey)).toPublicJWKSet()
113
165
}
114
166
167
+ @Synchronized
115
168
fun loadKeysFromEnvironment () {
169
+ resetJwtProcess()
116
170
System .getenv().filter { it.key.startsWith(" MODELIX_JWK_FILE" ) }.values.forEach {
117
171
File (it).walk().forEach { file ->
118
172
when (file.extension) {
@@ -127,6 +181,7 @@ class ModelixJWTUtil {
127
181
.forEach { addJwksUrl(URI (it).toURL()) }
128
182
}
129
183
184
+ @Synchronized
130
185
fun createAccessToken (user : String , grantedPermissions : List <String >, additionalTokenContent : (TokenBuilder ) -> Unit = {}): String {
131
186
val signer: JWSSigner
132
187
val algorithm: JWSAlgorithm
@@ -174,6 +229,7 @@ class ModelixJWTUtil {
174
229
return token.claims[ModelixTokenConstants .PERMISSIONS ]?.asList(String ::class .java)
175
230
}
176
231
232
+ @Synchronized
177
233
fun loadGrantedPermissions (token : DecodedJWT , evaluator : PermissionEvaluator ) {
178
234
val permissions = extractPermissions(token)
179
235
@@ -252,42 +308,17 @@ class ModelixJWTUtil {
252
308
}
253
309
254
310
private fun loadJwk (key : JWK ) {
311
+ resetJwtProcess()
255
312
if (key.isPrivate) {
256
313
setRSAPrivateKey(key)
257
314
} else {
258
315
addPublicKey(key)
259
316
}
260
317
}
261
318
319
+ @Synchronized
262
320
fun verifyToken (token : String ) {
263
- DefaultJWTProcessor <SecurityContext >().also { processor ->
264
- val keySelectors: List <JWSKeySelector <SecurityContext >> = hmacKeys.map { it.toPair() }.map {
265
- SingleKeyJWSKeySelector <SecurityContext >(it.first, SecretKeySpec (it.second, it.first.name))
266
- } + jwksUrls.map {
267
- val client = this .ktorClient
268
- if (client == null ) {
269
- JWSAlgorithmFamilyJWSKeySelector .fromJWKSetURL<SecurityContext >(it)
270
- } else {
271
- JWSAlgorithmFamilyJWSKeySelector .fromJWKSource<SecurityContext >(RemoteJWKSet (it, KtorResourceRetriever (client)))
272
- }
273
- } + rsaPublicKeys.map {
274
- JWSAlgorithmFamilyJWSKeySelector .fromJWKSource<SecurityContext >(ImmutableJWKSet (JWKSet (it.toPublicJWK())))
275
- }
276
-
277
- processor.jwsKeySelector = if (keySelectors.size == 1 ) keySelectors.single() else CompositeJWSKeySelector (keySelectors)
278
-
279
- val expectedKeyId = this .expectedKeyId
280
- if (expectedKeyId != null ) {
281
- processor.jwsVerifierFactory = object : DefaultJWSVerifierFactory () {
282
- override fun createJWSVerifier (header : JWSHeader , key : Key ): JWSVerifier {
283
- if (header.keyID != expectedKeyId) {
284
- throw BadJOSEException (" Invalid key ID. [expected=$expectedKeyId , actual=${header.keyID} ]" )
285
- }
286
- return super .createJWSVerifier(header, key)
287
- }
288
- }
289
- }
290
- }.process(JWTParser .parse(token), null )
321
+ getOrCreateJwtProcessor().process(JWTParser .parse(token), null )
291
322
}
292
323
293
324
class TokenBuilder (private val builder : JWTClaimsSet .Builder ) {
0 commit comments