Improve RemoteJWKSet - additional requirements and with example implementation

Issue #460 resolved
Thomas Rørvik Skjølberg created an issue

Hi, we have for some time used a JWK cache which has a few more features than the current implementation in this project. Now we would like to see whether contributing some of the features to this project is feasible. In a nutshell, the features are

  • rate-limited remote fetch on unknown key id
  • health indicator support
  • transient retry on network problems
  • lazy or proactive background refresh

    • in background thread
  • cache with improved logging for corner cases

This currently a synchronious (non-reactive) implementation, implemented as a layered set of classes with builders and various custom exceptions (modelled after Auth0s approach).

Note that

  • the implementation uses locks for synchronization, executors for background work
  • the rate-limited on remote fetch option can be improved; the user should select a strategy for refresh (frequent or infrequent + on demand)
  • an adaptation already exists (some refactoring would be needed)

Comments (16)

  1. Vladimir Dzhuvinov

    Thank you very much for considering this contribution. It is seldom that the library receives more complete and substantial enhancements. The majority tend to focus on very specific issues. Btw, the day day I joked on Twitter that this is becoming less of a library for cryptography and more for networking and caching. The RemoteJWKSet received several such related changes recently.

    I did a first quick scan of the code.

    To what extent does the code rely on Java 8+ features? At present the library targets Java 7. However, for some there have been thoughts to start a new Java 8 branch, and put it out eventually as a new major release.

    I also noticed that there is a logging dependency. So far the approach has been to keep logging out, but provide enough context and ability to hook logging when people want that.

  2. Thomas Rørvik Skjølberg reporter

    Java 8+ features, very good question, I did not catch the Java 7 compatiblity. I’ll give it a test run on 7 and get back to you. Now the library is currently at Java 8, and has a dependency on bucket4j which is Java 8 too. I am thinking that 7 compatibility will be straightforward, but would also welcome a Java 8 branch.

    Would you prefer the logging used java.util.logging.Logger? I would like to keep (most) of the logging, at least for informing the user that sertificates are about to expire (when the authorization server is down) and when threads are waiting (blocked) due to waiting for network requests.

  3. Thomas Rørvik Skjølberg reporter

    Java 7 is feasible, I have a Maven build running the tests from command line.

    Summary:

    • need to implement a simple leaky bucket type throttling, Bucket4J is Java 8 and also the additional footprint cannot be justified
    • unit tests needs to extend TestCase and use the classic approach for testing exceptions. Some assertions probably need rewriting.
    • time / duration must be reduced to milliseconds (which is fair and less bloated anyways)
    • junit and mockito downgrades
    • some default methods in interfaces need copying into implementations

    More adjustments are left in order for the code to follow the existing project conventions / tidy up dependencies etc, but this is looking good so far.

    I must admit that I have not missed the Java 7 all that much, this was like a walk down memory lane. This would perhaps be as a good as any to start a Java 8 branch.

  4. Vladimir Dzhuvinov

    The Java 7 lang level has been primarily kept to enable Android app developers to ship code with this lib that works with older versions of the OS. Having said that, every now and then we get queries to Connect2id support whether we could back port some recent lib release Java 6 to suit some legacy JBoss deployment :)

    I personally don’t feel good about modifying your piece of code so that it becomes compatible with java 7. Two possibilities I see:

    • Create a new Java 8 module for this new logic.
    • Start a new Java 8 branch and merge the new code there.

    What do you think about that? Pros / cons?

  5. Thomas Rørvik Skjølberg reporter

    I refactored the code into a Java 7 version, see this PR.

    For Java 8 and Android, perhaps you want to stay compatible with the subset of Java 8 supported by using com.android.tools:desugar_jdk_libs .

  6. Francisco Bento

    Hi guys, what is the current status of this issue ? any plans for having this merged and released ?

  7. Thomas Rørvik Skjølberg reporter

    @Francisco Bento everything lives in this PR and has undergone several rounds of revision and adjustments. The code is ready for merge as far as I understand.

  8. Yavor Vasilev

    The new feature is getting an article with examples on the lib site this week and then we’ll be good to go with release

  9. Francisco Bento

    Thank you @Thomas Skjønberg and the team for the superb work.

    Im currently hacking the current version to have some health indicator support on my spring boot/security based app. Looking forward to see your changes going to spring-security-oauth2-resource-server, interested specially on the rate-limited remote fetch of unknown kids.

    @Yavor Vasilev , what are the plans for the next version iteration ? Will it be a major version bump ?

  10. Vladimir Dzhuvinov

    On to Maven Central as

    version 9.28 (2023-01-05)
        * New JWKSourceBuilder entry point for building JWKSource instances that
          wrap a generic JWKSetSource with caching (including refresh ahead), rate
          limiting, retrial, fail-over, health status reporting and outage
          tolerance capabilities.
        * New CachingJWKSetSource, RefreshAheadCachingJWKSetSource,
          RateLimitedJWKSetSource, RetryingJWKSetSource, JWKSourceWithFailover,
          JWKSetSourceWithHealthStatusReporting and
          OutageTolerantJWKSetSource classes wrapping a generic JWKSetSource with a
          capability.
        * Deprecates the JWKSetCache interface and its DefaultJWKSetCache
          implementation.
        * Deprecates the RemoteJWKSet class.
        * Deprecates the JWKSetWithTimestamp class.
        * DefaultResourceRetriever adds support for file based URLs.
        * New JWKSet isEmpty and size shorthand methods.
        * New com.nimbusds.jose.util.cache package with generic CachedObject class.
        * New com.nimbusds.jose.util.events package with Event and EventListener
          interfaces.
        * New com.nimbusds.jose.util.health package with HealthReportListener
          interface, HealthReport class and HealthStatus enum.
        * New IOUtils.closeSilently(java.io.Closeable) method.
    
  11. Thomas Rørvik Skjølberg reporter

    Cheers, the current text is good, but please also add my employer Entur using a link to our Github account: https://github.com/entur, like so:

    Thomas Rørvik Skjølberg at Entur for contributing a powerful mini framework for retrieving JWK sets with caching, outage, retrial and fail-over capabilities.

    Let me know if you’re going to Oslo some time in the future, I’ll buy the beers. 🍻

  12. Log in to comment