001/* 002 * nimbus-jose-jwt 003 * 004 * Copyright 2012-2022, Connect2id Ltd. 005 * 006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use 007 * this file except in compliance with the License. You may obtain a copy of the 008 * License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software distributed 013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the 015 * specific language governing permissions and limitations under the License. 016 */ 017 018package com.nimbusds.jose.jwk.source; 019 020 021import java.net.URL; 022import java.util.Objects; 023 024import com.nimbusds.jose.proc.SecurityContext; 025import com.nimbusds.jose.util.DefaultResourceRetriever; 026import com.nimbusds.jose.util.ResourceRetriever; 027import com.nimbusds.jose.util.events.EventListener; 028import com.nimbusds.jose.util.health.HealthReportListener; 029 030 031/** 032 * {@linkplain JWKSource} builder. 033 * 034 * <p>Supports wrapping of a JWK set source, typically a URL, with the 035 * following capabilities: 036 * 037 * <ul> 038 * <li>{@linkplain CachingJWKSetSource caching} 039 * <li>{@linkplain RefreshAheadCachingJWKSetSource caching with refresh ahead} 040 * <li>{@linkplain RateLimitedJWKSetSource rate limiting} 041 * <li>{@linkplain RetryingJWKSetSource retrial} 042 * <li>{@linkplain JWKSourceWithFailover fail-over} 043 * <li>{@linkplain JWKSetSourceWithHealthStatusReporting health status reporting} 044 * <li>{@linkplain OutageTolerantJWKSetSource outage tolerance} 045 * </ul> 046 * 047 * @author Thomas Rørvik Skjølberg 048 * @author Vladimir Dzhuvinov 049 * @version 2023-12-10 050 */ 051public class JWKSourceBuilder<C extends SecurityContext> { 052 053 054 /** 055 * The default HTTP connect timeout for JWK set retrieval, in 056 * milliseconds. Set to 500 milliseconds. 057 */ 058 public static final int DEFAULT_HTTP_CONNECT_TIMEOUT = RemoteJWKSet.DEFAULT_HTTP_CONNECT_TIMEOUT; 059 060 061 /** 062 * The default HTTP read timeout for JWK set retrieval, in 063 * milliseconds. Set to 500 milliseconds. 064 */ 065 public static final int DEFAULT_HTTP_READ_TIMEOUT = RemoteJWKSet.DEFAULT_HTTP_READ_TIMEOUT; 066 067 068 /** 069 * The default HTTP entity size limit for JWK set retrieval, in bytes. 070 * Set to 50 KBytes. 071 */ 072 public static final int DEFAULT_HTTP_SIZE_LIMIT = RemoteJWKSet.DEFAULT_HTTP_SIZE_LIMIT; 073 074 075 /** 076 * The default time to live of cached JWK sets, in milliseconds. Set to 077 * 5 minutes. 078 */ 079 public static final long DEFAULT_CACHE_TIME_TO_LIVE = 5 * 60 * 1000L; 080 081 082 /** 083 * The default refresh timeout of cached JWK sets, in milliseconds. Set 084 * to 15 seconds. 085 */ 086 public static final long DEFAULT_CACHE_REFRESH_TIMEOUT = 15 * 1000L; 087 088 089 /** 090 * The default afresh-ahead time of cached JWK sets, in milliseconds. 091 * Set to 30 seconds. 092 */ 093 public static final long DEFAULT_REFRESH_AHEAD_TIME = 30_000L; 094 095 096 /** 097 * The default rate limiting minimum allowed time interval between two 098 * JWK set retrievals, in milliseconds. 099 */ 100 public static final long DEFAULT_RATE_LIMIT_MIN_INTERVAL = 30_000L; 101 102 103 /** 104 * Creates a new JWK source builder using the specified JWK set URL 105 * and {@linkplain DefaultResourceRetriever} with default timeouts. 106 * 107 * @param jwkSetURL The JWK set URL. Must not be {@code null}. 108 */ 109 public static <C extends SecurityContext> JWKSourceBuilder<C> create(final URL jwkSetURL) { 110 111 DefaultResourceRetriever retriever = new DefaultResourceRetriever( 112 DEFAULT_HTTP_CONNECT_TIMEOUT, 113 DEFAULT_HTTP_READ_TIMEOUT, 114 DEFAULT_HTTP_SIZE_LIMIT); 115 116 JWKSetSource<C> jwkSetSource = new URLBasedJWKSetSource<>(jwkSetURL, retriever); 117 118 return new JWKSourceBuilder<>(jwkSetSource); 119 } 120 121 122 /** 123 * Creates a new JWK source builder using the specified JWK set URL 124 * and resource retriever. 125 * 126 * @param jwkSetURL The JWK set URL. Must not be {@code null}. 127 * @param retriever The resource retriever. Must not be {@code null}. 128 */ 129 public static <C extends SecurityContext> JWKSourceBuilder<C> create(final URL jwkSetURL, final ResourceRetriever retriever) { 130 return new JWKSourceBuilder<>(new URLBasedJWKSetSource<C>(jwkSetURL, retriever)); 131 } 132 133 134 /** 135 * Creates a new JWK source builder wrapping an existing source. 136 * 137 * @param source The JWK source to wrap. Must not be {@code null}. 138 */ 139 public static <C extends SecurityContext> JWKSourceBuilder<C> create(final JWKSetSource<C> source) { 140 return new JWKSourceBuilder<>(source); 141 } 142 143 // the wrapped source 144 private final JWKSetSource<C> jwkSetSource; 145 146 // caching 147 private boolean caching = true; 148 private long cacheTimeToLive = DEFAULT_CACHE_TIME_TO_LIVE; 149 private long cacheRefreshTimeout = DEFAULT_CACHE_REFRESH_TIMEOUT; 150 private EventListener<CachingJWKSetSource<C>, C> cachingEventListener; 151 152 private boolean refreshAhead = true; 153 private long refreshAheadTime = DEFAULT_REFRESH_AHEAD_TIME; 154 private boolean refreshAheadScheduled = false; 155 156 // rate limiting (retry on network error will not count against this) 157 private boolean rateLimited = true; 158 private long minTimeInterval = DEFAULT_RATE_LIMIT_MIN_INTERVAL; 159 private EventListener<RateLimitedJWKSetSource<C>, C> rateLimitedEventListener; 160 161 // retrying 162 private boolean retrying = false; 163 private EventListener<RetryingJWKSetSource<C>, C> retryingEventListener; 164 165 // outage 166 private boolean outageTolerant = false; 167 private long outageCacheTimeToLive = -1L; 168 private EventListener<OutageTolerantJWKSetSource<C>, C> outageEventListener; 169 170 // health status reporting 171 private HealthReportListener<JWKSetSourceWithHealthStatusReporting<C>, C> healthReportListener; 172 173 // failover 174 protected JWKSource<C> failover; 175 176 177 /** 178 * Creates a new JWK set source. 179 * 180 * @param jwkSetSource The JWK set source to wrap. Must not be 181 * {@code null}. 182 */ 183 private JWKSourceBuilder(final JWKSetSource<C> jwkSetSource) { 184 Objects.requireNonNull(jwkSetSource); 185 this.jwkSetSource = jwkSetSource; 186 } 187 188 189 /** 190 * Toggles caching of the JWK set. 191 * 192 * @param enable {@code true} to cache the JWK set. 193 * 194 * @return This builder. 195 */ 196 public JWKSourceBuilder<C> cache(final boolean enable) { 197 this.caching = enable; 198 return this; 199 } 200 201 202 /** 203 * Enables caching of the retrieved JWK set. 204 * 205 * @param timeToLive The time to live of the cached JWK set, 206 * in milliseconds. 207 * @param cacheRefreshTimeout The cache refresh timeout, in 208 * milliseconds. 209 * 210 * @return This builder. 211 */ 212 public JWKSourceBuilder<C> cache(final long timeToLive, final long cacheRefreshTimeout) { 213 this.caching = true; 214 this.cacheTimeToLive = timeToLive; 215 this.cacheRefreshTimeout = cacheRefreshTimeout; 216 return this; 217 } 218 219 220 /** 221 * Enables caching of the retrieved JWK set. 222 * 223 * @param timeToLive The time to live of the cached JWK set, 224 * in milliseconds. 225 * @param cacheRefreshTimeout The cache refresh timeout, in 226 * milliseconds. 227 * @param eventListener The event listener, {@code null} if not 228 * specified. 229 * 230 * @return This builder. 231 */ 232 public JWKSourceBuilder<C> cache(final long timeToLive, 233 final long cacheRefreshTimeout, 234 final EventListener<CachingJWKSetSource<C>, C> eventListener) { 235 this.caching = true; 236 this.cacheTimeToLive = timeToLive; 237 this.cacheRefreshTimeout = cacheRefreshTimeout; 238 this.cachingEventListener = eventListener; 239 return this; 240 } 241 242 243 /** 244 * Enables caching of the JWK set forever (no expiration). 245 * 246 * @return This builder. 247 */ 248 public JWKSourceBuilder<C> cacheForever() { 249 this.caching = true; 250 this.cacheTimeToLive = Long.MAX_VALUE; 251 this.refreshAhead = false; // refresh ahead not necessary 252 return this; 253 } 254 255 256 /** 257 * Toggles refresh-ahead caching of the JWK set. 258 * 259 * @param enable {@code true} to enable refresh-ahead caching of the 260 * JWK set. 261 * 262 * @return This builder. 263 */ 264 public JWKSourceBuilder<C> refreshAheadCache(final boolean enable) { 265 if (enable) { 266 this.caching = true; 267 } 268 this.refreshAhead = enable; 269 return this; 270 } 271 272 273 /** 274 * Enables refresh-ahead caching of the JWK set. 275 * 276 * @param refreshAheadTime The refresh ahead time, in milliseconds. 277 * @param scheduled {@code true} to refresh in a scheduled 278 * manner, regardless of requests. 279 * 280 * @return This builder. 281 */ 282 public JWKSourceBuilder<C> refreshAheadCache(final long refreshAheadTime, final boolean scheduled) { 283 this.caching = true; 284 this.refreshAhead = true; 285 this.refreshAheadTime = refreshAheadTime; 286 this.refreshAheadScheduled = scheduled; 287 return this; 288 } 289 290 291 /** 292 * Enables refresh-ahead caching of the JWK set. 293 * 294 * @param refreshAheadTime The refresh ahead time, in milliseconds. 295 * @param scheduled {@code true} to refresh in a scheduled 296 * manner, regardless of requests. 297 * @param eventListener The event listener, {@code null} if not 298 * specified. 299 * 300 * @return This builder. 301 */ 302 public JWKSourceBuilder<C> refreshAheadCache(final long refreshAheadTime, 303 final boolean scheduled, 304 final EventListener<CachingJWKSetSource<C>, C> eventListener) { 305 this.caching = true; 306 this.refreshAhead = true; 307 this.refreshAheadTime = refreshAheadTime; 308 this.refreshAheadScheduled = scheduled; 309 this.cachingEventListener = eventListener; 310 return this; 311 } 312 313 314 /** 315 * Toggles rate limiting of the JWK set retrieval. 316 * 317 * @param enable {@code true} to rate limit the JWK set retrieval. 318 * 319 * @return This builder. 320 */ 321 public JWKSourceBuilder<C> rateLimited(final boolean enable) { 322 this.rateLimited = enable; 323 return this; 324 } 325 326 327 /** 328 * Enables rate limiting of the JWK set retrieval. 329 * 330 * @param minTimeInterval The minimum allowed time interval between two 331 * JWK set retrievals, in milliseconds. 332 * 333 * @return This builder. 334 */ 335 public JWKSourceBuilder<C> rateLimited(final long minTimeInterval) { 336 this.rateLimited = true; 337 this.minTimeInterval = minTimeInterval; 338 return this; 339 } 340 341 342 /** 343 * Enables rate limiting of the JWK set retrieval. 344 * 345 * @param minTimeInterval The minimum allowed time interval between two 346 * JWK set retrievals, in milliseconds. 347 * @param eventListener The event listener, {@code null} if not 348 * specified. 349 * 350 * @return This builder. 351 */ 352 public JWKSourceBuilder<C> rateLimited(final long minTimeInterval, 353 final EventListener<RateLimitedJWKSetSource<C>, C> eventListener) { 354 this.rateLimited = true; 355 this.minTimeInterval = minTimeInterval; 356 this.rateLimitedEventListener = eventListener; 357 return this; 358 } 359 360 361 /** 362 * Sets a failover JWK source. 363 * 364 * @param failover The failover JWK source, {@code null} if none. 365 * 366 * @return This builder. 367 */ 368 public JWKSourceBuilder<C> failover(final JWKSource<C> failover) { 369 this.failover = failover; 370 return this; 371 } 372 373 374 /** 375 * Enables single retrial to retrieve the JWK set to work around 376 * transient network issues. 377 * 378 * @param enable {@code true} to enable single retrial. 379 * 380 * @return This builder. 381 */ 382 public JWKSourceBuilder<C> retrying(final boolean enable) { 383 this.retrying = enable; 384 return this; 385 } 386 387 388 /** 389 * Enables single retrial to retrieve the JWK set to work around 390 * transient network issues. 391 * 392 * @param eventListener The event listener, {@code null} if not 393 * specified. 394 * 395 * @return This builder. 396 */ 397 public JWKSourceBuilder<C> retrying(final EventListener<RetryingJWKSetSource<C>, C> eventListener) { 398 this.retrying = true; 399 this.retryingEventListener = eventListener; 400 return this; 401 } 402 403 404 /** 405 * Sets a health report listener. 406 * 407 * @param listener The health report listener, {@code null} if not 408 * specified. 409 * 410 * @return This builder. 411 */ 412 public JWKSourceBuilder<C> healthReporting(final HealthReportListener<JWKSetSourceWithHealthStatusReporting<C>, C> listener) { 413 this.healthReportListener = listener; 414 return this; 415 } 416 417 418 /** 419 * Toggles outage tolerance by serving a cached JWK set in case of 420 * outage. 421 * 422 * @param enable {@code true} to enable the outage cache. 423 * 424 * @return This builder. 425 */ 426 public JWKSourceBuilder<C> outageTolerant(final boolean enable) { 427 this.outageTolerant = enable; 428 return this; 429 } 430 431 432 /** 433 * Enables outage tolerance by serving a non-expiring cached JWK set in 434 * case of outage. 435 * 436 * @return This builder. 437 */ 438 public JWKSourceBuilder<C> outageTolerantForever() { 439 this.outageTolerant = true; 440 this.outageCacheTimeToLive = Long.MAX_VALUE; 441 return this; 442 } 443 444 445 /** 446 * Enables outage tolerance by serving a non-expiring cached JWK set in 447 * case of outage. 448 * 449 * @param timeToLive The time to live of the cached JWK set to cover 450 * outages, in milliseconds. 451 * 452 * @return This builder. 453 */ 454 public JWKSourceBuilder<C> outageTolerant(final long timeToLive) { 455 this.outageTolerant = true; 456 this.outageCacheTimeToLive = timeToLive; 457 return this; 458 } 459 460 461 /** 462 * Enables outage tolerance by serving a non-expiring cached JWK set in 463 * case of outage. 464 * 465 * @param timeToLive The time to live of the cached JWK set to cover 466 * outages, in milliseconds. 467 * @param eventListener The event listener, {@code null} if not 468 * specified. 469 * 470 * @return This builder. 471 */ 472 public JWKSourceBuilder<C> outageTolerant(final long timeToLive, 473 final EventListener<OutageTolerantJWKSetSource<C>, C> eventListener) { 474 this.outageTolerant = true; 475 this.outageCacheTimeToLive = timeToLive; 476 this.outageEventListener = eventListener; 477 return this; 478 } 479 480 481 /** 482 * Builds the final {@link JWKSource}. 483 * 484 * @return The final {@link JWKSource}. 485 */ 486 public JWKSource<C> build() { 487 488 if (! caching && rateLimited) { 489 throw new IllegalStateException("Rate limiting requires caching"); 490 } else if (! caching && refreshAhead) { 491 throw new IllegalStateException("Refresh-ahead caching requires general caching"); 492 } 493 494 if (caching && rateLimited && cacheTimeToLive <= minTimeInterval) { 495 throw new IllegalStateException("The rate limiting min time interval between requests must be less than the cache time-to-live"); 496 } 497 498 if (caching && outageTolerant && cacheTimeToLive == Long.MAX_VALUE && outageCacheTimeToLive == Long.MAX_VALUE) { 499 // TODO consider adjusting instead of exception 500 throw new IllegalStateException("Outage tolerance not necessary with a non-expiring cache"); 501 } 502 503 if (caching && refreshAhead && cacheTimeToLive == Long.MAX_VALUE) { 504 // TODO consider adjusting instead of exception 505 throw new IllegalStateException("Refresh-ahead caching not necessary with a non-expiring cache"); 506 } 507 508 JWKSetSource<C> source = jwkSetSource; 509 510 if (retrying) { 511 source = new RetryingJWKSetSource<>(source, retryingEventListener); 512 } 513 514 if (outageTolerant) { 515 if (outageCacheTimeToLive == -1L) { 516 if (caching) { 517 outageCacheTimeToLive = cacheTimeToLive * 10; 518 } else { 519 outageCacheTimeToLive = DEFAULT_CACHE_TIME_TO_LIVE * 10; 520 } 521 } 522 source = new OutageTolerantJWKSetSource<>(source, outageCacheTimeToLive, outageEventListener); 523 } 524 525 if (healthReportListener != null) { 526 source = new JWKSetSourceWithHealthStatusReporting<>(source, healthReportListener); 527 } 528 529 if (rateLimited) { 530 source = new RateLimitedJWKSetSource<>(source, minTimeInterval, rateLimitedEventListener); 531 } 532 533 if (refreshAhead) { 534 source = new RefreshAheadCachingJWKSetSource<>(source, cacheTimeToLive, cacheRefreshTimeout, refreshAheadTime, refreshAheadScheduled, cachingEventListener); 535 } else if (caching) { 536 source = new CachingJWKSetSource<>(source, cacheTimeToLive, cacheRefreshTimeout, cachingEventListener); 537 } 538 539 JWKSource<C> jwkSource = new JWKSetBasedJWKSource<>(source); 540 if (failover != null) { 541 return new JWKSourceWithFailover<>(jwkSource, failover); 542 } 543 return jwkSource; 544 } 545}