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