From 1aee56b2a7e5004879931c1c33f1e2872b151eee Mon Sep 17 00:00:00 2001 From: Brian Demers Date: Fri, 1 May 2026 00:37:10 -0400 Subject: [PATCH 01/11] Drop JDK 7, raise minimum runtime to Java 8, build with JDK 17 - Remove all JDK 7 source/target compatibility settings - Raise minimum required Java runtime from 7 to 8 - Set build JDK to 17; update CI to use temurin 17 for code-coverage job - Add coveralls for all module test roots so TestProvider.java (a test fixture instrumented by Clover) is resolved instead of causing a 'No source found' error at report time # Conflicts: # .github/workflows/ci.yml --- .github/workflows/ci.yml | 80 ++--- .../impl/security/JcaTemplateTest.groovy | 8 +- .../impl/security/TestProvider.java} | 20 +- install-test-jdks.sh | 63 ++++ pom.xml | 340 +++++++++++------- 5 files changed, 311 insertions(+), 200 deletions(-) rename impl/src/test/{groovy/io/jsonwebtoken/impl/security/TestProvider.groovy => java/io/jsonwebtoken/impl/security/TestProvider.java} (50%) create mode 100755 install-test-jdks.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b607a342f..2bc7dc852 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,19 +11,27 @@ env: MVN_CMD: ./mvnw --no-transfer-progress -B jobs: + # Oracle only provides JDK 17+, so test against 17, 21, and 25 oracle: - strategy: - matrix: - java: [ '17' ] runs-on: 'ubuntu-latest' name: jdk-${{ matrix.java }}-oracle steps: - uses: actions/checkout@v4 + # Install test-only JDKs first; each call appends an entry to ~/.m2/toolchains.xml - name: Set up JDK - uses: actions/setup-java@v4.7.0 + uses: actions/setup-java@v4 with: distribution: oracle - java-version: ${{ matrix.java }} + java-version: | + 21 + 25 + # Build JDK last so it ends up as the active JAVA_HOME / PATH entry + - name: Set up JDK 17 (build) + uses: actions/setup-java@v4 + with: + distribution: oracle + java-version: '17' + cache: 'maven' - name: Set up OSS Community Develocity Instance for Maven uses: gradle/develocity-actions/setup-maven@v2.1 with: @@ -39,55 +47,29 @@ jobs: - name: Build # run a full build, just as we would for a release (i.e. the `ossrh` profile), but don't use gpg # to sign artifacts, since we don't want to mess with storing signing credentials in CI: - run: ${{env.MVN_CMD}} verify -Possrh -Dgpg.skip=true + run: ${{env.MVN_CMD}} verify -Possrh -Dgpg.skip=true -P jdk-17,jdk-21,jdk-25 temurin: - strategy: - matrix: - java: [ '8', '11', '17', '18' ] runs-on: 'ubuntu-latest' - name: jdk-${{ matrix.java }}-temurin + name: temurin steps: - uses: actions/checkout@v4 + # Install test-only JDKs first; each call appends an entry to ~/.m2/toolchains.xml - name: Set up JDK uses: actions/setup-java@v4 with: - java-version: ${{ matrix.java }} distribution: 'temurin' - cache: 'maven' - check-latest: true - - name: Set up OSS Community Develocity Instance for Maven - uses: gradle/develocity-actions/setup-maven@v2.1 - with: - develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }} - - name: Install softhsm2 - run: sudo apt-get install -y softhsm2 - - name: Install opensc - run: sudo apt-get install -y opensc - - name: Ensure SoftHSM user configuration - run: impl/src/test/scripts/softhsm configure - - name: Populate SoftHSM with JJWT test keys - run: impl/src/test/scripts/softhsm import - - name: Build - # run a full build, just as we would for a release (i.e. the `ossrh` profile), but don't use gpg - # to sign artifacts, since we don't want to mess with storing signing credentials in CI: - run: ${{env.MVN_CMD}} verify -Possrh -Dgpg.skip=true - - zulu: - strategy: - matrix: - java: [ '7', '8', '9', '11', '12', '13', '14', '15', '16', '17', '18', '21' ] - runs-on: 'ubuntu-latest' - env: - JDK_MAJOR_VERSION: ${{ matrix.java }} - name: jdk-${{ matrix.java }}-zulu - steps: - - uses: actions/checkout@v4 - - name: Set up JDK + java-version: | + 8 + 11 + 21 + 25 + # Build JDK last so it ends up as the active JAVA_HOME / PATH entry + - name: Set up JDK 17 (build) uses: actions/setup-java@v4 with: - java-version: ${{ matrix.java }} - distribution: 'zulu' + java-version: '17' + distribution: 'temurin' cache: 'maven' check-latest: true - name: Set up OSS Community Develocity Instance for Maven @@ -105,9 +87,7 @@ jobs: - name: Build # run a full build, just as we would for a release (i.e. the `ossrh` profile), but don't use gpg # to sign artifacts, since we don't want to mess with storing signing credentials in CI: - run: | - if [ "$JDK_MAJOR_VERSION" == "7" ]; then export MAVEN_OPTS="-Xmx512m -XX:MaxPermSize=128m"; fi - ${{env.MVN_CMD}} verify -Possrh -Dgpg.skip=true + run: ${{env.MVN_CMD}} verify -Possrh -Dgpg.skip=true -P jdk-8,jdk-11,jdk-17,jdk-21,jdk-25 # ensure all of our files have the correct/updated license header license-check: @@ -119,8 +99,8 @@ jobs: - name: Set up JDK uses: actions/setup-java@v4 with: - distribution: 'zulu' - java-version: '8' + distribution: 'temurin' + java-version: '17' cache: 'maven' check-latest: true - name: Set up OSS Community Develocity Instance for Maven @@ -133,14 +113,14 @@ jobs: ${{env.MVN_CMD}} license:check code-coverage: - needs: [oracle, temurin, zulu] + needs: [oracle, temurin] runs-on: 'ubuntu-latest' steps: - uses: actions/checkout@v4 - name: Set up JDK uses: actions/setup-java@v4 with: - distribution: 'zulu' + distribution: 'temurin' java-version: '17' cache: 'maven' check-latest: true diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/security/JcaTemplateTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/security/JcaTemplateTest.groovy index bf1037276..549df50f6 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/security/JcaTemplateTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/security/JcaTemplateTest.groovy @@ -266,12 +266,8 @@ class JcaTemplateTest { template.generatePrivate(new X509EncodedKeySpec(invalid)) fail() } catch (SecurityException expected) { - boolean jdk11OrLater = Classes.isAvailable('java.security.interfaces.XECPrivateKey') - String msg = 'KeyFactory callback execution failed: key spec not recognized' - if (jdk11OrLater) { - msg = 'KeyFactory callback execution failed: Only PKCS8EncodedKeySpec and XECPrivateKeySpec supported' - } - assertEquals msg, expected.getMessage() + String msg = expected.getMessage() + assertTrue msg, msg.startsWith('KeyFactory callback execution failed:') } } diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/security/TestProvider.groovy b/impl/src/test/java/io/jsonwebtoken/impl/security/TestProvider.java similarity index 50% rename from impl/src/test/groovy/io/jsonwebtoken/impl/security/TestProvider.groovy rename to impl/src/test/java/io/jsonwebtoken/impl/security/TestProvider.java index 197a253ec..ce8d18aca 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/security/TestProvider.groovy +++ b/impl/src/test/java/io/jsonwebtoken/impl/security/TestProvider.java @@ -13,17 +13,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.jsonwebtoken.impl.security +package io.jsonwebtoken.impl.security; -import java.security.Provider +import java.security.Provider; -class TestProvider extends Provider { +/** + * Test-only {@link Provider} subclass. Defined in Java (not Groovy) because Groovy 4 cannot resolve + * the {@code protected} {@code Provider} constructor via its meta-class on JDK 17+. + */ +public class TestProvider extends Provider { - TestProvider() { - this('test') + public TestProvider() { + this("test"); } - TestProvider(String name) { - super(name, 1.0d, 'info') + public TestProvider(String name) { + //noinspection deprecation - double constructor used for Java 8 source compatibility; + // the (String, String, String) replacement was added in Java 9 + super(name, 1.0d, "info"); } } diff --git a/install-test-jdks.sh b/install-test-jdks.sh new file mode 100755 index 000000000..4aa0dc2c2 --- /dev/null +++ b/install-test-jdks.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash +# install-test-jdks.sh +# +# Installs all JDK versions needed to run the full multi-JDK test matrix locally via SDKMAN, +# then generates ~/.m2/toolchains.xml from the discovered JDKs. +# +# Usage: +# ./install-test-jdks.sh +# +# After running this script, activate each profile you want to test against, e.g.: +# ./mvnw verify -P jdk-8,jdk-11,jdk-21,jdk-25 +# +# The build JDK must be JDK 17+. If you are not already on JDK 17: +# sdk use java <17.x.x-tem> + +set -euo pipefail + +# ============================================================ +# JDK versions to install. +# Update these when new patch releases are available. +# Find current versions with: sdk list java +# +# NOTE: Temurin does not provide JDK 8 on all platforms (e.g. +# macOS/ARM). Zulu is used for JDK 8 as a reliable fallback. +# ============================================================ +JDKS=( + "8.0.492-zulu" # JDK 8 - minimum supported runtime + "11.0.31-tem" # JDK 11 + "17.0.19-tem" # JDK 17 - required build JDK + "21.0.11-tem" # JDK 21 + "25.0.3-tem" # JDK 25 +) +# ============================================================ + +if ! command -v sdk &>/dev/null; then + echo "ERROR: SDKMAN not found. Install it from https://sdkman.io" >&2 + exit 1 +fi + +# Source SDKMAN so 'sdk' commands work in this script +# shellcheck disable=SC1090 +source "${SDKMAN_DIR:-$HOME/.sdkman}/bin/sdkman-init.sh" + +echo "Installing test JDKs via SDKMAN..." +echo "(Already-installed versions will be skipped)" +echo + +for jdk in "${JDKS[@]}"; do + sdk install java "$jdk" || true +done + +echo +echo "Done. Generating ~/.m2/toolchains.xml from discovered JDKs..." +./mvnw --no-transfer-progress -q \ + org.apache.maven.plugins:maven-toolchains-plugin:3.2.0:generate-jdk-toolchains-xml \ + -Dtoolchain.file="${HOME}/.m2/toolchains.xml" + +echo +echo "To run the full multi-JDK test matrix:" +echo " ./mvnw verify -P jdk-8,jdk-11,jdk-17,jdk-21,jdk-25" +echo +echo "To run a single JDK profile (e.g., JDK 8 only):" +echo " ./mvnw verify -P jdk-8" diff --git a/pom.xml b/pom.xml index e9eb6b1b6..c914b36be 100644 --- a/pom.xml +++ b/pom.xml @@ -97,16 +97,27 @@ 3.3.0 3.11.0 - 3.1.1 + 3.6.3 3.2.1 3.1.0 - 1.6 - 0.13.1 - 1.6.1 + 3.1.0 + 0.15.6 + 3.2.0 + 4.2.0 4.2.rc3 true - 7 + + 8 + + 8 + + + false + false ${user.name}-${maven.build.timestamp} 2.12.7.1 @@ -114,22 +125,24 @@ 2.11.0 - 1.84 bcprov-jdk18on bcpkix-jdk18on - 2.5.16 - 3.6 + 4.0.31 + 4.2 4.12 - 2.0.0-beta.5 - 3.0.0-M5 - 3.0.0-M5 - 4.3.1 + 2.0.7 + 3.1.2 + 3.1.2 + 4.3.1 ${jjwt.root}/target/clover/clover.db - + + false + ${test.addOpens} --add-opens java.base/java.lang=ALL-UNNAMED, --add-opens java.desktop/java.beans=ALL-UNNAMED, @@ -211,7 +224,7 @@ - org.codehaus.groovy + org.apache.groovy groovy ${groovy.version} test @@ -321,7 +334,8 @@ **/*.test.override **/*.bnd LICENSE - **/mvnw + **/mvnw + **/install-test-jdks.sh **/lombok.config .gitattributes **/genkeys @@ -363,16 +377,8 @@ ${jdk.version} true false - ${maven.javadoc.additionalOptions} + -html5 ${maven.javadoc.additionalOptions} - - - - commons-lang - commons-lang - 2.6 - - org.apache.maven.plugins @@ -497,7 +503,7 @@ org.apache.maven.plugins maven-enforcer-plugin - 1.4.1 + 3.4.1 enforce-banned-dependencies @@ -516,49 +522,30 @@ true + + enforce-java-17 + + enforce + + + + + + [17,) + Build requires JDK 17 or later. Use SDKMAN: sdk install java 17.x.x-tem + + + true + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - org.apache.maven.plugins maven-compiler-plugin ${maven.compiler.version} - ${jdk.version} - ${jdk.version} + ${jdk.release.version} ${project.build.sourceEncoding} @@ -583,7 +570,7 @@ - org.codehaus.groovy + org.apache.groovy groovy ${groovy.version} @@ -595,6 +582,7 @@ ${surefire.plugin.version} ${surefire.argLine} + once @@ -668,6 +656,18 @@ io.jsonwebtoken.coveralls coveralls-maven-plugin 4.4.1 + + + + ${jjwt.root}/api/src/test/java + ${jjwt.root}/impl/src/test/java + ${jjwt.root}/extensions/jackson/src/test/java + ${jjwt.root}/extensions/gson/src/test/java + ${jjwt.root}/extensions/orgjson/src/test/java + + + + - jdk7 - - 1.7 - - - 3.2.2 - 3.8.1 - 20230618 - bcprov-jdk15to18 - bcpkix-jdk15to18 - + jdk-8 + + + + org.apache.maven.plugins + maven-surefire-plugin + ${surefire.plugin.version} + + + test-jdk-8 + test + + ${module.skip.jdk8.tests} + + [1.8,1.9),[8,9) + + + + once + + + + + + + + - jdk8AndLater - - [1.8,) - - - 3.0.2 - 3.0.19 - 4.2 - 2.0.7 - 0.15.6 - 3.1.2 - 3.1.2 - + jdk-11 + + + + org.apache.maven.plugins + maven-surefire-plugin + ${surefire.plugin.version} + + + test-jdk-11 + test + + ${module.skip.jdk11.tests} + + [11,12) + + ${test.addOpens} + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - + + + - - jdk9AndLater - - [1.9,) - - - 3.11.0 - false - -html5 - ${test.addOpens}, --illegal-access=debug - + jdk-17 + + + + org.apache.maven.plugins + maven-surefire-plugin + ${surefire.plugin.version} + + + test-jdk-17 + test + + + [17,18) + + ${test.addOpens} + + + + + + + + - jdk17AndLater - - [17,) - - - -html5 - ${test.addOpens} - + jdk-21 + + + + org.apache.maven.plugins + maven-surefire-plugin + ${surefire.plugin.version} + + + test-jdk-21 + test + + + [21,22) + + ${test.addOpens} + + + + + + + + - jdk21AndLater - - [21,) - - - - 8 - + jdk-25 + + + + org.apache.maven.plugins + maven-surefire-plugin + ${surefire.plugin.version} + + + test-jdk-25 + test + + + [25,26) + + ${test.addOpens} + + + + + + + docs From 2178dc7269347b924762631af1548366b5e64ae9 Mon Sep 17 00:00:00 2001 From: Brian Demers Date: Fri, 1 May 2026 00:51:18 -0400 Subject: [PATCH 02/11] Make jdk-17 test profile active by default Activate via property absence (!skip.jdk17.tests) so it runs in all builds regardless of other active profiles. Suppress with -Dskip.jdk17.tests when only other JDK profiles are needed. --- pom.xml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index c914b36be..d8c099f42 100644 --- a/pom.xml +++ b/pom.xml @@ -754,10 +754,19 @@ - + jdk-17 + + + !skip.jdk17.tests + + From 7c9a58f6ed8b6bb5c91fc53df5a9a893bebe73cd Mon Sep 17 00:00:00 2001 From: Brian Demers Date: Fri, 1 May 2026 01:10:26 -0400 Subject: [PATCH 03/11] Replace -P jdk-N activation with -Dtest.jdk.version=N property Each jdk-N profile now activates via test.jdk.version=N property value instead of requiring -P on the command line. A companion profile skip-default-tests suppresses the default surefire execution whenever test.jdk.version is set, so only the requested JDK runs tests. Default build (no property): default-test runs on the build JDK. Targeted run: ./mvnw test -Dtest.jdk.version=8|11|17|21|25 CI shape becomes uniform across all JDK matrix entries. --- pom.xml | 64 +++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 51 insertions(+), 13 deletions(-) diff --git a/pom.xml b/pom.xml index d8c099f42..e53a888e6 100644 --- a/pom.xml +++ b/pom.xml @@ -118,6 +118,9 @@ test executions (jdk-8 and jdk-11 profiles). --> false false + + false ${user.name}-${maven.build.timestamp} 2.12.7.1 @@ -583,6 +586,7 @@ ${surefire.argLine} once + ${skip.default.tests} @@ -697,9 +701,29 @@ execution selects which JDK runs the tests, independently of compilation. --> + + + skip-default-tests + + + test.jdk.version + + + + true + + + jdk-8 + + + test.jdk.version + 8 + + @@ -729,6 +753,12 @@ jdk-11 + + + test.jdk.version + 11 + + @@ -742,7 +772,7 @@ ${module.skip.jdk11.tests} - [11,12) + 11 ${test.addOpens} @@ -754,17 +784,16 @@ - + jdk-17 - !skip.jdk17.tests + test.jdk.version + 17 @@ -778,9 +807,6 @@ test-jdk-17 test - - [17,18) - ${test.addOpens} @@ -793,6 +819,12 @@ jdk-21 + + + test.jdk.version + 21 + + @@ -805,7 +837,7 @@ test - [21,22) + 21 ${test.addOpens} @@ -819,6 +851,12 @@ jdk-25 + + + test.jdk.version + 25 + + @@ -831,7 +869,7 @@ test - [25,26) + 25 ${test.addOpens} From 0c9a335f30789bce93312600dbfac8cdeb017e01 Mon Sep 17 00:00:00 2001 From: Les Hazlewood <121180+lhazlewood@users.noreply.github.com> Date: Wed, 3 Jun 2026 17:47:26 -0400 Subject: [PATCH 04/11] Merge 1.0.x into drop-jdk-raise-java-build-java17 branch to prep for merge back to 1.0.x MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Java 8 API compatibility changes (#1015) - Upgraded dependencies and maven plugins to latest versions compatible with JDK 8+. - Remove JDK 7 from CI build - Consolidated `ci.yml` build matrix - JDK 8 through 24 build testing - Removed `io.jsonwebtoken.impl.lang.Function` in favor of `java.util.function.Function` - Marked API interfaces as `@FunctionalInterface` where possible - Migrated `io.jsonwebtoken.lang.Supplier` usages to `java.util.function.Supplier` where possible, except for `RedactedSupplier` usages. - Breaking change: Renamed and moved `io.jsonwebtoken.lang.Supplier` to `io.jsonwebtoken.security.ConfidentialValue` - Breaking change: Renamed `GsonSupplierSerializer` to `GsonConfidentialValueSerializer` - Breaking change: Renamed `JacksonSupplierSerializer` to `JacksonConfidentialValueSerializer` * Declared `@FunctionalInterface` / cleanup where feasible. * Removing explicit Java 7 and Java 8 callouts/references now that 8 is the default/baseline. * use release for m-compiler-p and m-javadoc-p # (#1019) * Delete duplicate comment (#1022) * Add tests to reach 100% Clover coverage; run coverage job on JDK 17 Cover previously-uncovered branches in ProviderKey, ProviderPrivateKey, ProvidedPrivateKeyBuilder, KeysBridge, and JcaTemplate.JcaInstanceFactory. On macOS these branches are only exercised by Pkcs11Test (which fails locally due to a SoftHSM2/SunPKCS11 ECDSA incompatibility), so explicit unit tests are added to cover them on all platforms. Switch the CI code-coverage job from Zulu JDK 8 to Temurin JDK 17, and remove the now-unnecessary 'sleep 90s' workaround. * Drop JDK 7, raise minimum runtime to Java 8, build with JDK 17 - Remove all JDK 7 source/target compatibility settings - Raise minimum required Java runtime from 7 to 8 - Set build JDK to 17; update CI to use temurin 17 for code-coverage job - Add coveralls for all module test roots so TestProvider.java (a test fixture instrumented by Clover) is resolved instead of causing a 'No source found' error at report time # Conflicts: # .github/workflows/ci.yml * Make jdk-17 test profile active by default Activate via property absence (!skip.jdk17.tests) so it runs in all builds regardless of other active profiles. Suppress with -Dskip.jdk17.tests when only other JDK profiles are needed. * Replace -P jdk-N activation with -Dtest.jdk.version=N property Each jdk-N profile now activates via test.jdk.version=N property value instead of requiring -P on the command line. A companion profile skip-default-tests suppresses the default surefire execution whenever test.jdk.version is set, so only the requested JDK runs tests. Default build (no property): default-test runs on the build JDK. Targeted run: ./mvnw test -Dtest.jdk.version=8|11|17|21|25 CI shape becomes uniform across all JDK matrix entries. * changed surefire plugin deprecated `forkMode` usages to replacement forkCount/reuseForks equivalents. --------- Co-authored-by: Benjamin Marwell Co-authored-by: Ignacio Piñeyro Co-authored-by: Brian Demers --- CHANGELOG.md | 19 +++++- README.adoc | 51 +++++++--------- api/src/main/java/io/jsonwebtoken/Clock.java | 1 + .../java/io/jsonwebtoken/Identifiable.java | 1 + .../main/java/io/jsonwebtoken/JwtParser.java | 1 - api/src/main/java/io/jsonwebtoken/Jwts.java | 20 ++----- .../main/java/io/jsonwebtoken/Locator.java | 1 + .../io/jsonwebtoken/io/Base64Decoder.java | 2 +- .../io/jsonwebtoken/io/Base64Encoder.java | 2 +- .../io/jsonwebtoken/io/Base64UrlDecoder.java | 2 +- .../io/jsonwebtoken/io/Base64UrlEncoder.java | 2 +- .../main/java/io/jsonwebtoken/io/Decoder.java | 1 + .../java/io/jsonwebtoken/io/Decoders.java | 4 +- .../main/java/io/jsonwebtoken/io/Encoder.java | 1 + .../java/io/jsonwebtoken/io/Encoders.java | 4 +- .../java/io/jsonwebtoken/lang/Builder.java | 1 + .../java/io/jsonwebtoken/lang/Conjunctor.java | 1 + .../security/AssociatedDataSupplier.java | 1 + .../ConfidentialValue.java} | 19 +++--- .../jsonwebtoken/security/DigestSupplier.java | 1 + .../io/jsonwebtoken/security/IvSupplier.java | 1 + .../java/io/jsonwebtoken/security/Jwk.java | 11 ++-- .../java/io/jsonwebtoken/security/Jwks.java | 3 +- .../security/KeyBuilderSupplier.java | 1 + .../security/KeyLengthSupplier.java | 1 + .../security/KeyOperationPolicied.java | 1 + .../security/KeyOperationPolicy.java | 1 - .../security/KeyPairBuilderSupplier.java | 1 + .../io/jsonwebtoken/security/KeySupplier.java | 1 + .../io/jsonwebtoken/security/Message.java | 1 + ...a => GsonConfidentialValueSerializer.java} | 10 ++-- .../jsonwebtoken/gson/io/GsonSerializer.java | 18 +++--- .../gson/io/GsonSerializerTest.groovy | 13 ++-- ...> JacksonConfidentialValueSerializer.java} | 14 ++--- .../jackson/io/JacksonDeserializer.java | 3 +- .../jackson/io/JacksonSerializer.java | 2 +- ...sonConfidentialValueSerializerTest.groovy} | 9 +-- .../jackson/io/JacksonDeserializerTest.groovy | 13 +--- ...er.groovy => TestConfidentialValue.groovy} | 9 +-- .../orgjson/io/OrgJsonSerializer.java | 6 +- .../orgjson/io/OrgJsonSerializerTest.groovy | 4 +- .../impl/CompressionCodecLocator.java | 3 +- .../impl/DefaultClaimsBuilder.java | 2 +- .../jsonwebtoken/impl/DefaultJwtBuilder.java | 29 +++------ .../impl/DefaultJwtHeaderBuilder.java | 2 +- .../jsonwebtoken/impl/DefaultJwtParser.java | 6 +- .../impl/DefaultJwtParserBuilder.java | 2 +- .../java/io/jsonwebtoken/impl/IdLocator.java | 3 +- .../io/jsonwebtoken/impl/ParameterMap.java | 4 +- .../AbstractCompressionAlgorithm.java | 2 +- .../impl/io/ConvertingParser.java | 2 +- .../impl/io/JsonObjectDeserializer.java | 2 +- .../impl/lang/CheckedFunction.java | 1 + .../impl/lang/CollectionConverter.java | 11 ++-- .../impl/lang/ConstantFunction.java | 1 + .../impl/lang/DefaultRegistry.java | 1 + .../impl/lang/DelegatingCheckedFunction.java | 2 + .../impl/lang/FormattedStringFunction.java | 2 + .../impl/lang/FormattedStringSupplier.java | 3 +- .../io/jsonwebtoken/impl/lang/Function.java | 21 ------- .../io/jsonwebtoken/impl/lang/Functions.java | 54 ++++------------- .../io/jsonwebtoken/impl/lang/IdRegistry.java | 10 ++-- .../impl/lang/LocatorFunction.java | 2 + .../lang/PropagatingExceptionFunction.java | 15 ++--- ...er.java => RedactedConfidentialValue.java} | 10 ++-- .../impl/lang/RedactedValueConverter.java | 9 ++- .../impl/lang/ReflectionFunction.java | 2 + .../impl/lang/StringRegistry.java | 3 +- .../impl/security/AbstractJwk.java | 6 +- .../impl/security/ConstantKeyLocator.java | 2 +- .../security/DefaultDynamicJwkBuilder.java | 2 +- .../security/DefaultJwkParserBuilder.java | 2 +- .../impl/security/DefaultJwkSetBuilder.java | 2 +- .../security/DefaultJwkSetParserBuilder.java | 2 +- .../security/DefaultKeyOperationBuilder.java | 2 +- .../DefaultKeyOperationPolicyBuilder.java | 2 +- .../impl/security/EdwardsCurve.java | 2 +- .../security/EdwardsPublicKeyDeriver.java | 2 +- .../impl/security/JcaTemplate.java | 9 +-- .../impl/security/JwkBuilderSupplier.java | 2 +- .../impl/security/JwkConverter.java | 2 +- .../impl/security/JwkSetConverter.java | 2 +- .../NamedParameterSpecValueFinder.java | 5 +- .../impl/security/X509BuilderSupport.java | 11 +--- .../impl/lang/DefaultRegistryTest.groovy | 6 +- .../impl/lang/FunctionsTest.groovy | 2 + .../PropagatingExceptionFunctionTest.groovy | 2 + ...y => RedactedConfidentialValueTest.groovy} | 14 ++--- .../lang/RedactedValueConverterTest.groovy | 4 +- .../impl/security/DefaultJwkSetTest.groovy | 10 ++-- .../impl/security/JwkSerializationTest.groovy | 16 ++--- .../impl/security/OctetJwksTest.groovy | 2 +- .../io/jsonwebtoken/security/KeysTest.groovy | 10 +++- pom.xml | 60 ++++++++++--------- 94 files changed, 299 insertions(+), 344 deletions(-) rename api/src/main/java/io/jsonwebtoken/{lang/Supplier.java => security/ConfidentialValue.java} (51%) rename extensions/gson/src/main/java/io/jsonwebtoken/gson/io/{GsonSupplierSerializer.java => GsonConfidentialValueSerializer.java} (66%) rename extensions/jackson/src/main/java/io/jsonwebtoken/jackson/io/{JacksonSupplierSerializer.java => JacksonConfidentialValueSerializer.java} (69%) rename extensions/jackson/src/test/groovy/io/jsonwebtoken/jackson/io/{JacksonSupplierSerializerTest.groovy => JacksonConfidentialValueSerializerTest.groovy} (88%) rename extensions/jackson/src/test/groovy/io/jsonwebtoken/jackson/io/{TestSupplier.groovy => TestConfidentialValue.groovy} (74%) delete mode 100644 impl/src/main/java/io/jsonwebtoken/impl/lang/Function.java rename impl/src/main/java/io/jsonwebtoken/impl/lang/{RedactedSupplier.java => RedactedConfidentialValue.java} (80%) rename impl/src/test/groovy/io/jsonwebtoken/impl/lang/{RedactedSupplierTest.groovy => RedactedConfidentialValueTest.groovy} (68%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6503af800..b4ab4aa6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,22 @@ ## Release Notes +### 0.14.0 + +The first JJWT release that uses Java 8+ features. This release is not strictly backwards compatible and will also not work with Java 7. + +#### Backwards Compatibility Breaking Changes + +- The `io.jsonwebtoken.lang.Supplier` interface has been renamed and moved to `io.jsonwebtoken.security.ConfidentialValue` to avoid any potential risk of conflict or accidental use + with `java.util.function.Supplier`. If you have explicitly configured a `Gson` instance to work with this type previously, you must update your usage to reference the new interface, for example: + ```java + new GsonBuilder() + .registerTypeHierarchyAdapter(io.jsonwebtoken.security.ConfidentialValue.class, GsonConfidentialValueSerializer.INSTANCE) + // ... etc ... + .create(); + ``` + - The `io.jsonwebtoken.gson.io.GsonSupplierSerializer` class has been renamed to `GsonConfidentialValueSerializer` + - The `io.jsonwebtoken.jackson.io.JacksonSupplierSerializer` has been renamed to `JacksonConfidentialValueSerializer`.` + ### 0.13.0 This is the last minor JJWT release branch that will support Java 7. Any necessary emergency bug fixes will be fixed in subsequent `0.13.x` patch releases, but all new development, including Java 8 compatible changes, will be in the next minor (`0.14.0`) release. @@ -412,7 +429,7 @@ deprecate some concepts, or in some cases, completely break backwards compatibil `GsonSupplierSerializer` type adapter, for example: ```java new GsonBuilder() - .registerTypeHierarchyAdapter(io.jsonwebtoken.lang.Supplier.class, GsonSupplierSerializer.INSTANCE) + .registerTypeHierarchyAdapter(io.jsonwebtoken.security.ConfidentialValue.class, GsonSupplierSerializer.INSTANCE) .disableHtmlEscaping().create(); ``` This is to ensure JWKs have `toString()` and application log safety (do not print secure material), but still diff --git a/README.adoc b/README.adoc index 4ed4e1518..4ed62e940 100644 --- a/README.adoc +++ b/README.adoc @@ -15,7 +15,6 @@ ifdef::env-github[] endif::[] // Macros -:fn-require-java8-plus: Requires Java 8 or a compatible JCA Provider (like BouncyCastle) in the runtime classpath. :fn-require-java11-plus: Requires Java 11 or a compatible JCA Provider (like BouncyCastle) in the runtime classpath. :fn-require-java15-plus: Requires Java 15 or a compatible JCA Provider (like BouncyCastle) in the runtime classpath. @@ -57,7 +56,7 @@ toc::[] == Features -* Fully functional on all Java 7+ JDKs and Android +* Fully functional on all Java 8+ JDKs and Android * Automatic security best practices and assertions * Easy to learn and read API * Convenient and readable http://en.wikipedia.org/wiki/Fluent_interface[fluent] interfaces, great for IDE @@ -129,16 +128,15 @@ and conditional branch variant in the entire codebase is tested and required to | https://www.rfc-editor.org/rfc/rfc7518.html#section-5.2.5[AES_256_CBC_HMAC_SHA_512] authenticated encryption algorithm | `A128GCM` -| AES GCM using 128-bit key^*1*^ +| AES GCM using 128-bit key | `A192GCM` -| AES GCM using 192-bit key^*1*^ +| AES GCM using 192-bit key | `A256GCM` -| AES GCM using 256-bit key^*1*^ +| AES GCM using 256-bit key |=== + -^*1*.{sp}{fn-require-java8-plus}^ * All Key Management Algorithms for obtaining JWE encryption and decryption keys: + @@ -179,25 +177,24 @@ and conditional branch variant in the entire codebase is tested and required to | ECDH-ES using Concat KDF and CEK wrapped with "A256KW" | `A128GCMKW` -| Key wrapping with AES GCM using 128-bit key^*1*^ +| Key wrapping with AES GCM using 128-bit key | `A192GCMKW` -| Key wrapping with AES GCM using 192-bit key^*1*^ +| Key wrapping with AES GCM using 192-bit key | `A256GCMKW` -| Key wrapping with AES GCM using 256-bit key^*1*^ +| Key wrapping with AES GCM using 256-bit key | `PBES2-HS256+A128KW` -| PBES2 with HMAC SHA-256 and "A128KW" wrapping^*1*^ +| PBES2 with HMAC SHA-256 and "A128KW" wrapping | `PBES2-HS384+A192KW` -| PBES2 with HMAC SHA-384 and "A192KW" wrapping^*1*^ +| PBES2 with HMAC SHA-384 and "A192KW" wrapping | `PBES2‑HS512+A256KW` -| PBES2 with HMAC SHA-512 and "A256KW" wrapping^*1*^ +| PBES2 with HMAC SHA-512 and "A256KW" wrapping |=== + -^*1*.{sp}{fn-require-java8-plus}^ * Creating, parsing and verifying JSON Web Keys (JWKs) in all standard JWA key formats using native Java `Key` types: + @@ -2218,19 +2215,17 @@ The JWT specification defines 6 standard Authenticated Encryption algorithms use | `A128GCM` | 128 -| AES GCM using 128-bit key^*1*^ +| AES GCM using 128-bit key | `A192GCM` | 192 -| AES GCM using 192-bit key^*1*^ +| AES GCM using 192-bit key | `A256GCM` | 256 -| AES GCM using 256-bit key^*1*^ +| AES GCM using 256-bit key |=== -^*1*.{sp}{fn-require-java8-plus}^ - These are all represented as constants in the `io.jsonwebtoken.Jwts.ENC` registry singleton as implementations of the `io.jsonwebtoken.security.AeadAlgorithm` interface. @@ -2358,26 +2353,24 @@ Content Encryption Key (CEK): | ECDH-ES using Concat KDF and CEK wrapped with "A256KW" | `A128GCMKW` -| Key wrapping with AES GCM using 128-bit key^*1*^ +| Key wrapping with AES GCM using 128-bit key | `A192GCMKW` -| Key wrapping with AES GCM using 192-bit key^*1*^ +| Key wrapping with AES GCM using 192-bit key | `A256GCMKW` -| Key wrapping with AES GCM using 256-bit key^*1*^ +| Key wrapping with AES GCM using 256-bit key | `PBES2-HS256+A128KW` -| PBES2 with HMAC SHA-256 and "A128KW" wrapping^*1*^ +| PBES2 with HMAC SHA-256 and "A128KW" wrapping | `PBES2-HS384+A192KW` -| PBES2 with HMAC SHA-384 and "A192KW" wrapping^*1*^ +| PBES2 with HMAC SHA-384 and "A192KW" wrapping | `PBES2‑HS512+A256KW` -| PBES2 with HMAC SHA-512 and "A256KW" wrapping^*1*^ +| PBES2 with HMAC SHA-512 and "A256KW" wrapping |=== -^*1*.{sp}{fn-require-java8-plus}^ - These are all represented as constants in the `io.jsonwebtoken.Jwts.KEY` registry singleton as implementations of the `io.jsonwebtoken.security.KeyAlgorithm` interface. @@ -3472,7 +3465,7 @@ If you're curious, JJWT will automatically create an internal default Gson insta [,java] ---- new GsonBuilder() - .registerTypeHierarchyAdapter(io.jsonwebtoken.lang.Supplier.class, GsonSupplierSerializer.INSTANCE) + .registerTypeHierarchyAdapter(io.jsonwebtoken.security.ConfidentialValue.class, GsonConfidentialValueSerializer.INSTANCE) .setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE) .setNumberToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE) .disableHtmlEscaping() @@ -3515,7 +3508,7 @@ And then you can specify the `GsonSerializer` using your own `Gson` instance on Gson gson = new GsonBuilder() // don't forget this line!: - .registerTypeHierarchyAdapter(io.jsonwebtoken.lang.Supplier.class, GsonSupplierSerializer.INSTANCE) + .registerTypeHierarchyAdapter(io.jsonwebtoken.security.ConfidentialValue.class, GsonConfidentialValueSerializer.INSTANCE) .setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE) .setNumberToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE) .disableHtmlEscaping().create(); @@ -3544,7 +3537,7 @@ Again, as shown above, it is critical to create your `Gson` instance using the ` [,java] ---- -.registerTypeHierarchyAdapter(io.jsonwebtoken.lang.Supplier.class, GsonSupplierSerializer.INSTANCE) +.registerTypeHierarchyAdapter(io.jsonwebtoken.security.ConfidentialValue.class, GsonConfidentialValueSerializer.INSTANCE) ---- to ensure JWK serialization works as expected. diff --git a/api/src/main/java/io/jsonwebtoken/Clock.java b/api/src/main/java/io/jsonwebtoken/Clock.java index 584dd605f..680251628 100644 --- a/api/src/main/java/io/jsonwebtoken/Clock.java +++ b/api/src/main/java/io/jsonwebtoken/Clock.java @@ -22,6 +22,7 @@ * * @since 0.7.0 */ +@FunctionalInterface public interface Clock { /** diff --git a/api/src/main/java/io/jsonwebtoken/Identifiable.java b/api/src/main/java/io/jsonwebtoken/Identifiable.java index 88725715c..a8085220b 100644 --- a/api/src/main/java/io/jsonwebtoken/Identifiable.java +++ b/api/src/main/java/io/jsonwebtoken/Identifiable.java @@ -81,6 +81,7 @@ * * @since 0.12.0 */ +@FunctionalInterface public interface Identifiable { /** diff --git a/api/src/main/java/io/jsonwebtoken/JwtParser.java b/api/src/main/java/io/jsonwebtoken/JwtParser.java index df7e173b8..2f132e360 100644 --- a/api/src/main/java/io/jsonwebtoken/JwtParser.java +++ b/api/src/main/java/io/jsonwebtoken/JwtParser.java @@ -22,7 +22,6 @@ import java.io.InputStream; /** - * A parser for reading JWT strings, used to convert them into a {@link Jwt} object representing the expanded JWT. * A parser for reading JWT strings, used to convert them into a {@link Jwt} object representing the expanded JWT. * * @since 0.1 diff --git a/api/src/main/java/io/jsonwebtoken/Jwts.java b/api/src/main/java/io/jsonwebtoken/Jwts.java index 3e425bb34..eed9bca1b 100644 --- a/api/src/main/java/io/jsonwebtoken/Jwts.java +++ b/api/src/main/java/io/jsonwebtoken/Jwts.java @@ -19,7 +19,6 @@ import io.jsonwebtoken.lang.Builder; import io.jsonwebtoken.lang.Classes; import io.jsonwebtoken.lang.Registry; -import io.jsonwebtoken.lang.Supplier; import io.jsonwebtoken.security.AeadAlgorithm; import io.jsonwebtoken.security.KeyAlgorithm; import io.jsonwebtoken.security.KeyPairBuilderSupplier; @@ -35,6 +34,7 @@ import java.security.PrivateKey; import java.security.PublicKey; import java.util.Map; +import java.util.function.Supplier; /** * Factory class useful for creating instances of JWT interfaces. Using this factory class can be a good @@ -121,34 +121,22 @@ private ENC() { /** * "AES GCM using 128-bit key" as defined by - * RFC 7518, Section 5.31. This + * RFC 7518, Section 5.3. This * algorithm requires a 128-bit (16 byte) key. - * - *

1 Requires Java 8 or a compatible JCA Provider (like BouncyCastle) in the runtime - * classpath. If on Java 7 or earlier, BouncyCastle will be used automatically if found in the runtime - * classpath.

*/ public static final AeadAlgorithm A128GCM = get().forKey("A128GCM"); /** * "AES GCM using 192-bit key" as defined by - * RFC 7518, Section 5.31. This + * RFC 7518, Section 5.3. This * algorithm requires a 192-bit (24 byte) key. - * - *

1 Requires Java 8 or a compatible JCA Provider (like BouncyCastle) in the runtime - * classpath. If on Java 7 or earlier, BouncyCastle will be used automatically if found in the runtime - * classpath.

*/ public static final AeadAlgorithm A192GCM = get().forKey("A192GCM"); /** * "AES GCM using 256-bit key" as defined by - * RFC 7518, Section 5.31. This + * RFC 7518, Section 5.3. This * algorithm requires a 256-bit (32 byte) key. - * - *

1 Requires Java 8 or a compatible JCA Provider (like BouncyCastle) in the runtime - * classpath. If on Java 7 or earlier, BouncyCastle will be used automatically if found in the runtime - * classpath.

*/ public static final AeadAlgorithm A256GCM = get().forKey("A256GCM"); } diff --git a/api/src/main/java/io/jsonwebtoken/Locator.java b/api/src/main/java/io/jsonwebtoken/Locator.java index 1d22258c1..f0a0073c7 100644 --- a/api/src/main/java/io/jsonwebtoken/Locator.java +++ b/api/src/main/java/io/jsonwebtoken/Locator.java @@ -28,6 +28,7 @@ * @param the type of object that may be returned from the {@link #locate(Header)} method * @since 0.12.0 */ +@FunctionalInterface public interface Locator { /** diff --git a/api/src/main/java/io/jsonwebtoken/io/Base64Decoder.java b/api/src/main/java/io/jsonwebtoken/io/Base64Decoder.java index e0cb96333..8a57e8a80 100644 --- a/api/src/main/java/io/jsonwebtoken/io/Base64Decoder.java +++ b/api/src/main/java/io/jsonwebtoken/io/Base64Decoder.java @@ -19,7 +19,7 @@ /** * Very fast Base64 decoder guaranteed to - * work in all >= Java 7 JDK and Android environments. + * work in all Java JDK and Android environments. * * @since 0.10.0 */ diff --git a/api/src/main/java/io/jsonwebtoken/io/Base64Encoder.java b/api/src/main/java/io/jsonwebtoken/io/Base64Encoder.java index 6e0a6b034..249a4c4e9 100644 --- a/api/src/main/java/io/jsonwebtoken/io/Base64Encoder.java +++ b/api/src/main/java/io/jsonwebtoken/io/Base64Encoder.java @@ -19,7 +19,7 @@ /** * Very fast Base64 encoder guaranteed to - * work in all >= Java 7 JDK and Android environments. + * work in all Java JDK and Android environments. * * @since 0.10.0 */ diff --git a/api/src/main/java/io/jsonwebtoken/io/Base64UrlDecoder.java b/api/src/main/java/io/jsonwebtoken/io/Base64UrlDecoder.java index fcca4cba5..c8a76f56b 100644 --- a/api/src/main/java/io/jsonwebtoken/io/Base64UrlDecoder.java +++ b/api/src/main/java/io/jsonwebtoken/io/Base64UrlDecoder.java @@ -17,7 +17,7 @@ /** * Very fast Base64Url decoder guaranteed to - * work in all >= Java 7 JDK and Android environments. + * work in all Java JDK and Android environments. * * @since 0.10.0 */ diff --git a/api/src/main/java/io/jsonwebtoken/io/Base64UrlEncoder.java b/api/src/main/java/io/jsonwebtoken/io/Base64UrlEncoder.java index 1377d31e7..c2a1ca59f 100644 --- a/api/src/main/java/io/jsonwebtoken/io/Base64UrlEncoder.java +++ b/api/src/main/java/io/jsonwebtoken/io/Base64UrlEncoder.java @@ -17,7 +17,7 @@ /** * Very fast Base64Url encoder guaranteed to - * work in all >= Java 7 JDK and Android environments. + * work in all Java JDK and Android environments. * * @since 0.10.0 */ diff --git a/api/src/main/java/io/jsonwebtoken/io/Decoder.java b/api/src/main/java/io/jsonwebtoken/io/Decoder.java index 2cf4fe896..db391c0aa 100644 --- a/api/src/main/java/io/jsonwebtoken/io/Decoder.java +++ b/api/src/main/java/io/jsonwebtoken/io/Decoder.java @@ -22,6 +22,7 @@ * @param decoding output type * @since 0.10.0 */ +@FunctionalInterface public interface Decoder { /** diff --git a/api/src/main/java/io/jsonwebtoken/io/Decoders.java b/api/src/main/java/io/jsonwebtoken/io/Decoders.java index 6b7c7e66a..6c414245c 100644 --- a/api/src/main/java/io/jsonwebtoken/io/Decoders.java +++ b/api/src/main/java/io/jsonwebtoken/io/Decoders.java @@ -26,13 +26,13 @@ public final class Decoders { /** * Very fast Base64 decoder guaranteed to - * work in all >= Java 7 JDK and Android environments. + * work in all Java JDK and Android environments. */ public static final Decoder BASE64 = new ExceptionPropagatingDecoder<>(new Base64Decoder()); /** * Very fast Base64Url decoder guaranteed to - * work in all >= Java 7 JDK and Android environments. + * work in all Java JDK and Android environments. */ public static final Decoder BASE64URL = new ExceptionPropagatingDecoder<>(new Base64UrlDecoder()); diff --git a/api/src/main/java/io/jsonwebtoken/io/Encoder.java b/api/src/main/java/io/jsonwebtoken/io/Encoder.java index f334ee8c2..f07c9816b 100644 --- a/api/src/main/java/io/jsonwebtoken/io/Encoder.java +++ b/api/src/main/java/io/jsonwebtoken/io/Encoder.java @@ -22,6 +22,7 @@ * @param the type of the resulting formatted data * @since 0.10.0 */ +@FunctionalInterface public interface Encoder { /** diff --git a/api/src/main/java/io/jsonwebtoken/io/Encoders.java b/api/src/main/java/io/jsonwebtoken/io/Encoders.java index 17f03f250..f3b5f9913 100644 --- a/api/src/main/java/io/jsonwebtoken/io/Encoders.java +++ b/api/src/main/java/io/jsonwebtoken/io/Encoders.java @@ -26,13 +26,13 @@ public final class Encoders { /** * Very fast Base64 encoder guaranteed to - * work in all >= Java 7 JDK and Android environments. + * work in all Java JDK and Android environments. */ public static final Encoder BASE64 = new ExceptionPropagatingEncoder<>(new Base64Encoder()); /** * Very fast Base64Url encoder guaranteed to - * work in all >= Java 7 JDK and Android environments. + * work in all Java JDK and Android environments. */ public static final Encoder BASE64URL = new ExceptionPropagatingEncoder<>(new Base64UrlEncoder()); diff --git a/api/src/main/java/io/jsonwebtoken/lang/Builder.java b/api/src/main/java/io/jsonwebtoken/lang/Builder.java index 506c802dc..07499c82a 100644 --- a/api/src/main/java/io/jsonwebtoken/lang/Builder.java +++ b/api/src/main/java/io/jsonwebtoken/lang/Builder.java @@ -21,6 +21,7 @@ * @param The type of object that will be created when {@link #build()} is invoked. * @since 0.12.0 */ +@FunctionalInterface public interface Builder { /** diff --git a/api/src/main/java/io/jsonwebtoken/lang/Conjunctor.java b/api/src/main/java/io/jsonwebtoken/lang/Conjunctor.java index c604752fd..b754b7220 100644 --- a/api/src/main/java/io/jsonwebtoken/lang/Conjunctor.java +++ b/api/src/main/java/io/jsonwebtoken/lang/Conjunctor.java @@ -22,6 +22,7 @@ * @param the type of joined object to return. * @since 0.12.0 */ +@FunctionalInterface public interface Conjunctor { /** diff --git a/api/src/main/java/io/jsonwebtoken/security/AssociatedDataSupplier.java b/api/src/main/java/io/jsonwebtoken/security/AssociatedDataSupplier.java index 4f5cd3719..564d17ca0 100644 --- a/api/src/main/java/io/jsonwebtoken/security/AssociatedDataSupplier.java +++ b/api/src/main/java/io/jsonwebtoken/security/AssociatedDataSupplier.java @@ -24,6 +24,7 @@ * @see #getAssociatedData() * @since 0.12.0 */ +@FunctionalInterface public interface AssociatedDataSupplier { /** diff --git a/api/src/main/java/io/jsonwebtoken/lang/Supplier.java b/api/src/main/java/io/jsonwebtoken/security/ConfidentialValue.java similarity index 51% rename from api/src/main/java/io/jsonwebtoken/lang/Supplier.java rename to api/src/main/java/io/jsonwebtoken/security/ConfidentialValue.java index 7a94e591c..f0db518fc 100644 --- a/api/src/main/java/io/jsonwebtoken/lang/Supplier.java +++ b/api/src/main/java/io/jsonwebtoken/security/ConfidentialValue.java @@ -13,25 +13,24 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.jsonwebtoken.lang; +package io.jsonwebtoken.security; /** - * Represents a supplier of results. + * A wrapper for a value that should be treated as confidential. Calling {@link Object#toString()} on a + * {@code ConfidentialValue} instance will return the string literal <redacted>. * - *

There is no requirement that a new or distinct result be returned each time the supplier is invoked.

- * - *

This interface is the equivalent of a JDK 8 {@code java.util.function.Supplier}, backported for JJWT's use in - * JDK 7 environments.

+ *

There is no requirement that a new or distinct result be returned each time the value is invoked.

* * @param the type of object returned by this supplier - * @since 0.12.0 + * @since 0.14.0, renamed and moved from {@code io.jsonwebtoken.lang.Supplier} introduced in 0.12.0 */ -public interface Supplier { +// MAINTAINER NOTE: do not mark this as @FunctionalInterface - it makes it easier to break the toString() contract +public interface ConfidentialValue { /** - * Returns a result. + * Returns a confidential value that should be treated with care and not exposed unnecessarily. * - * @return a result. + * @return a confidential value that should be treated with care and not exposed unnecessarily. */ T get(); } diff --git a/api/src/main/java/io/jsonwebtoken/security/DigestSupplier.java b/api/src/main/java/io/jsonwebtoken/security/DigestSupplier.java index 4c697d9bf..d4d829e73 100644 --- a/api/src/main/java/io/jsonwebtoken/security/DigestSupplier.java +++ b/api/src/main/java/io/jsonwebtoken/security/DigestSupplier.java @@ -21,6 +21,7 @@ * * @since 0.12.0 */ +@FunctionalInterface public interface DigestSupplier { /** diff --git a/api/src/main/java/io/jsonwebtoken/security/IvSupplier.java b/api/src/main/java/io/jsonwebtoken/security/IvSupplier.java index f1cc3d506..bc0c4e952 100644 --- a/api/src/main/java/io/jsonwebtoken/security/IvSupplier.java +++ b/api/src/main/java/io/jsonwebtoken/security/IvSupplier.java @@ -23,6 +23,7 @@ * * @since 0.12.0 */ +@FunctionalInterface public interface IvSupplier { /** diff --git a/api/src/main/java/io/jsonwebtoken/security/Jwk.java b/api/src/main/java/io/jsonwebtoken/security/Jwk.java index fef6420aa..cd09f082b 100644 --- a/api/src/main/java/io/jsonwebtoken/security/Jwk.java +++ b/api/src/main/java/io/jsonwebtoken/security/Jwk.java @@ -16,7 +16,6 @@ package io.jsonwebtoken.security; import io.jsonwebtoken.Identifiable; -import io.jsonwebtoken.lang.Supplier; import java.security.Key; import java.util.Map; @@ -49,8 +48,8 @@ * *

JWKs often represent secret or private key data which should never be exposed publicly, nor mistakenly printed * to application logs or {@code System.out.println} calls. As a result, all JJWT JWK - * private or secret values are 'wrapped' in a {@link io.jsonwebtoken.lang.Supplier Supplier} instance to ensure - * any attempt to call {@link String#toString() toString()} on the value will print a redacted value instead of an + * private or secret values are 'wrapped' in a {@link ConfidentialValue ConfidentialValue} instance to ensure + * any attempt to call {@link Object#toString() toString()} on the value will print a redacted value instead of an * actual private or secret value.

* *

For example, a {@link SecretJwk} will have an internal "{@code k}" member whose value reflects raw @@ -70,11 +69,11 @@ * k=<redacted> *

instead of the actual/raw {@code k} value.

* - *

Finally, because all private or secret values are wrapped as {@link io.jsonwebtoken.lang.Supplier} + *

Finally, because all private or secret values are wrapped as {@link ConfidentialValue} * instances, if you really wanted the real internal value, you could just call the supplier's - * {@link Supplier#get() get()} method:

+ * {@link ConfidentialValue#get() get()} method:

*
- * String k = ((Supplier<String>)aSecretJwk.get("k")).get();
+ * String k = ((ConfidentialValue<String>)aSecretJwk.get("k")).get(); *

but BE CAREFUL: obtaining the raw value in your application code exposes greater security * risk - you must ensure to keep that value safe and out of console or log output. It is almost always better to * interact with the JWK's {@link #toKey() toKey()} instance directly instead of accessing diff --git a/api/src/main/java/io/jsonwebtoken/security/Jwks.java b/api/src/main/java/io/jsonwebtoken/security/Jwks.java index ba59847cf..0701669fa 100644 --- a/api/src/main/java/io/jsonwebtoken/security/Jwks.java +++ b/api/src/main/java/io/jsonwebtoken/security/Jwks.java @@ -19,7 +19,8 @@ import io.jsonwebtoken.io.Parser; import io.jsonwebtoken.lang.Classes; import io.jsonwebtoken.lang.Registry; -import io.jsonwebtoken.lang.Supplier; + +import java.util.function.Supplier; /** * Utility methods for creating diff --git a/api/src/main/java/io/jsonwebtoken/security/KeyBuilderSupplier.java b/api/src/main/java/io/jsonwebtoken/security/KeyBuilderSupplier.java index d55611209..f78175863 100644 --- a/api/src/main/java/io/jsonwebtoken/security/KeyBuilderSupplier.java +++ b/api/src/main/java/io/jsonwebtoken/security/KeyBuilderSupplier.java @@ -27,6 +27,7 @@ * @see KeyBuilder * @since 0.12.0 */ +@FunctionalInterface public interface KeyBuilderSupplier> { /** diff --git a/api/src/main/java/io/jsonwebtoken/security/KeyLengthSupplier.java b/api/src/main/java/io/jsonwebtoken/security/KeyLengthSupplier.java index f550dcfd9..db50cfbe3 100644 --- a/api/src/main/java/io/jsonwebtoken/security/KeyLengthSupplier.java +++ b/api/src/main/java/io/jsonwebtoken/security/KeyLengthSupplier.java @@ -20,6 +20,7 @@ * * @since 0.12.0 */ +@FunctionalInterface public interface KeyLengthSupplier { /** diff --git a/api/src/main/java/io/jsonwebtoken/security/KeyOperationPolicied.java b/api/src/main/java/io/jsonwebtoken/security/KeyOperationPolicied.java index 49e893879..060d01b39 100644 --- a/api/src/main/java/io/jsonwebtoken/security/KeyOperationPolicied.java +++ b/api/src/main/java/io/jsonwebtoken/security/KeyOperationPolicied.java @@ -21,6 +21,7 @@ * * @param the implementing instance for method chaining */ +@FunctionalInterface public interface KeyOperationPolicied> { /** diff --git a/api/src/main/java/io/jsonwebtoken/security/KeyOperationPolicy.java b/api/src/main/java/io/jsonwebtoken/security/KeyOperationPolicy.java index 60389a24b..184dd6212 100644 --- a/api/src/main/java/io/jsonwebtoken/security/KeyOperationPolicy.java +++ b/api/src/main/java/io/jsonwebtoken/security/KeyOperationPolicy.java @@ -38,6 +38,5 @@ public interface KeyOperationPolicy { * * @param ops the operations to validate */ - @SuppressWarnings("GrazieInspection") void validate(Collection ops) throws IllegalArgumentException; } diff --git a/api/src/main/java/io/jsonwebtoken/security/KeyPairBuilderSupplier.java b/api/src/main/java/io/jsonwebtoken/security/KeyPairBuilderSupplier.java index 98d42eae6..f331fd03c 100644 --- a/api/src/main/java/io/jsonwebtoken/security/KeyPairBuilderSupplier.java +++ b/api/src/main/java/io/jsonwebtoken/security/KeyPairBuilderSupplier.java @@ -25,6 +25,7 @@ * @see KeyPairBuilder * @since 0.12.0 */ +@FunctionalInterface public interface KeyPairBuilderSupplier { /** diff --git a/api/src/main/java/io/jsonwebtoken/security/KeySupplier.java b/api/src/main/java/io/jsonwebtoken/security/KeySupplier.java index 2026b2553..b475d40eb 100644 --- a/api/src/main/java/io/jsonwebtoken/security/KeySupplier.java +++ b/api/src/main/java/io/jsonwebtoken/security/KeySupplier.java @@ -23,6 +23,7 @@ * @param the type of key provided by this supplier. * @since 0.12.0 */ +@FunctionalInterface public interface KeySupplier { /** diff --git a/api/src/main/java/io/jsonwebtoken/security/Message.java b/api/src/main/java/io/jsonwebtoken/security/Message.java index cd5e8df16..8b9ab1dd6 100644 --- a/api/src/main/java/io/jsonwebtoken/security/Message.java +++ b/api/src/main/java/io/jsonwebtoken/security/Message.java @@ -23,6 +23,7 @@ * @param The type of payload in the message. * @since 0.12.0 */ +@FunctionalInterface public interface Message { /** diff --git a/extensions/gson/src/main/java/io/jsonwebtoken/gson/io/GsonSupplierSerializer.java b/extensions/gson/src/main/java/io/jsonwebtoken/gson/io/GsonConfidentialValueSerializer.java similarity index 66% rename from extensions/gson/src/main/java/io/jsonwebtoken/gson/io/GsonSupplierSerializer.java rename to extensions/gson/src/main/java/io/jsonwebtoken/gson/io/GsonConfidentialValueSerializer.java index 8ae511716..a9b8e552d 100644 --- a/extensions/gson/src/main/java/io/jsonwebtoken/gson/io/GsonSupplierSerializer.java +++ b/extensions/gson/src/main/java/io/jsonwebtoken/gson/io/GsonConfidentialValueSerializer.java @@ -18,17 +18,17 @@ import com.google.gson.JsonElement; import com.google.gson.JsonSerializationContext; import com.google.gson.JsonSerializer; -import io.jsonwebtoken.lang.Supplier; +import io.jsonwebtoken.security.ConfidentialValue; import java.lang.reflect.Type; -public final class GsonSupplierSerializer implements JsonSerializer> { +public final class GsonConfidentialValueSerializer implements JsonSerializer> { - public static final GsonSupplierSerializer INSTANCE = new GsonSupplierSerializer(); + public static final GsonConfidentialValueSerializer INSTANCE = new GsonConfidentialValueSerializer(); @Override - public JsonElement serialize(Supplier supplier, Type type, JsonSerializationContext ctx) { - Object value = supplier.get(); + public JsonElement serialize(ConfidentialValue confidentialValue, Type type, JsonSerializationContext ctx) { + Object value = confidentialValue.get(); return ctx.serialize(value); } } diff --git a/extensions/gson/src/main/java/io/jsonwebtoken/gson/io/GsonSerializer.java b/extensions/gson/src/main/java/io/jsonwebtoken/gson/io/GsonSerializer.java index 18beeb1f7..e90d08d0c 100644 --- a/extensions/gson/src/main/java/io/jsonwebtoken/gson/io/GsonSerializer.java +++ b/extensions/gson/src/main/java/io/jsonwebtoken/gson/io/GsonSerializer.java @@ -22,7 +22,7 @@ import io.jsonwebtoken.io.Encoders; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Objects; -import io.jsonwebtoken.lang.Supplier; +import io.jsonwebtoken.security.ConfidentialValue; import java.io.OutputStream; import java.io.OutputStreamWriter; @@ -34,7 +34,7 @@ public class GsonSerializer extends AbstractSerializer { static final Gson DEFAULT_GSON = new GsonBuilder() .setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE) .setNumberToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE) - .registerTypeHierarchyAdapter(Supplier.class, GsonSupplierSerializer.INSTANCE) + .registerTypeHierarchyAdapter(ConfidentialValue.class, GsonConfidentialValueSerializer.INSTANCE) .disableHtmlEscaping().create(); protected final Gson gson; @@ -48,13 +48,13 @@ public GsonSerializer(Gson gson) { this.gson = gson; //ensure the necessary type adapter has been registered, and if not, throw an error: - String json = this.gson.toJson(TestSupplier.INSTANCE); + String json = this.gson.toJson(TestConfidentialValue.INSTANCE); if (json.contains("value")) { String msg = "Invalid Gson instance - it has not been registered with the necessary " + - Supplier.class.getName() + " type adapter. When using the GsonBuilder, ensure this " + + ConfidentialValue.class.getName() + " type adapter. When using the GsonBuilder, ensure this " + "type adapter is registered by calling gsonBuilder.registerTypeHierarchyAdapter(" + - Supplier.class.getName() + ".class, " + - GsonSupplierSerializer.class.getName() + ".INSTANCE) before calling gsonBuilder.create()"; + ConfidentialValue.class.getName() + ".class, " + + GsonConfidentialValueSerializer.class.getName() + ".INSTANCE) before calling gsonBuilder.create()"; throw new IllegalArgumentException(msg); } } @@ -79,12 +79,12 @@ protected void writeValue(Object o, java.io.Writer writer) { this.gson.toJson(o, writer); } - private static class TestSupplier implements Supplier { + private static class TestConfidentialValue implements ConfidentialValue { - private static final TestSupplier INSTANCE = new TestSupplier<>("test"); + private static final TestConfidentialValue INSTANCE = new TestConfidentialValue<>("test"); private final T value; - private TestSupplier(T value) { + private TestConfidentialValue(T value) { this.value = value; } diff --git a/extensions/gson/src/test/groovy/io/jsonwebtoken/gson/io/GsonSerializerTest.groovy b/extensions/gson/src/test/groovy/io/jsonwebtoken/gson/io/GsonSerializerTest.groovy index 624831b77..d0b6b4b34 100644 --- a/extensions/gson/src/test/groovy/io/jsonwebtoken/gson/io/GsonSerializerTest.groovy +++ b/extensions/gson/src/test/groovy/io/jsonwebtoken/gson/io/GsonSerializerTest.groovy @@ -21,7 +21,7 @@ import com.google.gson.GsonBuilder import io.jsonwebtoken.io.SerializationException import io.jsonwebtoken.io.Serializer import io.jsonwebtoken.lang.Strings -import io.jsonwebtoken.lang.Supplier +import io.jsonwebtoken.security.ConfidentialValue import org.junit.Before import org.junit.Test @@ -54,7 +54,7 @@ class GsonSerializerTest { @Test void testGsonConstructor() { def customGSON = new GsonBuilder() - .registerTypeHierarchyAdapter(Supplier.class, GsonSupplierSerializer.INSTANCE) + .registerTypeHierarchyAdapter(ConfidentialValue.class, GsonConfidentialValueSerializer.INSTANCE) .disableHtmlEscaping().create() s = new GsonSerializer(customGSON) assertSame customGSON, s.gson @@ -83,10 +83,11 @@ class GsonSerializerTest { fail() } catch (IllegalArgumentException expected) { String msg = 'Invalid Gson instance - it has not been registered with the necessary ' + - 'io.jsonwebtoken.lang.Supplier type adapter. When using the GsonBuilder, ensure this type ' + - 'adapter is registered by calling ' + - 'gsonBuilder.registerTypeHierarchyAdapter(io.jsonwebtoken.lang.Supplier.class, ' + - 'io.jsonwebtoken.gson.io.GsonSupplierSerializer.INSTANCE) before calling gsonBuilder.create()' + 'io.jsonwebtoken.security.ConfidentialValue type adapter. When using the GsonBuilder, ' + + 'ensure this type adapter is registered by calling ' + + 'gsonBuilder.registerTypeHierarchyAdapter(io.jsonwebtoken.security.ConfidentialValue.class, ' + + 'io.jsonwebtoken.gson.io.GsonConfidentialValueSerializer.INSTANCE) ' + + 'before calling gsonBuilder.create()' assertEquals msg, expected.message } } diff --git a/extensions/jackson/src/main/java/io/jsonwebtoken/jackson/io/JacksonSupplierSerializer.java b/extensions/jackson/src/main/java/io/jsonwebtoken/jackson/io/JacksonConfidentialValueSerializer.java similarity index 69% rename from extensions/jackson/src/main/java/io/jsonwebtoken/jackson/io/JacksonSupplierSerializer.java rename to extensions/jackson/src/main/java/io/jsonwebtoken/jackson/io/JacksonConfidentialValueSerializer.java index a415bcf09..1bfcb0013 100644 --- a/extensions/jackson/src/main/java/io/jsonwebtoken/jackson/io/JacksonSupplierSerializer.java +++ b/extensions/jackson/src/main/java/io/jsonwebtoken/jackson/io/JacksonConfidentialValueSerializer.java @@ -19,21 +19,21 @@ import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.ser.std.StdSerializer; -import io.jsonwebtoken.lang.Supplier; +import io.jsonwebtoken.security.ConfidentialValue; import java.io.IOException; -final class JacksonSupplierSerializer extends StdSerializer> { +final class JacksonConfidentialValueSerializer extends StdSerializer> { - static final JacksonSupplierSerializer INSTANCE = new JacksonSupplierSerializer(); + static final JacksonConfidentialValueSerializer INSTANCE = new JacksonConfidentialValueSerializer(); - public JacksonSupplierSerializer() { - super(Supplier.class, false); + public JacksonConfidentialValueSerializer() { + super(ConfidentialValue.class, false); } @Override - public void serialize(Supplier supplier, JsonGenerator generator, SerializerProvider provider) throws IOException { - Object value = supplier.get(); + public void serialize(ConfidentialValue confidentialValue, JsonGenerator generator, SerializerProvider provider) throws IOException { + Object value = confidentialValue.get(); if (value == null) { provider.defaultSerializeNull(generator); diff --git a/extensions/jackson/src/main/java/io/jsonwebtoken/jackson/io/JacksonDeserializer.java b/extensions/jackson/src/main/java/io/jsonwebtoken/jackson/io/JacksonDeserializer.java index 5736134f8..b3fe900f1 100644 --- a/extensions/jackson/src/main/java/io/jsonwebtoken/jackson/io/JacksonDeserializer.java +++ b/extensions/jackson/src/main/java/io/jsonwebtoken/jackson/io/JacksonDeserializer.java @@ -17,6 +17,7 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.deser.std.UntypedObjectDeserializer; import com.fasterxml.jackson.databind.module.SimpleModule; @@ -145,7 +146,7 @@ private static class MappedTypeDeserializer extends UntypedObjectDeserializer { private final Map> claimTypeMap; private MappedTypeDeserializer(Map> claimTypeMap) { - super(null, null); + super((JavaType)null, null); this.claimTypeMap = claimTypeMap; } diff --git a/extensions/jackson/src/main/java/io/jsonwebtoken/jackson/io/JacksonSerializer.java b/extensions/jackson/src/main/java/io/jsonwebtoken/jackson/io/JacksonSerializer.java index a00541b61..5d8bca834 100644 --- a/extensions/jackson/src/main/java/io/jsonwebtoken/jackson/io/JacksonSerializer.java +++ b/extensions/jackson/src/main/java/io/jsonwebtoken/jackson/io/JacksonSerializer.java @@ -39,7 +39,7 @@ public class JacksonSerializer extends AbstractSerializer { static { SimpleModule module = new SimpleModule(MODULE_ID); - module.addSerializer(JacksonSupplierSerializer.INSTANCE); + module.addSerializer(JacksonConfidentialValueSerializer.INSTANCE); MODULE = module; } diff --git a/extensions/jackson/src/test/groovy/io/jsonwebtoken/jackson/io/JacksonSupplierSerializerTest.groovy b/extensions/jackson/src/test/groovy/io/jsonwebtoken/jackson/io/JacksonConfidentialValueSerializerTest.groovy similarity index 88% rename from extensions/jackson/src/test/groovy/io/jsonwebtoken/jackson/io/JacksonSupplierSerializerTest.groovy rename to extensions/jackson/src/test/groovy/io/jsonwebtoken/jackson/io/JacksonConfidentialValueSerializerTest.groovy index 90399c95a..c923ad3ae 100644 --- a/extensions/jackson/src/test/groovy/io/jsonwebtoken/jackson/io/JacksonSupplierSerializerTest.groovy +++ b/extensions/jackson/src/test/groovy/io/jsonwebtoken/jackson/io/JacksonConfidentialValueSerializerTest.groovy @@ -15,18 +15,19 @@ */ package io.jsonwebtoken.jackson.io + import io.jsonwebtoken.lang.Strings -import io.jsonwebtoken.lang.Supplier +import io.jsonwebtoken.security.ConfidentialValue import org.junit.Test import static org.junit.Assert.assertEquals -class JacksonSupplierSerializerTest { +class JacksonConfidentialValueSerializerTest { @Test void testSupplierNullValue() { def serializer = new JacksonSerializer() - def supplier = new Supplier() { + def supplier = new ConfidentialValue() { @Override Object get() { return null @@ -40,7 +41,7 @@ class JacksonSupplierSerializerTest { @Test void testSupplierStringValue() { def serializer = new JacksonSerializer() - def supplier = new Supplier() { + def supplier = new ConfidentialValue() { @Override Object get() { return 'hello' diff --git a/extensions/jackson/src/test/groovy/io/jsonwebtoken/jackson/io/JacksonDeserializerTest.groovy b/extensions/jackson/src/test/groovy/io/jsonwebtoken/jackson/io/JacksonDeserializerTest.groovy index b21667441..aba1f921a 100644 --- a/extensions/jackson/src/test/groovy/io/jsonwebtoken/jackson/io/JacksonDeserializerTest.groovy +++ b/extensions/jackson/src/test/groovy/io/jsonwebtoken/jackson/io/JacksonDeserializerTest.groovy @@ -139,7 +139,7 @@ class JacksonDeserializerTest { new JacksonDeserializer<>().deserialize(new StringReader(json)) fail() } catch (DeserializationException expected) { - String causeMsg = "Duplicate field 'bKey'\n at [Source: (StringReader); line: 5, column: 23]" + String causeMsg = "Duplicate field 'bKey'\n at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 5, column: 23]" String msg = "Unable to deserialize: $causeMsg" assertEquals msg, expected.getMessage() assertTrue expected.getCause() instanceof JsonParseException @@ -222,17 +222,6 @@ class JacksonDeserializerTest { } } - // TODO: the following does NOT work with Java 1.7 - // when we stop supporting that version we can use a partial mock instead - // the `typeMap.put("custom", CustomBean)` line below results in an NPE, (only on 1.7) - -// Map typeMap = partialMockBuilder(HashMap) -// .addMockedMethod("containsKey") -// .createNiceMock() -// -// expect(typeMap.containsKey(null)).andThrow(new NullPointerException("key is null, expected for this test")) -// replay(typeMap) - typeMap.put("custom", CustomBean) def deserializer = new JacksonDeserializer(typeMap) diff --git a/extensions/jackson/src/test/groovy/io/jsonwebtoken/jackson/io/TestSupplier.groovy b/extensions/jackson/src/test/groovy/io/jsonwebtoken/jackson/io/TestConfidentialValue.groovy similarity index 74% rename from extensions/jackson/src/test/groovy/io/jsonwebtoken/jackson/io/TestSupplier.groovy rename to extensions/jackson/src/test/groovy/io/jsonwebtoken/jackson/io/TestConfidentialValue.groovy index 4faf2c8ac..08b619f52 100644 --- a/extensions/jackson/src/test/groovy/io/jsonwebtoken/jackson/io/TestSupplier.groovy +++ b/extensions/jackson/src/test/groovy/io/jsonwebtoken/jackson/io/TestConfidentialValue.groovy @@ -15,14 +15,15 @@ */ package io.jsonwebtoken.jackson.io -import io.jsonwebtoken.lang.Supplier -class TestSupplier implements Supplier { +import io.jsonwebtoken.security.ConfidentialValue - private static final TestSupplier INSTANCE = new TestSupplier<>("test") +class TestConfidentialValue implements ConfidentialValue { + + private static final TestConfidentialValue INSTANCE = new TestConfidentialValue<>("test") private final T value; - private TestSupplier(T value) { + private TestConfidentialValue(T value) { this.value = value; } diff --git a/extensions/orgjson/src/main/java/io/jsonwebtoken/orgjson/io/OrgJsonSerializer.java b/extensions/orgjson/src/main/java/io/jsonwebtoken/orgjson/io/OrgJsonSerializer.java index 0c81f5ce4..da27e5a57 100644 --- a/extensions/orgjson/src/main/java/io/jsonwebtoken/orgjson/io/OrgJsonSerializer.java +++ b/extensions/orgjson/src/main/java/io/jsonwebtoken/orgjson/io/OrgJsonSerializer.java @@ -22,7 +22,7 @@ import io.jsonwebtoken.lang.DateFormats; import io.jsonwebtoken.lang.Objects; import io.jsonwebtoken.lang.Strings; -import io.jsonwebtoken.lang.Supplier; +import io.jsonwebtoken.security.ConfidentialValue; import org.json.JSONArray; import org.json.JSONObject; @@ -78,8 +78,8 @@ private Object toJSONInstance(Object object) throws IOException { return JSONObject.NULL; } - if (object instanceof Supplier) { - object = ((Supplier) object).get(); + if (object instanceof ConfidentialValue) { + object = ((ConfidentialValue) object).get(); } if (object instanceof JSONObject || object instanceof JSONArray diff --git a/extensions/orgjson/src/test/groovy/io/jsonwebtoken/orgjson/io/OrgJsonSerializerTest.groovy b/extensions/orgjson/src/test/groovy/io/jsonwebtoken/orgjson/io/OrgJsonSerializerTest.groovy index 382c5bb14..e8e5dcdf9 100644 --- a/extensions/orgjson/src/test/groovy/io/jsonwebtoken/orgjson/io/OrgJsonSerializerTest.groovy +++ b/extensions/orgjson/src/test/groovy/io/jsonwebtoken/orgjson/io/OrgJsonSerializerTest.groovy @@ -21,7 +21,7 @@ import io.jsonwebtoken.io.SerializationException import io.jsonwebtoken.io.Serializer import io.jsonwebtoken.lang.DateFormats import io.jsonwebtoken.lang.Strings -import io.jsonwebtoken.lang.Supplier +import io.jsonwebtoken.security.ConfidentialValue import org.json.JSONObject import org.json.JSONString import org.junit.Before @@ -165,7 +165,7 @@ class OrgJsonSerializerTest { @Test void testSupplier() { - def supplier = new Supplier() { + def supplier = new ConfidentialValue() { @Override Object get() { return 'test' diff --git a/impl/src/main/java/io/jsonwebtoken/impl/CompressionCodecLocator.java b/impl/src/main/java/io/jsonwebtoken/impl/CompressionCodecLocator.java index fe00bb545..211a60e7f 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/CompressionCodecLocator.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/CompressionCodecLocator.java @@ -18,10 +18,11 @@ import io.jsonwebtoken.CompressionCodecResolver; import io.jsonwebtoken.Header; import io.jsonwebtoken.Locator; -import io.jsonwebtoken.impl.lang.Function; import io.jsonwebtoken.io.CompressionAlgorithm; import io.jsonwebtoken.lang.Assert; +import java.util.function.Function; + //TODO: delete when deleting CompressionCodecResolver public class CompressionCodecLocator implements Function, Locator { diff --git a/impl/src/main/java/io/jsonwebtoken/impl/DefaultClaimsBuilder.java b/impl/src/main/java/io/jsonwebtoken/impl/DefaultClaimsBuilder.java index d4b63f778..11f738faa 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/DefaultClaimsBuilder.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/DefaultClaimsBuilder.java @@ -36,7 +36,7 @@ public Claims build() { // @since 0.12.7 per https://github.com/jwtk/jjwt/issues/988 @SuppressWarnings("unused") // used via reflection in the api module's Jwts class. - public static final class Supplier implements io.jsonwebtoken.lang.Supplier { + public static final class Supplier implements java.util.function.Supplier { @Override public ClaimsBuilder get() { return new DefaultClaimsBuilder(); diff --git a/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtBuilder.java b/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtBuilder.java index 27fd42d20..56706ba30 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtBuilder.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtBuilder.java @@ -29,7 +29,6 @@ import io.jsonwebtoken.impl.io.Streams; import io.jsonwebtoken.impl.io.UncloseableInputStream; import io.jsonwebtoken.impl.lang.Bytes; -import io.jsonwebtoken.impl.lang.Function; import io.jsonwebtoken.impl.lang.Functions; import io.jsonwebtoken.impl.lang.Parameter; import io.jsonwebtoken.impl.lang.Services; @@ -77,6 +76,7 @@ import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; +import java.util.function.Function; public class DefaultJwtBuilder implements JwtBuilder { @@ -239,12 +239,7 @@ public JwtBuilder signWith(K key, final SecureDigestAlgorithm) alg; - this.signFunction = Functions.wrap(new Function, byte[]>() { - @Override - public byte[] apply(SecureRequest request) { - return sigAlg.digest(request); - } - }, SignatureException.class, "Unable to compute %s signature.", id); + this.signFunction = Functions.wrap(request -> sigAlg.digest(request), SignatureException.class, "Unable to compute %s signature.", id); return this; } @@ -309,12 +304,7 @@ public JwtBuilder encryptWith(final K key, final KeyAlgorithm alg = this.keyAlg; final String cekMsg = "Unable to obtain content encryption key from key management algorithm '%s'."; - this.keyAlgFunction = Functions.wrap(new Function, KeyResult>() { - @Override - public KeyResult apply(KeyRequest request) { - return alg.getEncryptionKey(request); - } - }, SecurityException.class, cekMsg, algId); + this.keyAlgFunction = Functions.wrap(alg::getEncryptionKey, SecurityException.class, cekMsg, algId); return this; } @@ -434,7 +424,7 @@ public JwtBuilder setAudience(String aud) { @Override public AudienceCollection audience() { - return new DelegateAudienceCollection<>((JwtBuilder) this, claims().audience()); + return new DelegateAudienceCollection<>(this, claims().audience()); } @Override @@ -668,12 +658,9 @@ private String unprotected(final Payload content) { } private void encrypt(final AeadRequest req, final AeadResult res) throws SecurityException { - Function fn = Functions.wrap(new Function() { - @Override - public Object apply(Object o) { - enc.encrypt(req, res); - return null; - } + Function fn = Functions.wrap(o -> { + enc.encrypt(req, res); + return null; }, SecurityException.class, "%s encryption failed.", enc.getId()); fn.apply(null); } @@ -820,7 +807,7 @@ private InputStream toInputStream(final String name, Payload payload) { // @since 0.12.7 per https://github.com/jwtk/jjwt/issues/988 @SuppressWarnings("unused") // used via reflection in the api module's Jwts class. - public static final class Supplier implements io.jsonwebtoken.lang.Supplier { + public static final class Supplier implements java.util.function.Supplier { @Override public JwtBuilder get() { return new DefaultJwtBuilder(); diff --git a/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtHeaderBuilder.java b/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtHeaderBuilder.java index bace2d671..f696cfe86 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtHeaderBuilder.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtHeaderBuilder.java @@ -87,7 +87,7 @@ public Header build() { // @since 0.12.7 per https://github.com/jwtk/jjwt/issues/988 @SuppressWarnings("unused") // used via reflection in the api module's Jwts class. - public static final class Supplier implements io.jsonwebtoken.lang.Supplier { + public static final class Supplier implements java.util.function.Supplier { @Override public Jwts.HeaderBuilder get() { return new DefaultJwtHeaderBuilder(); diff --git a/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java b/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java index 622f5d78b..b646f7d05 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java @@ -45,8 +45,7 @@ import io.jsonwebtoken.impl.io.Streams; import io.jsonwebtoken.impl.io.UncloseableInputStream; import io.jsonwebtoken.impl.lang.Bytes; -import io.jsonwebtoken.impl.lang.Function; -import io.jsonwebtoken.impl.lang.RedactedSupplier; +import io.jsonwebtoken.impl.lang.RedactedConfidentialValue; import io.jsonwebtoken.impl.security.DefaultDecryptAeadRequest; import io.jsonwebtoken.impl.security.DefaultDecryptionKeyRequest; import io.jsonwebtoken.impl.security.DefaultVerifySecureDigestRequest; @@ -90,6 +89,7 @@ import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; +import java.util.function.Function; @SuppressWarnings("unchecked") public class DefaultJwtParser extends AbstractParser> implements JwtParser { @@ -883,7 +883,7 @@ protected byte[] decode(CharSequence base64UrlEncoded, String name) { return Streams.bytes(decoding, "Unable to Base64Url-decode input."); } catch (Throwable t) { // Don't disclose potentially-sensitive information per https://github.com/jwtk/jjwt/issues/824: - String value = "payload".equals(name) ? RedactedSupplier.REDACTED_VALUE : base64UrlEncoded.toString(); + String value = "payload".equals(name) ? RedactedConfidentialValue.REDACTED_VALUE : base64UrlEncoded.toString(); String msg = "Invalid Base64Url " + name + ": " + value; throw new MalformedJwtException(msg, t); } diff --git a/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParserBuilder.java b/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParserBuilder.java index e113064c3..c12b1054b 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParserBuilder.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParserBuilder.java @@ -430,7 +430,7 @@ public JwtParser build() { // @since 0.12.7 per https://github.com/jwtk/jjwt/issues/988 @SuppressWarnings("unused") // used via reflection in the api module's Jwts class. - public static final class Supplier implements io.jsonwebtoken.lang.Supplier { + public static final class Supplier implements java.util.function.Supplier { @Override public JwtParserBuilder get() { return new DefaultJwtParserBuilder(); diff --git a/impl/src/main/java/io/jsonwebtoken/impl/IdLocator.java b/impl/src/main/java/io/jsonwebtoken/impl/IdLocator.java index 88b8df2cb..cceac6c4d 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/IdLocator.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/IdLocator.java @@ -20,12 +20,13 @@ import io.jsonwebtoken.Locator; import io.jsonwebtoken.MalformedJwtException; import io.jsonwebtoken.UnsupportedJwtException; -import io.jsonwebtoken.impl.lang.Function; import io.jsonwebtoken.impl.lang.Parameter; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Registry; import io.jsonwebtoken.lang.Strings; +import java.util.function.Function; + public class IdLocator implements Locator, Function { private final Parameter param; diff --git a/impl/src/main/java/io/jsonwebtoken/impl/ParameterMap.java b/impl/src/main/java/io/jsonwebtoken/impl/ParameterMap.java index c8e462ec0..070336ad9 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/ParameterMap.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/ParameterMap.java @@ -19,7 +19,7 @@ import io.jsonwebtoken.impl.lang.Parameter; import io.jsonwebtoken.impl.lang.ParameterReadable; import io.jsonwebtoken.impl.lang.Parameters; -import io.jsonwebtoken.impl.lang.RedactedSupplier; +import io.jsonwebtoken.impl.lang.RedactedConfidentialValue; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.lang.Objects; @@ -181,7 +181,7 @@ private Object apply(Parameter param, Object rawValue) { StringBuilder sb = new StringBuilder(100); sb.append("Invalid ").append(getName()).append(" ").append(param).append(" value"); if (param.isSecret()) { - sb.append(": ").append(RedactedSupplier.REDACTED_VALUE); + sb.append(": ").append(RedactedConfidentialValue.REDACTED_VALUE); } else if (!(rawValue instanceof byte[])) { // don't print raw byte array gibberish. We can't base64[url] encode it either because that could // make the exception message confusing: the developer would see an encoded string and could think diff --git a/impl/src/main/java/io/jsonwebtoken/impl/compression/AbstractCompressionAlgorithm.java b/impl/src/main/java/io/jsonwebtoken/impl/compression/AbstractCompressionAlgorithm.java index 1e6d6f0ba..6d7fdcd73 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/compression/AbstractCompressionAlgorithm.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/compression/AbstractCompressionAlgorithm.java @@ -20,7 +20,6 @@ import io.jsonwebtoken.impl.io.Streams; import io.jsonwebtoken.impl.lang.Bytes; import io.jsonwebtoken.impl.lang.CheckedFunction; -import io.jsonwebtoken.impl.lang.Function; import io.jsonwebtoken.impl.lang.PropagatingExceptionFunction; import io.jsonwebtoken.io.CompressionAlgorithm; import io.jsonwebtoken.lang.Assert; @@ -31,6 +30,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.function.Function; /** * Abstract class that asserts arguments and wraps IOException with CompressionException. diff --git a/impl/src/main/java/io/jsonwebtoken/impl/io/ConvertingParser.java b/impl/src/main/java/io/jsonwebtoken/impl/io/ConvertingParser.java index 232bc6eb2..d053d7e8a 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/io/ConvertingParser.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/io/ConvertingParser.java @@ -16,11 +16,11 @@ package io.jsonwebtoken.impl.io; import io.jsonwebtoken.impl.lang.Converter; -import io.jsonwebtoken.impl.lang.Function; import io.jsonwebtoken.lang.Assert; import java.io.Reader; import java.util.Map; +import java.util.function.Function; public class ConvertingParser extends AbstractParser { diff --git a/impl/src/main/java/io/jsonwebtoken/impl/io/JsonObjectDeserializer.java b/impl/src/main/java/io/jsonwebtoken/impl/io/JsonObjectDeserializer.java index a79a50aad..b97a5e66c 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/io/JsonObjectDeserializer.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/io/JsonObjectDeserializer.java @@ -16,13 +16,13 @@ package io.jsonwebtoken.impl.io; import io.jsonwebtoken.MalformedJwtException; -import io.jsonwebtoken.impl.lang.Function; import io.jsonwebtoken.io.DeserializationException; import io.jsonwebtoken.io.Deserializer; import io.jsonwebtoken.lang.Assert; import java.io.Reader; import java.util.Map; +import java.util.function.Function; /** * Function that wraps a {@link Deserializer} to add JWT-related error handling. diff --git a/impl/src/main/java/io/jsonwebtoken/impl/lang/CheckedFunction.java b/impl/src/main/java/io/jsonwebtoken/impl/lang/CheckedFunction.java index 0cdfd4e37..d77dc22c0 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/lang/CheckedFunction.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/lang/CheckedFunction.java @@ -15,6 +15,7 @@ */ package io.jsonwebtoken.impl.lang; +@FunctionalInterface public interface CheckedFunction { R apply(T t) throws Exception; } diff --git a/impl/src/main/java/io/jsonwebtoken/impl/lang/CollectionConverter.java b/impl/src/main/java/io/jsonwebtoken/impl/lang/CollectionConverter.java index 41f50b322..2941ecb98 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/lang/CollectionConverter.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/lang/CollectionConverter.java @@ -23,18 +23,19 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Set; +import java.util.function.Function; class CollectionConverter> implements Converter { private final Converter elementConverter; private final Function fn; - public static CollectionConverter> forList(Converter elementConverter) { - return new CollectionConverter<>(elementConverter, new CreateListFunction()); + public static CollectionConverter> forList(Converter elementConverter) { + return new CollectionConverter<>(elementConverter, new CreateListFunction<>()); } public static CollectionConverter> forSet(Converter elementConverter) { - return new CollectionConverter<>(elementConverter, new CreateSetFunction()); + return new CollectionConverter<>(elementConverter, new CreateSetFunction<>()); } public CollectionConverter(Converter elementConverter, Function fn) { @@ -91,14 +92,14 @@ public C applyFrom(Object value) { private static class CreateListFunction implements Function> { @Override public List apply(Integer size) { - return size > 0 ? new ArrayList(size) : new ArrayList(); + return size > 0 ? new ArrayList<>(size) : new ArrayList<>(); } } private static class CreateSetFunction implements Function> { @Override public Set apply(Integer size) { - return size > 0 ? new LinkedHashSet(size) : new LinkedHashSet(); + return size > 0 ? new LinkedHashSet<>(size) : new LinkedHashSet<>(); } } } diff --git a/impl/src/main/java/io/jsonwebtoken/impl/lang/ConstantFunction.java b/impl/src/main/java/io/jsonwebtoken/impl/lang/ConstantFunction.java index 973c60a82..0ec4598c5 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/lang/ConstantFunction.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/lang/ConstantFunction.java @@ -15,6 +15,7 @@ */ package io.jsonwebtoken.impl.lang; +import java.util.function.Function; /** * Function that always returns the same value diff --git a/impl/src/main/java/io/jsonwebtoken/impl/lang/DefaultRegistry.java b/impl/src/main/java/io/jsonwebtoken/impl/lang/DefaultRegistry.java index 0b4887385..c659c1218 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/lang/DefaultRegistry.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/lang/DefaultRegistry.java @@ -23,6 +23,7 @@ import java.util.Collection; import java.util.LinkedHashMap; import java.util.Map; +import java.util.function.Function; public class DefaultRegistry extends DelegatingMap> implements Registry, Function { diff --git a/impl/src/main/java/io/jsonwebtoken/impl/lang/DelegatingCheckedFunction.java b/impl/src/main/java/io/jsonwebtoken/impl/lang/DelegatingCheckedFunction.java index 0c9c843d3..0288ae88e 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/lang/DelegatingCheckedFunction.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/lang/DelegatingCheckedFunction.java @@ -15,6 +15,8 @@ */ package io.jsonwebtoken.impl.lang; +import java.util.function.Function; + public class DelegatingCheckedFunction implements CheckedFunction { final Function delegate; diff --git a/impl/src/main/java/io/jsonwebtoken/impl/lang/FormattedStringFunction.java b/impl/src/main/java/io/jsonwebtoken/impl/lang/FormattedStringFunction.java index 584234539..4ea24531a 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/lang/FormattedStringFunction.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/lang/FormattedStringFunction.java @@ -17,6 +17,8 @@ import io.jsonwebtoken.lang.Assert; +import java.util.function.Function; + public class FormattedStringFunction implements Function { private final String msg; diff --git a/impl/src/main/java/io/jsonwebtoken/impl/lang/FormattedStringSupplier.java b/impl/src/main/java/io/jsonwebtoken/impl/lang/FormattedStringSupplier.java index 967a6cefb..4e166aaaf 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/lang/FormattedStringSupplier.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/lang/FormattedStringSupplier.java @@ -16,7 +16,8 @@ package io.jsonwebtoken.impl.lang; import io.jsonwebtoken.lang.Assert; -import io.jsonwebtoken.lang.Supplier; + +import java.util.function.Supplier; public class FormattedStringSupplier implements Supplier { diff --git a/impl/src/main/java/io/jsonwebtoken/impl/lang/Function.java b/impl/src/main/java/io/jsonwebtoken/impl/lang/Function.java deleted file mode 100644 index 4afd81f28..000000000 --- a/impl/src/main/java/io/jsonwebtoken/impl/lang/Function.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright © 2020 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.impl.lang; - -public interface Function { - - R apply(T t); -} diff --git a/impl/src/main/java/io/jsonwebtoken/impl/lang/Functions.java b/impl/src/main/java/io/jsonwebtoken/impl/lang/Functions.java index 85523a4b8..6c8d1cebd 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/lang/Functions.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/lang/Functions.java @@ -17,18 +17,16 @@ import io.jsonwebtoken.lang.Assert; +import java.util.function.Function; + public final class Functions { private Functions() { } + @Deprecated public static Function identity() { - return new Function() { - @Override - public T apply(T t) { - return t; - } - }; + return t -> t; } /** @@ -56,32 +54,6 @@ public static Function wrap(Function(new DelegatingCheckedFunction<>(fn), exClass, new FormattedStringSupplier(fmt, args)); } - /** - * Returns a composed function that first applies the {@code before} function to its input, and then applies - * the {@code after} function to the result. If evaluation of either function throws an exception, it is relayed to - * the caller of the composed function. - * - * @param type of input to the {@code before} function and the resulting composed function. - * @param the type of output of the {@code before} function, and of the input to the {@code after} function. - * @param return type of the {@code after} function and the resulting composed function. - * @param before the function to invoke first - * @param after the function to invoke second with the output from the first - * @return a composed function that first applies the {@code before} function and then - * applies the {@code after} function. - * @throws IllegalArgumentException if either {@code before} or {@code after} are null. - */ - public static Function andThen(final Function before, final Function after) { - Assert.notNull(before, "Before function cannot be null."); - Assert.notNull(after, "After function cannot be null."); - return new Function() { - @Override - public R apply(T t) { - V result = before.apply(t); - return after.apply(result); - } - }; - } - /** * Returns a composed function that invokes the specified functions in iteration order, and returns the first * non-null result. Once a non-null result is discovered, no further functions will be invoked, 'short-circuiting' @@ -93,23 +65,19 @@ public R apply(T t) { * @param fns the functions to iterate * @return a composed function that invokes the specified functions in iteration order, returning the first non-null * result. - * @throws NullPointerException if after is null */ @SafeVarargs public static Function firstResult(final Function... fns) { Assert.notEmpty(fns, "Function list cannot be null or empty."); - return new Function() { - @Override - public R apply(T t) { - for (Function fn : fns) { - Assert.notNull(fn, "Function cannot be null."); - R result = fn.apply(t); - if (result != null) { - return result; - } + return t -> { + for (Function fn : fns) { + Assert.notNull(fn, "Function cannot be null."); + R result = fn.apply(t); + if (result != null) { + return result; } - return null; } + return null; }; } } diff --git a/impl/src/main/java/io/jsonwebtoken/impl/lang/IdRegistry.java b/impl/src/main/java/io/jsonwebtoken/impl/lang/IdRegistry.java index 91f062fc2..051d0d722 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/lang/IdRegistry.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/lang/IdRegistry.java @@ -20,15 +20,13 @@ import io.jsonwebtoken.lang.Strings; import java.util.Collection; +import java.util.function.Function; public class IdRegistry extends StringRegistry { - public static final Function FN = new Function() { - @Override - public String apply(Identifiable identifiable) { - Assert.notNull(identifiable, "Identifiable argument cannot be null."); - return Assert.notNull(Strings.clean(identifiable.getId()), "Identifier cannot be null or empty."); - } + public static final Function FN = identifiable -> { + Assert.notNull(identifiable, "Identifiable argument cannot be null."); + return Assert.notNull(Strings.clean(identifiable.getId()), "Identifier cannot be null or empty."); }; @SuppressWarnings("unchecked") diff --git a/impl/src/main/java/io/jsonwebtoken/impl/lang/LocatorFunction.java b/impl/src/main/java/io/jsonwebtoken/impl/lang/LocatorFunction.java index 77c5ba873..d643f84b8 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/lang/LocatorFunction.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/lang/LocatorFunction.java @@ -19,6 +19,8 @@ import io.jsonwebtoken.Locator; import io.jsonwebtoken.lang.Assert; +import java.util.function.Function; + public class LocatorFunction implements Function { private final Locator locator; diff --git a/impl/src/main/java/io/jsonwebtoken/impl/lang/PropagatingExceptionFunction.java b/impl/src/main/java/io/jsonwebtoken/impl/lang/PropagatingExceptionFunction.java index e7ed1644b..a82b20b40 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/lang/PropagatingExceptionFunction.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/lang/PropagatingExceptionFunction.java @@ -17,32 +17,27 @@ import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Classes; -import io.jsonwebtoken.lang.Supplier; import java.lang.reflect.Constructor; +import java.util.function.Function; +import java.util.function.Supplier; public class PropagatingExceptionFunction implements Function { private final CheckedFunction function; - private final Function msgFunction; private final Class clazz; public PropagatingExceptionFunction(Function f, Class exceptionClass, String msg) { - this(new DelegatingCheckedFunction<>(f), exceptionClass, new ConstantFunction(msg)); + this(new DelegatingCheckedFunction<>(f), exceptionClass, new ConstantFunction<>(msg)); } public PropagatingExceptionFunction(CheckedFunction f, Class exceptionClass, final String msg) { - this(f, exceptionClass, new ConstantFunction(msg)); + this(f, exceptionClass, new ConstantFunction<>(msg)); } public PropagatingExceptionFunction(CheckedFunction fn, Class exceptionClass, final Supplier msgSupplier) { - this(fn, exceptionClass, new Function() { - @Override - public String apply(T t) { - return msgSupplier.get(); - } - }); + this(fn, exceptionClass, t -> msgSupplier.get()); } public PropagatingExceptionFunction(CheckedFunction f, Class exceptionClass, Function msgFunction) { diff --git a/impl/src/main/java/io/jsonwebtoken/impl/lang/RedactedSupplier.java b/impl/src/main/java/io/jsonwebtoken/impl/lang/RedactedConfidentialValue.java similarity index 80% rename from impl/src/main/java/io/jsonwebtoken/impl/lang/RedactedSupplier.java rename to impl/src/main/java/io/jsonwebtoken/impl/lang/RedactedConfidentialValue.java index 8b7a0d240..64bc2a6e8 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/lang/RedactedSupplier.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/lang/RedactedConfidentialValue.java @@ -17,15 +17,15 @@ import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Objects; -import io.jsonwebtoken.lang.Supplier; +import io.jsonwebtoken.security.ConfidentialValue; -public class RedactedSupplier implements Supplier { +public class RedactedConfidentialValue implements ConfidentialValue { public static final String REDACTED_VALUE = ""; private final T value; - public RedactedSupplier(T value) { + public RedactedConfidentialValue(T value) { this.value = Assert.notNull(value, "value cannot be null."); } @@ -44,8 +44,8 @@ public boolean equals(Object obj) { if (obj == this) { return true; } - if (obj instanceof RedactedSupplier) { - obj = ((RedactedSupplier) obj).value; // get the wrapped value + if (obj instanceof RedactedConfidentialValue) { + obj = ((RedactedConfidentialValue) obj).value; // get the wrapped value } return Objects.nullSafeEquals(this.value, obj); } diff --git a/impl/src/main/java/io/jsonwebtoken/impl/lang/RedactedValueConverter.java b/impl/src/main/java/io/jsonwebtoken/impl/lang/RedactedValueConverter.java index 12b46aabf..e0263d058 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/lang/RedactedValueConverter.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/lang/RedactedValueConverter.java @@ -16,7 +16,6 @@ package io.jsonwebtoken.impl.lang; import io.jsonwebtoken.lang.Assert; -import io.jsonwebtoken.lang.Supplier; public class RedactedValueConverter implements Converter { @@ -29,16 +28,16 @@ public RedactedValueConverter(Converter delegate) { @Override public Object applyTo(T t) { Object value = this.delegate.applyTo(t); - if (value != null && !(value instanceof RedactedSupplier)) { - value = new RedactedSupplier<>(value); + if (value != null && !(value instanceof RedactedConfidentialValue)) { + value = new RedactedConfidentialValue<>(value); } return value; } @Override public T applyFrom(Object o) { - if (o instanceof RedactedSupplier) { - o = ((Supplier) o).get(); + if (o instanceof RedactedConfidentialValue) { + o = ((RedactedConfidentialValue) o).get(); } return this.delegate.applyFrom(o); } diff --git a/impl/src/main/java/io/jsonwebtoken/impl/lang/ReflectionFunction.java b/impl/src/main/java/io/jsonwebtoken/impl/lang/ReflectionFunction.java index bf2225e14..a4df059af 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/lang/ReflectionFunction.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/lang/ReflectionFunction.java @@ -15,6 +15,8 @@ */ package io.jsonwebtoken.impl.lang; +import java.util.function.Function; + abstract class ReflectionFunction implements Function { public static final String ERR_MSG = "Reflection operation failed. This is likely due to an internal " + diff --git a/impl/src/main/java/io/jsonwebtoken/impl/lang/StringRegistry.java b/impl/src/main/java/io/jsonwebtoken/impl/lang/StringRegistry.java index ea226723d..aec6d3a18 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/lang/StringRegistry.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/lang/StringRegistry.java @@ -23,6 +23,7 @@ import java.util.LinkedHashMap; import java.util.Locale; import java.util.Map; +import java.util.function.Function; public class StringRegistry extends DefaultRegistry { @@ -31,7 +32,7 @@ public class StringRegistry extends DefaultRegistry { private final Map CI_VALUES; public StringRegistry(String name, String keyName, Collection values, Function keyFn, boolean caseSensitive) { - this(name, keyName, values, keyFn, caseSensitive ? Functions.identity() : CaseInsensitiveFunction.ENGLISH); + this(name, keyName, values, keyFn, caseSensitive ? Function.identity() : CaseInsensitiveFunction.ENGLISH); } public StringRegistry(String name, String keyName, Collection values, Function keyFn, Function caseFn) { diff --git a/impl/src/main/java/io/jsonwebtoken/impl/security/AbstractJwk.java b/impl/src/main/java/io/jsonwebtoken/impl/security/AbstractJwk.java index 86bb6b3f0..1fc849044 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/security/AbstractJwk.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/security/AbstractJwk.java @@ -25,7 +25,7 @@ import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.lang.Objects; import io.jsonwebtoken.lang.Strings; -import io.jsonwebtoken.lang.Supplier; +import io.jsonwebtoken.security.ConfidentialValue; import io.jsonwebtoken.security.HashAlgorithm; import io.jsonwebtoken.security.Jwk; import io.jsonwebtoken.security.JwkThumbprint; @@ -115,8 +115,8 @@ private int computeHashCode() { private String getRequiredThumbprintValue(Parameter param) { Object value = get(param.getId()); - if (value instanceof Supplier) { - value = ((Supplier) value).get(); + if (value instanceof ConfidentialValue) { + value = ((ConfidentialValue) value).get(); } return Assert.isInstanceOf(String.class, value, "Parameter canonical value is not a String."); } diff --git a/impl/src/main/java/io/jsonwebtoken/impl/security/ConstantKeyLocator.java b/impl/src/main/java/io/jsonwebtoken/impl/security/ConstantKeyLocator.java index f97ccd50e..111ae3e5c 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/security/ConstantKeyLocator.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/security/ConstantKeyLocator.java @@ -19,9 +19,9 @@ import io.jsonwebtoken.JweHeader; import io.jsonwebtoken.JwsHeader; import io.jsonwebtoken.LocatorAdapter; -import io.jsonwebtoken.impl.lang.Function; import java.security.Key; +import java.util.function.Function; public class ConstantKeyLocator extends LocatorAdapter implements Function { diff --git a/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultDynamicJwkBuilder.java b/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultDynamicJwkBuilder.java index 582af174f..a0528e684 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultDynamicJwkBuilder.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultDynamicJwkBuilder.java @@ -212,7 +212,7 @@ public J build() { // @since 0.12.7 per https://github.com/jwtk/jjwt/issues/988 @SuppressWarnings("unused") // used via reflection in the api module's Jwks class. - public static final class Supplier> implements io.jsonwebtoken.lang.Supplier> { + public static final class Supplier> implements java.util.function.Supplier> { @Override public DynamicJwkBuilder get() { return new DefaultDynamicJwkBuilder<>(); diff --git a/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultJwkParserBuilder.java b/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultJwkParserBuilder.java index 0ffb2908b..8a495dc4a 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultJwkParserBuilder.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultJwkParserBuilder.java @@ -32,7 +32,7 @@ public Parser> doBuild() { // @since 0.12.7 per https://github.com/jwtk/jjwt/issues/988 @SuppressWarnings("unused") // used via reflection in the api module's Jwks class. - public static final class Supplier implements io.jsonwebtoken.lang.Supplier { + public static final class Supplier implements java.util.function.Supplier { @Override public JwkParserBuilder get() { return new DefaultJwkParserBuilder(); diff --git a/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultJwkSetBuilder.java b/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultJwkSetBuilder.java index ae97a2ebc..9e42a0e5e 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultJwkSetBuilder.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultJwkSetBuilder.java @@ -132,7 +132,7 @@ public JwkSet build() { // @since 0.12.7 per https://github.com/jwtk/jjwt/issues/988 @SuppressWarnings("unused") // used via reflection in the api module's Jwks class. - public static final class Supplier implements io.jsonwebtoken.lang.Supplier { + public static final class Supplier implements java.util.function.Supplier { @Override public JwkSetBuilder get() { return new DefaultJwkSetBuilder(); diff --git a/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultJwkSetParserBuilder.java b/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultJwkSetParserBuilder.java index 0e0bd48e5..20e89918d 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultJwkSetParserBuilder.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultJwkSetParserBuilder.java @@ -41,7 +41,7 @@ public Parser doBuild() { // @since 0.12.7 per https://github.com/jwtk/jjwt/issues/988 @SuppressWarnings("unused") // used via reflection in the api module's Jwks class. - public static final class Supplier implements io.jsonwebtoken.lang.Supplier { + public static final class Supplier implements java.util.function.Supplier { @Override public JwkSetParserBuilder get() { return new DefaultJwkSetParserBuilder(); diff --git a/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultKeyOperationBuilder.java b/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultKeyOperationBuilder.java index b42b6d422..d5c24fd64 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultKeyOperationBuilder.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultKeyOperationBuilder.java @@ -55,7 +55,7 @@ public KeyOperation build() { // @since 0.12.7 per https://github.com/jwtk/jjwt/issues/988 @SuppressWarnings("unused") // used via reflection in the api module's Jwks class. - public static final class Supplier implements io.jsonwebtoken.lang.Supplier { + public static final class Supplier implements java.util.function.Supplier { @Override public KeyOperationBuilder get() { return new DefaultKeyOperationBuilder(); diff --git a/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultKeyOperationPolicyBuilder.java b/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultKeyOperationPolicyBuilder.java index edb3fdb2c..6a71ac49a 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultKeyOperationPolicyBuilder.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultKeyOperationPolicyBuilder.java @@ -44,7 +44,7 @@ public KeyOperationPolicy build() { // @since 0.12.7 per https://github.com/jwtk/jjwt/issues/988 @SuppressWarnings("unused") // used via reflection in the api module's Jwks class. - public static final class Supplier implements io.jsonwebtoken.lang.Supplier { + public static final class Supplier implements java.util.function.Supplier { @Override public KeyOperationPolicyBuilder get() { return new DefaultKeyOperationPolicyBuilder(); diff --git a/impl/src/main/java/io/jsonwebtoken/impl/security/EdwardsCurve.java b/impl/src/main/java/io/jsonwebtoken/impl/security/EdwardsCurve.java index 410dfb631..a92c9dd71 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/security/EdwardsCurve.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/security/EdwardsCurve.java @@ -16,7 +16,6 @@ package io.jsonwebtoken.impl.security; import io.jsonwebtoken.impl.lang.Bytes; -import io.jsonwebtoken.impl.lang.Function; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.lang.Strings; @@ -36,6 +35,7 @@ import java.util.Collection; import java.util.LinkedHashMap; import java.util.Map; +import java.util.function.Function; public class EdwardsCurve extends AbstractCurve implements KeyLengthSupplier { diff --git a/impl/src/main/java/io/jsonwebtoken/impl/security/EdwardsPublicKeyDeriver.java b/impl/src/main/java/io/jsonwebtoken/impl/security/EdwardsPublicKeyDeriver.java index 399659251..fa335134b 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/security/EdwardsPublicKeyDeriver.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/security/EdwardsPublicKeyDeriver.java @@ -15,7 +15,6 @@ */ package io.jsonwebtoken.impl.security; -import io.jsonwebtoken.impl.lang.Function; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.security.InvalidKeyException; @@ -23,6 +22,7 @@ import java.security.PrivateKey; import java.security.PublicKey; import java.security.SecureRandom; +import java.util.function.Function; /** * Derives a PublicKey from an Edwards-curve PrivateKey instance. diff --git a/impl/src/main/java/io/jsonwebtoken/impl/security/JcaTemplate.java b/impl/src/main/java/io/jsonwebtoken/impl/security/JcaTemplate.java index 5c19556c3..210e64ee5 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/security/JcaTemplate.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/security/JcaTemplate.java @@ -21,7 +21,6 @@ import io.jsonwebtoken.impl.lang.CheckedFunction; import io.jsonwebtoken.impl.lang.CheckedSupplier; import io.jsonwebtoken.impl.lang.DefaultRegistry; -import io.jsonwebtoken.impl.lang.Function; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.lang.Objects; @@ -79,13 +78,7 @@ public class JcaTemplate { ); private static final Registry, InstanceFactory> REGISTRY = new DefaultRegistry<>( - "JCA Instance Factory", "instance class", FACTORIES, - new Function, Class>() { - @Override - public Class apply(InstanceFactory factory) { - return factory.getInstanceClass(); - } - }); + "JCA Instance Factory", "instance class", FACTORIES, InstanceFactory::getInstanceClass); // visible for testing protected Provider findBouncyCastle() { diff --git a/impl/src/main/java/io/jsonwebtoken/impl/security/JwkBuilderSupplier.java b/impl/src/main/java/io/jsonwebtoken/impl/security/JwkBuilderSupplier.java index a64683ce5..33b85ab38 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/security/JwkBuilderSupplier.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/security/JwkBuilderSupplier.java @@ -15,12 +15,12 @@ */ package io.jsonwebtoken.impl.security; -import io.jsonwebtoken.lang.Supplier; import io.jsonwebtoken.security.DynamicJwkBuilder; import io.jsonwebtoken.security.Jwks; import io.jsonwebtoken.security.KeyOperationPolicy; import java.security.Provider; +import java.util.function.Supplier; public class JwkBuilderSupplier implements Supplier> { diff --git a/impl/src/main/java/io/jsonwebtoken/impl/security/JwkConverter.java b/impl/src/main/java/io/jsonwebtoken/impl/security/JwkConverter.java index c07a698a3..d308ce490 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/security/JwkConverter.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/security/JwkConverter.java @@ -21,7 +21,6 @@ import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.lang.Strings; -import io.jsonwebtoken.lang.Supplier; import io.jsonwebtoken.security.DynamicJwkBuilder; import io.jsonwebtoken.security.EcPrivateJwk; import io.jsonwebtoken.security.EcPublicJwk; @@ -36,6 +35,7 @@ import io.jsonwebtoken.security.SecretJwk; import java.util.Map; +import java.util.function.Supplier; import static io.jsonwebtoken.lang.Strings.nespace; diff --git a/impl/src/main/java/io/jsonwebtoken/impl/security/JwkSetConverter.java b/impl/src/main/java/io/jsonwebtoken/impl/security/JwkSetConverter.java index c13e9933d..b7528592b 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/security/JwkSetConverter.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/security/JwkSetConverter.java @@ -19,7 +19,6 @@ import io.jsonwebtoken.impl.lang.Parameter; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Collections; -import io.jsonwebtoken.lang.Supplier; import io.jsonwebtoken.security.DynamicJwkBuilder; import io.jsonwebtoken.security.Jwk; import io.jsonwebtoken.security.JwkSet; @@ -32,6 +31,7 @@ import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; +import java.util.function.Supplier; public class JwkSetConverter implements Converter { diff --git a/impl/src/main/java/io/jsonwebtoken/impl/security/NamedParameterSpecValueFinder.java b/impl/src/main/java/io/jsonwebtoken/impl/security/NamedParameterSpecValueFinder.java index 22ba96273..e170b2741 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/security/NamedParameterSpecValueFinder.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/security/NamedParameterSpecValueFinder.java @@ -15,12 +15,12 @@ */ package io.jsonwebtoken.impl.security; -import io.jsonwebtoken.impl.lang.Function; import io.jsonwebtoken.impl.lang.Functions; import io.jsonwebtoken.impl.lang.OptionalMethodInvoker; import java.security.Key; import java.security.spec.AlgorithmParameterSpec; +import java.util.function.Function; public class NamedParameterSpecValueFinder implements Function { @@ -31,7 +31,8 @@ public class NamedParameterSpecValueFinder implements Function { private static final Function GET_NAME = new OptionalMethodInvoker<>("java.security.spec.NamedParameterSpec", "getName"); // >= JDK 11 - private static final Function COMPOSED = Functions.andThen(Functions.firstResult(EDEC_KEY_GET_PARAMS, XEC_KEY_GET_PARAMS), GET_NAME); + private static final Function COMPOSED = + Functions.firstResult(EDEC_KEY_GET_PARAMS, XEC_KEY_GET_PARAMS).andThen(GET_NAME); @Override public String apply(final Key key) { diff --git a/impl/src/main/java/io/jsonwebtoken/impl/security/X509BuilderSupport.java b/impl/src/main/java/io/jsonwebtoken/impl/security/X509BuilderSupport.java index f5984ac9a..4ab1a821c 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/security/X509BuilderSupport.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/security/X509BuilderSupport.java @@ -17,8 +17,6 @@ import io.jsonwebtoken.impl.ParameterMap; import io.jsonwebtoken.impl.io.Streams; -import io.jsonwebtoken.impl.lang.CheckedFunction; -import io.jsonwebtoken.impl.lang.Function; import io.jsonwebtoken.impl.lang.Functions; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Collections; @@ -30,8 +28,10 @@ import java.io.InputStream; import java.net.URI; +import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.util.List; +import java.util.function.Function; //Consolidates logic between DefaultJwtHeaderBuilder and AbstractAsymmetricJwkBuilder public class X509BuilderSupport implements X509Builder { @@ -46,12 +46,7 @@ public class X509BuilderSupport implements X509Builder { protected Boolean computeX509Sha256Thumbprint = null; private static Function createGetBytesFunction(Class clazz) { - return Functions.wrapFmt(new CheckedFunction() { - @Override - public byte[] apply(X509Certificate cert) throws Exception { - return cert.getEncoded(); - } - }, clazz, "Unable to access X509Certificate encoded bytes necessary to compute thumbprint. Certificate: %s"); + return Functions.wrapFmt(Certificate::getEncoded, clazz, "Unable to access X509Certificate encoded bytes necessary to compute thumbprint. Certificate: %s"); } private final Function GET_X509_BYTES; diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/lang/DefaultRegistryTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/lang/DefaultRegistryTest.groovy index 8e0f3b739..93553b73a 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/lang/DefaultRegistryTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/lang/DefaultRegistryTest.groovy @@ -18,6 +18,8 @@ package io.jsonwebtoken.impl.lang import org.junit.Before import org.junit.Test +import java.util.function.Function + import static org.junit.Assert.* class DefaultRegistryTest { @@ -26,7 +28,7 @@ class DefaultRegistryTest { @Before void setUp() { - reg = new DefaultRegistry<>('test', 'id', ['a', 'b', 'c', 'd'], Functions.identity()) + reg = new DefaultRegistry<>('test', 'id', ['a', 'b', 'c', 'd'], Function.identity()) } static void immutable(Closure c) { @@ -65,7 +67,7 @@ class DefaultRegistryTest { @Test void testEqualsValues() { - def another = new DefaultRegistry<>('test', 'id', ['a', 'b', 'c', 'd'], Functions.identity()) + def another = new DefaultRegistry<>('test', 'id', ['a', 'b', 'c', 'd'], Function.identity()) assertEquals another, reg } diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/lang/FunctionsTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/lang/FunctionsTest.groovy index 09c8b6576..fd48de972 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/lang/FunctionsTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/lang/FunctionsTest.groovy @@ -18,6 +18,8 @@ package io.jsonwebtoken.impl.lang import io.jsonwebtoken.MalformedJwtException import org.junit.Test +import java.util.function.Function + import static org.junit.Assert.* class FunctionsTest { diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/lang/PropagatingExceptionFunctionTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/lang/PropagatingExceptionFunctionTest.groovy index b4d54cf2e..cf1895c13 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/lang/PropagatingExceptionFunctionTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/lang/PropagatingExceptionFunctionTest.groovy @@ -18,6 +18,8 @@ package io.jsonwebtoken.impl.lang import io.jsonwebtoken.security.SecurityException import org.junit.Test +import java.util.function.Function + import static org.junit.Assert.assertEquals import static org.junit.Assert.assertSame diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/lang/RedactedSupplierTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/lang/RedactedConfidentialValueTest.groovy similarity index 68% rename from impl/src/test/groovy/io/jsonwebtoken/impl/lang/RedactedSupplierTest.groovy rename to impl/src/test/groovy/io/jsonwebtoken/impl/lang/RedactedConfidentialValueTest.groovy index 7928fada2..06f512cbf 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/lang/RedactedSupplierTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/lang/RedactedConfidentialValueTest.groovy @@ -19,39 +19,39 @@ import org.junit.Test import static org.junit.Assert.* -class RedactedSupplierTest { +class RedactedConfidentialValueTest { @Test void testEqualsWrappedSameValue() { def value = 42 - assertTrue new RedactedSupplier<>(value).equals(value) + assertTrue new RedactedConfidentialValue<>(value).equals(value) } @Test void testEqualsWrappedDifferentValue() { - assertFalse new RedactedSupplier<>(42).equals(30) + assertFalse new RedactedConfidentialValue<>(42).equals(30) } @Test void testEquals() { - assertTrue new RedactedSupplier<>(42).equals(new RedactedSupplier(42)) + assertTrue new RedactedConfidentialValue<>(42).equals(new RedactedConfidentialValue(42)) } @Test void testEqualsSameTypeDifferentValue() { - assertFalse new RedactedSupplier<>(42).equals(new RedactedSupplier(30)) + assertFalse new RedactedConfidentialValue<>(42).equals(new RedactedConfidentialValue(30)) } @Test void testEqualsIdentity() { - def supplier = new RedactedSupplier('hello') + def supplier = new RedactedConfidentialValue('hello') assertEquals supplier, supplier } @Test void testHashCode() { int hashCode = 42.hashCode() - assertEquals hashCode, new RedactedSupplier(42).hashCode() + assertEquals hashCode, new RedactedConfidentialValue(42).hashCode() } } diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/lang/RedactedValueConverterTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/lang/RedactedValueConverterTest.groovy index 65c776b68..f87a1806b 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/lang/RedactedValueConverterTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/lang/RedactedValueConverterTest.groovy @@ -37,7 +37,7 @@ class RedactedValueConverterTest { @Test void testDelegateReturnsRedactedSupplierValue() { def suri = 'https://jsonwebtoken.io' - def supplier = new RedactedSupplier(suri) + def supplier = new RedactedConfidentialValue(suri) def delegate = new Converter() { @Override Object applyTo(Object o) { @@ -51,7 +51,7 @@ class RedactedValueConverterTest { } def c = new RedactedValueConverter(delegate) - // ensure applyTo doesn't change or wrap the delegate return value that is already of type RedactedSupplier: + // ensure applyTo doesn't change or wrap the delegate return value that is already of type RedactedConfidentialValue: assertSame supplier, c.applyTo(suri) } } diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/security/DefaultJwkSetTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/security/DefaultJwkSetTest.groovy index a39fa37ef..359cab5a9 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/security/DefaultJwkSetTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/security/DefaultJwkSetTest.groovy @@ -15,7 +15,7 @@ */ package io.jsonwebtoken.impl.security -import io.jsonwebtoken.impl.lang.RedactedSupplier +import io.jsonwebtoken.impl.lang.RedactedConfidentialValue import io.jsonwebtoken.security.Jwks import org.junit.Test @@ -67,17 +67,17 @@ class DefaultJwkSetTest { } /** - * Asserts that the raw 'keys' value is not a RedactedSupplier per https://github.com/jwtk/jjwt/issues/976, - * but an internal secret key parameter does have a RedactedSupplier + * Asserts that the raw 'keys' value is not a RedactedConfidentialValue per https://github.com/jwtk/jjwt/issues/976, + * but an internal secret key parameter does have a RedactedConfidentialValue */ @Test void testGetKeysNotRedactedSupplier() { def jwk = Jwks.builder().key(TestKeys.HS256).build() def set = new DefaultJwkSet(DefaultJwkSet.KEYS, [keys: [jwk]]) def keys = set.get('keys') - assertFalse keys instanceof RedactedSupplier + assertFalse keys instanceof RedactedConfidentialValue keys = keys as List def element = keys[0] as Map// result is an array/list, so get first JWK in the list - assertTrue element.k instanceof RedactedSupplier // 'k' is a secret property, should be redacted + assertTrue element.k instanceof RedactedConfidentialValue // 'k' is a secret property, should be redacted } } diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/security/JwkSerializationTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/security/JwkSerializationTest.groovy index 9f95fbc21..f8857b85a 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/security/JwkSerializationTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/security/JwkSerializationTest.groovy @@ -22,9 +22,9 @@ import io.jsonwebtoken.io.Serializer import io.jsonwebtoken.jackson.io.JacksonDeserializer import io.jsonwebtoken.jackson.io.JacksonSerializer import io.jsonwebtoken.lang.Strings -import io.jsonwebtoken.lang.Supplier import io.jsonwebtoken.orgjson.io.OrgJsonDeserializer import io.jsonwebtoken.orgjson.io.OrgJsonSerializer +import io.jsonwebtoken.security.ConfidentialValue import io.jsonwebtoken.security.Jwk import io.jsonwebtoken.security.Jwks import org.junit.Test @@ -36,7 +36,7 @@ import static org.junit.Assert.assertTrue /** * Asserts that serializing and deserializing private or secret key values works as expected without - * exposing raw strings in the JWKs themselves (should be wrapped with RedactedSupplier instances) for toString safety. + * exposing raw strings in the JWKs themselves (should be wrapped with ConfidentialValue instances) for toString safety. */ class JwkSerializationTest { @@ -115,7 +115,7 @@ class JwkSerializationTest { //now ensure it deserializes back to a JWK: def map = deserialize(des, json) def jwk2 = Jwks.builder().add(map).build() - assertTrue jwk.k instanceof Supplier + assertTrue jwk.k instanceof ConfidentialValue assertEquals jwk, jwk2 assertEquals jwk.k, jwk2.k assertEquals jwk.k.get(), jwk2.k.get() @@ -145,7 +145,7 @@ class JwkSerializationTest { //now ensure it deserializes back to a JWK: def map = deserialize(des, json) def jwk2 = Jwks.builder().add(map).build() - assertTrue jwk.d instanceof Supplier + assertTrue jwk.d instanceof ConfidentialValue assertEquals jwk, jwk2 assertEquals jwk.d, jwk2.d assertEquals jwk.d.get(), jwk2.d.get() @@ -154,8 +154,8 @@ class JwkSerializationTest { private static assertWrapped(Map map, List keys) { for (String key : keys) { def value = map.get(key) - assertTrue value instanceof Supplier - value = ((Supplier) value).get() + assertTrue value instanceof ConfidentialValue + value = ((ConfidentialValue) value).get() assertTrue value instanceof String } } @@ -163,8 +163,8 @@ class JwkSerializationTest { private static assertEquals(Jwk jwk1, Jwk jwk2, List keys) { assertEquals jwk1, jwk2 for (String key : keys) { - assertTrue jwk1.get(key) instanceof Supplier - assertTrue jwk2.get(key) instanceof Supplier + assertTrue jwk1.get(key) instanceof ConfidentialValue + assertTrue jwk2.get(key) instanceof ConfidentialValue assertEquals jwk1.get(key), jwk2.get(key) assertEquals jwk1.get(key).get(), jwk2.get(key).get() } diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/security/OctetJwksTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/security/OctetJwksTest.groovy index 5620280fb..c0fa043cd 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/security/OctetJwksTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/security/OctetJwksTest.groovy @@ -106,7 +106,7 @@ class OctetJwksTest { assertEquals kty, jwk.getType() assertEquals crv, jwk.get('crv') assertEquals x, jwk.get('x') - assertEquals d, jwk.get('d').get() // Supplier + assertEquals d, jwk.get('d').get() // ConfidentialValue def pubJwk = jwk.toPublicJwk() assertEquals use, pubJwk.getPublicKeyUse() assertEquals kty, pubJwk.getType() diff --git a/impl/src/test/groovy/io/jsonwebtoken/security/KeysTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/security/KeysTest.groovy index 0f08ef628..2590eb8d9 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/security/KeysTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/security/KeysTest.groovy @@ -295,7 +295,15 @@ class KeysTest { assertTrue result instanceof PrivateECKey def key = result as PrivateECKey assertSame priv, key.getKey() - assertSame pub.getParams(), key.getParams() + // NOTE: + // There is a bug with Groovy 4 on >= JDK 22 that cannot resolve + // the ambiguity when calling key.getParams() between + // java.security.AsymmetricKey (introduced in JDK 22) and + // java.security.interfaces.ECKey, and returns AsymmetricKey's default + // interface implementation value of `null`. So we just assert the internal + // object state by looking at the field directly: + //assertSame pub.getParams(), key.getParams() //ambiguous w/ Groovy 4 on >= JDK 22 + assertSame pub.getParams(), key.@params } @Test diff --git a/pom.xml b/pom.xml index e53a888e6..9d330116b 100644 --- a/pom.xml +++ b/pom.xml @@ -14,7 +14,8 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - + 4.0.0 io.jsonwebtoken @@ -95,16 +96,16 @@ ${basedir} 0.11.2 - 3.3.0 - 3.11.0 - 3.6.3 - 3.2.1 - 3.1.0 - 3.1.0 - 0.15.6 + 3.4.2 + 3.14.0 + 3.11.3 + 3.3.1 + 3.3.1 + 3.2.8 + 0.23.1 3.2.0 - 4.2.0 - 4.2.rc3 + 4.2.1 + 4.6 true @@ -135,12 +136,12 @@ 4.0.31 - 4.2 - 4.12 - 2.0.7 + 5.6.0 + 4.13.2 + 2.0.9 3.1.2 3.1.2 - 4.3.1 + 4.5.2 ${jjwt.root}/target/clover/clover.db @@ -150,6 +151,7 @@ --add-opens java.base/java.lang=ALL-UNNAMED, --add-opens java.desktop/java.beans=ALL-UNNAMED, --add-opens java.base/java.lang.ref=ALL-UNNAMED, + --add-opens java.base/java.security=ALL-UNNAMED, --add-opens java.base/sun.security.util=ALL-UNNAMED @@ -259,7 +261,7 @@ junit junit - 4.13.1 + ${junit.version} test @@ -270,12 +272,12 @@ org.apache.maven.plugins maven-release-plugin - 2.5.3 + 3.1.1 org.apache.maven.scm maven-scm-provider-gitexe - 1.9.5 + 2.1.0 @@ -377,7 +379,7 @@ - ${jdk.version} + ${jdk.version} true false -html5 ${maven.javadoc.additionalOptions} @@ -477,7 +479,7 @@ org.apache.maven.plugins maven-shade-plugin - 3.2.1 + 3.6.0 deprecated true @@ -506,7 +508,7 @@ org.apache.maven.plugins maven-enforcer-plugin - 3.4.1 + 3.6.1 enforce-banned-dependencies @@ -585,7 +587,9 @@ ${surefire.plugin.version} ${surefire.argLine} - once + + 1 + true ${skip.default.tests} @@ -625,7 +629,7 @@ org.sonatype.plugins nexus-staging-maven-plugin - 1.6.8 + 1.6.14 true ossrh @@ -636,7 +640,7 @@ org.apache.felix maven-bundle-plugin - 3.5.0 + 5.1.9 bundle-manifest @@ -741,7 +745,9 @@ - once + + 1 + true From 197ce20eb3113a5f4061448dc7797225211190c6 Mon Sep 17 00:00:00 2001 From: Les Hazlewood <121180+lhazlewood@users.noreply.github.com> Date: Wed, 3 Jun 2026 21:19:20 -0400 Subject: [PATCH 05/11] - Reverting TestProvider.java back to TestProvider.groovy - Specified surefire.argLine property for surefire runs, with an empty value for JDK 8 (since not supported on 8) --- .../impl/security/TestProvider.groovy} | 20 ++++-------- pom.xml | 32 +++++++------------ 2 files changed, 18 insertions(+), 34 deletions(-) rename impl/src/test/{java/io/jsonwebtoken/impl/security/TestProvider.java => groovy/io/jsonwebtoken/impl/security/TestProvider.groovy} (50%) diff --git a/impl/src/test/java/io/jsonwebtoken/impl/security/TestProvider.java b/impl/src/test/groovy/io/jsonwebtoken/impl/security/TestProvider.groovy similarity index 50% rename from impl/src/test/java/io/jsonwebtoken/impl/security/TestProvider.java rename to impl/src/test/groovy/io/jsonwebtoken/impl/security/TestProvider.groovy index ce8d18aca..197a253ec 100644 --- a/impl/src/test/java/io/jsonwebtoken/impl/security/TestProvider.java +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/security/TestProvider.groovy @@ -13,23 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.jsonwebtoken.impl.security; +package io.jsonwebtoken.impl.security -import java.security.Provider; +import java.security.Provider -/** - * Test-only {@link Provider} subclass. Defined in Java (not Groovy) because Groovy 4 cannot resolve - * the {@code protected} {@code Provider} constructor via its meta-class on JDK 17+. - */ -public class TestProvider extends Provider { +class TestProvider extends Provider { - public TestProvider() { - this("test"); + TestProvider() { + this('test') } - public TestProvider(String name) { - //noinspection deprecation - double constructor used for Java 8 source compatibility; - // the (String, String, String) replacement was added in Java 9 - super(name, 1.0d, "info"); + TestProvider(String name) { + super(name, 1.0d, 'info') } } diff --git a/pom.xml b/pom.xml index 9d330116b..7efcdbf01 100644 --- a/pom.xml +++ b/pom.xml @@ -146,7 +146,6 @@ false - ${test.addOpens} --add-opens java.base/java.lang=ALL-UNNAMED, --add-opens java.desktop/java.beans=ALL-UNNAMED, @@ -156,7 +155,7 @@ KeysTest.testKeyPairBuilder: --> --add-opens java.base/sun.security.util=ALL-UNNAMED - + ${test.addOpens} @@ -472,7 +471,7 @@ - @@ -664,18 +663,6 @@ io.jsonwebtoken.coveralls coveralls-maven-plugin 4.4.1 - - - - ${jjwt.root}/api/src/test/java - ${jjwt.root}/impl/src/test/java - ${jjwt.root}/extensions/jackson/src/test/java - ${jjwt.root}/extensions/gson/src/test/java - ${jjwt.root}/extensions/orgjson/src/test/java - - + @@ -743,8 +734,7 @@ [1.8,1.9),[8,9) - - + ${surefire.argLine} 1 true @@ -780,7 +770,7 @@ 11 - ${test.addOpens} + ${surefire.argLine} @@ -813,7 +803,7 @@ test-jdk-17 test - ${test.addOpens} + ${surefire.argLine} @@ -845,7 +835,7 @@ 21 - ${test.addOpens} + ${surefire.argLine} @@ -877,7 +867,7 @@ 25 - ${test.addOpens} + ${surefire.argLine} From 4e147aaab8f5cf1aebafd35c427f40ebff7235e5 Mon Sep 17 00:00:00 2001 From: Les Hazlewood <121180+lhazlewood@users.noreply.github.com> Date: Wed, 3 Jun 2026 21:31:19 -0400 Subject: [PATCH 06/11] - Testing empty argLine for jdk8 --- pom.xml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 39c5d2acd..440816039 100644 --- a/pom.xml +++ b/pom.xml @@ -715,10 +715,6 @@ 8 - - surefire.argLine - - @@ -734,7 +730,7 @@ [1.8,1.9),[8,9) - ${surefire.argLine} + 1 true From 67ae4fc059f24d671e10faa3a78d47257e1db441 Mon Sep 17 00:00:00 2001 From: Les Hazlewood <121180+lhazlewood@users.noreply.github.com> Date: Wed, 3 Jun 2026 21:51:27 -0400 Subject: [PATCH 07/11] - Build matrix testing --- .github/workflows/ci.yml | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2bc7dc852..0c8422c7d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,27 +49,29 @@ jobs: # to sign artifacts, since we don't want to mess with storing signing credentials in CI: run: ${{env.MVN_CMD}} verify -Possrh -Dgpg.skip=true -P jdk-17,jdk-21,jdk-25 - temurin: + non-oracle: runs-on: 'ubuntu-latest' - name: temurin + strategy: + matrix: + distribution: [ 'temurin', 'zulu' ] + java: [ 8, 11, 21, 25 ] # don't specify 17, since it's explicitly defined below + name: jdk-${{ matrix.java }}-${{ matrix.distribution }} steps: - uses: actions/checkout@v4 - # Install test-only JDKs first; each call appends an entry to ~/.m2/toolchains.xml - - name: Set up JDK + # Install test-only JDK first; will append an entry to ~/.m2/toolchains.xml + - name: Set up ${{ matrix.distribution }} JDK ${{ matrix.java }} uses: actions/setup-java@v4 with: - distribution: 'temurin' - java-version: | - 8 - 11 - 21 - 25 - # Build JDK last so it ends up as the active JAVA_HOME / PATH entry + distribution: ${{ matrix.distribution }} + java-version: ${{ matrix.java }} + cache: 'maven' + check-latest: true + # Install full build JDK last so it ends up as the active JAVA_HOME / PATH entry - name: Set up JDK 17 (build) uses: actions/setup-java@v4 with: java-version: '17' - distribution: 'temurin' + distribution: 'zulu' cache: 'maven' check-latest: true - name: Set up OSS Community Develocity Instance for Maven @@ -87,7 +89,7 @@ jobs: - name: Build # run a full build, just as we would for a release (i.e. the `ossrh` profile), but don't use gpg # to sign artifacts, since we don't want to mess with storing signing credentials in CI: - run: ${{env.MVN_CMD}} verify -Possrh -Dgpg.skip=true -P jdk-8,jdk-11,jdk-17,jdk-21,jdk-25 + run: ${{env.MVN_CMD}} verify -Possrh -Dgpg.skip=true -P jdk-17,jdk-${{ matrix.java }} # ensure all of our files have the correct/updated license header license-check: @@ -108,12 +110,10 @@ jobs: with: develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }} - name: License Check - # This adds about 1 minute to any build, which is why we don't want to do it on every other build: - run: | - ${{env.MVN_CMD}} license:check + # This adds about 1 minute to any build, which is why we don't want to do it on every build: + run: ${{env.MVN_CMD}} license:check code-coverage: - needs: [oracle, temurin] runs-on: 'ubuntu-latest' steps: - uses: actions/checkout@v4 From e7034882d9a2a93675f466c878fbd64bd8d1c08f Mon Sep 17 00:00:00 2001 From: Brian Demers Date: Thu, 4 Jun 2026 06:34:00 -0400 Subject: [PATCH 08/11] Update CI build matrix Java versions (#1053) - Runs each job JDK version in parallel - uses `test.jdk.version` to remove _default_ execution of test (only running the matrix target tests) --- .github/workflows/ci.yml | 22 ++++++++++++---------- pom.xml | 23 ++++++++++++++++------- 2 files changed, 28 insertions(+), 17 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0c8422c7d..59887c7ad 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,6 +15,9 @@ jobs: oracle: runs-on: 'ubuntu-latest' name: jdk-${{ matrix.java }}-oracle + strategy: + matrix: + java: [ 17, 21, 25 ] # don't specify 17, since it's explicitly defined below steps: - uses: actions/checkout@v4 # Install test-only JDKs first; each call appends an entry to ~/.m2/toolchains.xml @@ -22,9 +25,7 @@ jobs: uses: actions/setup-java@v4 with: distribution: oracle - java-version: | - 21 - 25 + java-version: ${{ matrix.java }} # Build JDK last so it ends up as the active JAVA_HOME / PATH entry - name: Set up JDK 17 (build) uses: actions/setup-java@v4 @@ -47,14 +48,15 @@ jobs: - name: Build # run a full build, just as we would for a release (i.e. the `ossrh` profile), but don't use gpg # to sign artifacts, since we don't want to mess with storing signing credentials in CI: - run: ${{env.MVN_CMD}} verify -Possrh -Dgpg.skip=true -P jdk-17,jdk-21,jdk-25 + run: ${{env.MVN_CMD}} verify -Possrh -Dgpg.skip=true -Dtest.jdk.version=${{ matrix.java }} non-oracle: runs-on: 'ubuntu-latest' strategy: matrix: - distribution: [ 'temurin', 'zulu' ] - java: [ 8, 11, 21, 25 ] # don't specify 17, since it's explicitly defined below + distribution: [ 'temurin', 'zulu', 'corretto' ] + java: [ 8, 11, 17, 21, 25 ] + fail-fast: false name: jdk-${{ matrix.java }}-${{ matrix.distribution }} steps: - uses: actions/checkout@v4 @@ -71,7 +73,7 @@ jobs: uses: actions/setup-java@v4 with: java-version: '17' - distribution: 'zulu' + distribution: ${{ matrix.distribution }} cache: 'maven' check-latest: true - name: Set up OSS Community Develocity Instance for Maven @@ -89,7 +91,7 @@ jobs: - name: Build # run a full build, just as we would for a release (i.e. the `ossrh` profile), but don't use gpg # to sign artifacts, since we don't want to mess with storing signing credentials in CI: - run: ${{env.MVN_CMD}} verify -Possrh -Dgpg.skip=true -P jdk-17,jdk-${{ matrix.java }} + run: ${{env.MVN_CMD}} verify -Possrh -Dgpg.skip=true -Dtest.jdk.version=${{ matrix.java }} # ensure all of our files have the correct/updated license header license-check: @@ -101,7 +103,7 @@ jobs: - name: Set up JDK uses: actions/setup-java@v4 with: - distribution: 'temurin' + distribution: 'zulu' java-version: '17' cache: 'maven' check-latest: true @@ -120,7 +122,7 @@ jobs: - name: Set up JDK uses: actions/setup-java@v4 with: - distribution: 'temurin' + distribution: 'zulu' java-version: '17' cache: 'maven' check-latest: true diff --git a/pom.xml b/pom.xml index 440816039..f4383af48 100644 --- a/pom.xml +++ b/pom.xml @@ -119,9 +119,6 @@ test executions (jdk-8 and jdk-11 profiles). --> false false - - false ${user.name}-${maven.build.timestamp} 2.19.2 @@ -589,7 +586,6 @@ 1 true - ${skip.default.tests} @@ -701,9 +697,22 @@ test.jdk.version - - true - + + + + org.apache.maven.plugins + maven-surefire-plugin + + + default-test + + true + + + + + + From a5efc7063d5d59978889fbb6fdf61f8f3cbe5a68 Mon Sep 17 00:00:00 2001 From: Brian Demers Date: Thu, 4 Jun 2026 11:01:36 -0400 Subject: [PATCH 09/11] Extract repetitive CI setup steps into a composite action (#1054) * Extract repetitive CI setup steps into a composite action Consolidate checkout, setup-java, setup-maven, and SoftHSM steps into a reusable local composite action under `.github/actions/`. This will help simplify maintenance and future action version updates. * Pin GitHub Actions to commit SHAs Use immutable commit SHAs for checkout, setup-java, and setup-maven actions: https://docs.github.com/en/actions/reference/security/secure-use#using-third-party-actions --- .github/actions/setup-build/action.yml | 68 +++++++++++++++ .github/workflows/ci.yml | 114 ++++++------------------- 2 files changed, 96 insertions(+), 86 deletions(-) create mode 100644 .github/actions/setup-build/action.yml diff --git a/.github/actions/setup-build/action.yml b/.github/actions/setup-build/action.yml new file mode 100644 index 000000000..8ab3f8256 --- /dev/null +++ b/.github/actions/setup-build/action.yml @@ -0,0 +1,68 @@ +name: 'Setup Build Environment' +description: 'Sets up JDK(s), Maven Develocity, and optionally SoftHSM' +inputs: + java-version-tests: + description: 'The primary/test Java version' + required: false + default: '17' + distribution: + description: 'JDK distribution to use' + required: false + default: 'zulu' + java-version-maven: + description: 'The Java version used to execute Maven' + required: false + default: '17' + develocity-access-key: + description: 'Develocity access key' + required: false + setup-softhsm: + description: 'Whether to set up SoftHSM' + required: false + default: 'true' + +runs: + using: 'composite' + steps: + + - name: Set up JDK ${{ inputs.java-version-tests }} (Runs Tests) + if: ${{ inputs.java-version != inputs.java-version-maven }} + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 + with: + distribution: ${{ inputs.distribution }} + java-version: ${{ inputs.java-version-tests }} + cache: 'maven' + check-latest: true + + - name: Set up JDK ${{ inputs.java-version-maven }} (Runs Maven) + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 + with: + distribution: ${{ inputs.distribution }} + java-version: ${{ inputs.java-version-maven }} + cache: 'maven' + check-latest: true + + - name: Set up OSS Community Develocity Instance for Maven + uses: gradle/develocity-actions/setup-maven@974e8dbcbda40db6828fc35f349c80a7c0e71529 #v2.1 + with: + develocity-access-key: ${{ inputs.develocity-access-key }} + + - name: Install softhsm2 + if: ${{ inputs.setup-softhsm == 'true' }} + run: sudo apt-get install -y softhsm2 + shell: bash + + - name: Install opensc + if: ${{ inputs.setup-softhsm == 'true' }} + run: sudo apt-get install -y opensc + shell: bash + + - name: Ensure SoftHSM user configuration + if: ${{ inputs.setup-softhsm == 'true' }} + run: impl/src/test/scripts/softhsm configure + shell: bash + + - name: Populate SoftHSM with JJWT test keys + if: ${{ inputs.setup-softhsm == 'true' }} + run: impl/src/test/scripts/softhsm import + shell: bash diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 59887c7ad..46e7ed4bc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,7 +1,7 @@ name: CI on: - workflow_dispatch: + workflow_dispatch: pull_request: # all pull requests push: branches: @@ -14,103 +14,59 @@ jobs: # Oracle only provides JDK 17+, so test against 17, 21, and 25 oracle: runs-on: 'ubuntu-latest' - name: jdk-${{ matrix.java }}-oracle + name: jdk-${{ matrix.java-version }}-oracle strategy: matrix: - java: [ 17, 21, 25 ] # don't specify 17, since it's explicitly defined below + java-version: [ 17, 21, 25 ] steps: - - uses: actions/checkout@v4 - # Install test-only JDKs first; each call appends an entry to ~/.m2/toolchains.xml - - name: Set up JDK - uses: actions/setup-java@v4 + - name: Checkout + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + - name: Setup Build Environment + uses: ./.github/actions/setup-build with: + java-version-tests: ${{ matrix.java-version }} distribution: oracle - java-version: ${{ matrix.java }} - # Build JDK last so it ends up as the active JAVA_HOME / PATH entry - - name: Set up JDK 17 (build) - uses: actions/setup-java@v4 - with: - distribution: oracle - java-version: '17' - cache: 'maven' - - name: Set up OSS Community Develocity Instance for Maven - uses: gradle/develocity-actions/setup-maven@v2.1 - with: develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }} - - name: Install softhsm2 - run: sudo apt-get install -y softhsm2 - - name: Install opensc - run: sudo apt-get install -y opensc - - name: Ensure SoftHSM user configuration - run: impl/src/test/scripts/softhsm configure - - name: Populate SoftHSM with JJWT test keys - run: impl/src/test/scripts/softhsm import - name: Build # run a full build, just as we would for a release (i.e. the `ossrh` profile), but don't use gpg # to sign artifacts, since we don't want to mess with storing signing credentials in CI: - run: ${{env.MVN_CMD}} verify -Possrh -Dgpg.skip=true -Dtest.jdk.version=${{ matrix.java }} + run: ${{env.MVN_CMD}} verify -Possrh -Dgpg.skip=true -Dtest.jdk.version=${{ matrix.java-version }} non-oracle: runs-on: 'ubuntu-latest' strategy: matrix: - distribution: [ 'temurin', 'zulu', 'corretto' ] - java: [ 8, 11, 17, 21, 25 ] + distribution: [ 'temurin', 'zulu', 'corretto', 'semeru' ] + java-version: [ 8, 11, 17, 21, 25 ] fail-fast: false - name: jdk-${{ matrix.java }}-${{ matrix.distribution }} + name: jdk-${{ matrix.java-version }}-${{ matrix.distribution }} steps: - - uses: actions/checkout@v4 - # Install test-only JDK first; will append an entry to ~/.m2/toolchains.xml - - name: Set up ${{ matrix.distribution }} JDK ${{ matrix.java }} - uses: actions/setup-java@v4 - with: - distribution: ${{ matrix.distribution }} - java-version: ${{ matrix.java }} - cache: 'maven' - check-latest: true - # Install full build JDK last so it ends up as the active JAVA_HOME / PATH entry - - name: Set up JDK 17 (build) - uses: actions/setup-java@v4 + - name: Checkout + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + - name: Setup Build Environment + uses: ./.github/actions/setup-build with: - java-version: '17' + java-version-tests: ${{ matrix.java-version }} distribution: ${{ matrix.distribution }} - cache: 'maven' - check-latest: true - - name: Set up OSS Community Develocity Instance for Maven - uses: gradle/develocity-actions/setup-maven@v2.1 - with: develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }} - - name: Install softhsm2 - run: sudo apt-get install -y softhsm2 - - name: Install opensc - run: sudo apt-get install -y opensc - - name: Ensure SoftHSM user configuration - run: impl/src/test/scripts/softhsm configure - - name: Populate SoftHSM with JJWT test keys - run: impl/src/test/scripts/softhsm import - name: Build # run a full build, just as we would for a release (i.e. the `ossrh` profile), but don't use gpg # to sign artifacts, since we don't want to mess with storing signing credentials in CI: - run: ${{env.MVN_CMD}} verify -Possrh -Dgpg.skip=true -Dtest.jdk.version=${{ matrix.java }} + run: ${{env.MVN_CMD}} verify -Possrh -Dgpg.skip=true -Dtest.jdk.version=${{ matrix.java-version }} # ensure all of our files have the correct/updated license header license-check: runs-on: 'ubuntu-latest' steps: - - uses: actions/checkout@v4 + - name: Checkout + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: - fetch-depth: 0 # avoid license plugin history warnings (plus it needs full history) - - name: Set up JDK - uses: actions/setup-java@v4 - with: - distribution: 'zulu' - java-version: '17' - cache: 'maven' - check-latest: true - - name: Set up OSS Community Develocity Instance for Maven - uses: gradle/develocity-actions/setup-maven@v2.1 + fetch-depth: 0 + - name: Setup Build Environment + uses: ./.github/actions/setup-build with: develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }} + setup-softhsm: 'false' - name: License Check # This adds about 1 minute to any build, which is why we don't want to do it on every build: run: ${{env.MVN_CMD}} license:check @@ -118,26 +74,12 @@ jobs: code-coverage: runs-on: 'ubuntu-latest' steps: - - uses: actions/checkout@v4 - - name: Set up JDK - uses: actions/setup-java@v4 - with: - distribution: 'zulu' - java-version: '17' - cache: 'maven' - check-latest: true - - name: Set up OSS Community Develocity Instance for Maven - uses: gradle/develocity-actions/setup-maven@v2.1 + - name: Checkout + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + - name: Setup Build Environment + uses: ./.github/actions/setup-build with: develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }} - - name: Install softhsm2 - run: sudo apt-get install -y softhsm2 - - name: Install opensc - run: sudo apt-get install -y opensc - - name: Ensure SoftHSM user configuration - run: impl/src/test/scripts/softhsm configure - - name: Populate SoftHSM with JJWT test keys - run: impl/src/test/scripts/softhsm import - name: Code Coverage # run a full build, just as we would for a release (i.e. the `ossrh` profile), but don't use gpg # to sign artifacts, since we don't want to mess with storing signing credentials in CI: From fe3e51fffa6e0b210b8c68f99401564f40e70c4f Mon Sep 17 00:00:00 2001 From: Les Hazlewood <121180+lhazlewood@users.noreply.github.com> Date: Thu, 4 Jun 2026 11:56:16 -0400 Subject: [PATCH 10/11] - Testing single JDK matrix job with all JDK vendors --- .github/workflows/ci.yml | 33 +++++++++------------------------ 1 file changed, 9 insertions(+), 24 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 46e7ed4bc..b144899ce 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,34 +11,19 @@ env: MVN_CMD: ./mvnw --no-transfer-progress -B jobs: - # Oracle only provides JDK 17+, so test against 17, 21, and 25 - oracle: - runs-on: 'ubuntu-latest' - name: jdk-${{ matrix.java-version }}-oracle - strategy: - matrix: - java-version: [ 17, 21, 25 ] - steps: - - name: Checkout - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - - name: Setup Build Environment - uses: ./.github/actions/setup-build - with: - java-version-tests: ${{ matrix.java-version }} - distribution: oracle - develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }} - - name: Build - # run a full build, just as we would for a release (i.e. the `ossrh` profile), but don't use gpg - # to sign artifacts, since we don't want to mess with storing signing credentials in CI: - run: ${{env.MVN_CMD}} verify -Possrh -Dgpg.skip=true -Dtest.jdk.version=${{ matrix.java-version }} - - non-oracle: + build: runs-on: 'ubuntu-latest' strategy: + fail-fast: false matrix: - distribution: [ 'temurin', 'zulu', 'corretto', 'semeru' ] + distribution: [ 'oracle', 'temurin', 'zulu', 'corretto', 'semeru' ] java-version: [ 8, 11, 17, 21, 25 ] - fail-fast: false + # Oracle only provides JDK 17+, so exclude earlier versions: + exclude: + - distribution: 'oracle' + java-version: 8 + - distribution: 'oracle' + java-version: 11 name: jdk-${{ matrix.java-version }}-${{ matrix.distribution }} steps: - name: Checkout From b9c505cee13b04e0209ae6175d7568c841c9a135 Mon Sep 17 00:00:00 2001 From: Les Hazlewood <121180+lhazlewood@users.noreply.github.com> Date: Thu, 4 Jun 2026 20:51:37 -0400 Subject: [PATCH 11/11] - Removed module.skip.jdk*.tests properties until we figure out how to do this with multi-module builds (if we do them) --- pom.xml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/pom.xml b/pom.xml index f4383af48..2207039fb 100644 --- a/pom.xml +++ b/pom.xml @@ -114,11 +114,6 @@ 8 - - false - false ${user.name}-${maven.build.timestamp} 2.19.2 @@ -735,7 +730,6 @@ test-jdk-8 test - ${module.skip.jdk8.tests} [1.8,1.9),[8,9) @@ -771,7 +765,6 @@ test-jdk-11 test - ${module.skip.jdk11.tests} 11