JWE support (#279)

* JWE support. Resolves #113

Huge feature/code change - see CHANGELOG.MD for an in-depth review of changes.

Commit note log:

- Added JWE AeadAlgorithm and KeyAlgorithm and supporting interfaces/implementations, and refactored SignatureAlgorithm to be an interface instead of an enum to enable custom algorithms
- NoneSignatureAlgorithm cleanup. Added UnsupportedKeyExceptionTest.
- Added JWK support!

* Removed the previous SignatureAlgorithm implementation concepts (Provider/Signer/Validator implementations).  Implementations are now interface-driven and fully pluggable.

* adding tests, working towards 100% coverage.  Moved api static factory class tests to impl module to avoid mocking static calls due to bridge/reflection logic.

* adding tests, removed unused class

* implementation checkpoint (safety save)

* clean build checkpoint

* continued testing w/ more coverage.  Replaced PbeKey concept with PasswordKey

* fixed erroneous optimize imports

* Added ECDH-ES key algorithms + RFC tests

* 1. Enabled targeted/limited use of BouncyCastle only when required by eliminating use of RuntimeEnvironment in favor of new Providers implementation.  JJWT will no longer modify the system providers list.
2. Changed SecretKeyGenerator.generateKey() to KeyBuilderSupplier.keyBuilder() and a new SecretKeyBuilder interface.  This allows users to customize the Provider and SecureRandom used during key generation if desired.
3. Added KeyLengthSupplier to allow certain algorithms the ability to determine a key bit length without forcing a key generation.
4. Ensured Pbes2 algorithms defaulted to OWASP-recommended iteration counts if not specified by the user.

* 1. EcdhKeyAlgorithm: consolidated duplicate logic to a single private helper method
2. Updated RequiredTypeConverter exception message to represent the expected type as well as the found type

* Minor javadoc update

* 1. Javadoc cleanup.
2. Ensured CompressionException extends from io.jsonwebtoken.io.IOException
3. Deleted old POC unused JwkParser interface
4. Ensured NoneSignatureAlgorithm reflected the correct exception message for `sign`

* Fixed erroneous JavaDoc, enhanced code coverage for DefaultClaims

* Added jwe compression test

* Added TestKeys concept for Groovy test authoring

* Added tests, cleaned up state assertions for code coverage

* Added tests, cleaned up state assertions for code coverage

* Removed unused code

* Merge branch 'master' into jwe-merge

* using groovy syntax to avoid conflict with legacy SignatureAlgorithm type

* JavaDoc fixes

* JavaDoc fixes, test additions to work on JDK >= 15

* JavaDoc fixes, test additions to work on JDK 7 and JDK >= 15

* Test adjustment to work on Java 7

* Test adjustment to work on Java 7

* code coverage work cont'd

* code coverage work cont'd

* JavaDoc fix

* Update impl/src/main/java/io/jsonwebtoken/impl/JwtTokenizer.java

Co-authored-by: sonatype-lift[bot] <37194012+sonatype-lift[bot]@users.noreply.github.com>

* lift edits

* lift edits

* lift edits

* code coverage testing cont'd

* PayloadSupplier renamed to Message

* Added new KeyPairBuilder/KeyPairBuilderSupplier for parity with KeyBuilder/KeyBuilderSupplier.  Switched all generate* calls to use the new API methods.

* - Added lots of JavaDoc
- JwtMap: Ensured Groovy GString implementations that invoke Groovy's InvokerHelper on a JwtMap implementation will print redacted values instead of the actual secret values.

* - JavaDoc additions cont'd

* - JavaDoc additions and syntax cleanup cont'd
- Minor work to fix compilation errors on a few Groovy test classes

* - JavaDoc additions and syntax cleanup cont'd
- Minor work to fix compilation errors on a few Groovy test classes

* - JavaDoc additions and syntax cleanup cont'd
- Minor work to fix compilation errors on a few Groovy test classes

* - JavaDoc additions and syntax cleanup cont'd
- Minor work to fix compilation errors on a few Groovy test classes

* - JavaDoc cont'd

* Code coverage updates cont'd

* Propagating exception wrapper function enhancements

* 100% code coverage!

* Minor test changes to work with JDK >= 11

* Ensured all JWK secret or private values were wrapped in a RedactedSupplier instance to prevent accidental printing of secure values

* - Updated JavaDoc to reflect JWK toString safety and property access
- Updated README to reflect new GsonSerializer requirements for io.jsonwebtoken.lang.Supplier

* Documentation enhancements

* Fixed erroneous JavaDoc element

* Fixed LocatorAdapter usage now that it's abstract

* Minor JavaDoc fix

* JavaDoc is now complete (no warnings) for api module

* Ensured JWS signatures are computed first before deserializing the body if no SigningKeyResolver has been configured.

* Removed EllipticCurveSignatureAlgorithm and RsaSignatureAlgorithm concepts due to some PKCS11 and HSM security providers that cannot provide keys that implement the ECKey or RSAKey interfaces.

* Removed reliance on io.jsonwebtoken.security.KeyPair now that KeyPairBuilder implementations cannot guarantee RSAKey or ECKey types

* Ensured RsaKeyAlgorithm used PublicKey and PrivateKey parameters due to PKCS11 and HSM key stores that may not expose the RSAKey interface on their RSA key implementations

* cleaned up EC point addition/doubling logic to be more readable and match equations in literature

* Deprecated JwtParserBuilder setSigningKey* methods in favor of verifyWith for name accuracy and congruence with decryptWith

* Added PositiveIntegerConverter and PublicJwkConverter for JWE Header "p2c" and "jwk" fields

* Ensured CompressionCodec inherited Identifiable for consistency w/ all other algorithms

* Ensured CompressionCodec inherited Identifiable for consistency w/ all other algorithms

* Ensured CompressionCodec inherited Identifiable for consistency w/ all other algorithms

* JavaDoc enhancement

* Added Jwks.parserBuilder(), JwkParserBuilder and JwkParser concepts

* Ensured ProtoJwkBuilder method names were all congruent (remaining set* methods were renamed to for*)

* Minor JavaDoc organization change

* Changed JweBuilder to have only two encryptWith* methods for consistency with JwtBuilder signWith* methods.  Also prevents incorrect configuration by forgetting to call follow-up methods.

* Removed DefaultValueGetter in favor of new FieldReadable concept to leverage Field instances instead of duplicating logic.

* Adding copyright headers

* Deprecated CompressionCodecResolver in favor of Locator<CompressionCodec>

* Deprecated CompressionCodecResolver in favor of Locator<CompressionCodec>

* Folded in JweBuilder concept and implementation into the existing JwtBuilder

* Cleanup to reduce duplicate logic

* Changed plaintext JWT payload type from String to byte[]

* Minor internal doc fixes

* - Added UnprotectedHeader interface
- Renamed DefaultHeader to AbstractHeader
- Renamed JwtParser 'plaintext'* methods to 'payload'* methods to more accurately reflect the JWT nomenclature

* Updated changelog to reflect the following changes:
- Added UnprotectedHeader interface
- Renamed DefaultHeader to AbstractHeader
- Renamed JwtParser 'plaintext'* methods to 'payload'* methods to more accurately reflect the JWT nomenclature

* Adding RFC 7520 test cases.

* Changed JwtBuilder/JwtParser/JwtHandler/JwtHandlerAdapter `payload` concept to `content` concept

* JavaDoc error fix

* Added JwtParserBuilder#addCompressionCodecs method and supporting tests

* Updated changelog to reflect recent changes

* doc updates

* Fixed minor types in JWE related change log

* Enabled Mutator and HeaderBuilder interfaces and implementations

* Fixed erroneous JavaDoc

* Added additional .pem files for testing PEM parsing (TBD at a later date)

* Updating documentation to prepare for JWE release

* more docs

* docs cont'd

* Update README.md

Trying to prevent table wrap

* Update README.md

testing table whitespace wrap

* Update README.md

formatting testing

* Update README.md

formatting testing cont'd

* docs cont'd

* docs cont'd

* docs cont'd

* documentation checkpoint

* documentation checkpoint, still work in progress

* added extra info about Android BouncyCastle registration

* added extra info about Android BouncyCastle registration

* added extra info about Android BouncyCastle registration

* documentation cont'd

* documentation cont'd

* documentation cont'd

* documentation cont'd

* documentation cont'd

* Upgraded Jackson to 2.12.7 due to Jackson CVE

* trying to get sonatype lift working again

* Removed unnecessary println statement in test

* Added JDK 19

* Removing JDK 19 until we can resolve incompatible groovy version

* doc formatting test

* Update README.md

Fixed 'JWE Encryption Algorithms' table formatting

* renamed PasswordKey to just Password, removed unnecessary WrappedSecretKey class and its one usage in favor of JDK-standard SecretKeySpec

* Updated PasswordKey references to Password

* Renamed DefaultPassword to PasswordSpec while also implementing KeySpec per JDK conventions.

* documentation cont'd

* documentation cont'd

* documentation cont'd

* JWT expired exception is now shows difference as now - expired

Instead of now + skew - expired

Fixes: #660

* documentation cont'd

* Fixed erroneous error message (should be '521' not '512').

* - Removed EcKeyAlgorithm and RsaKeyAlgorithm in favor of KeyAlgorithm<PublicKey, PrivateKey> due to HSM key types unable to conform to respective ECKey or RSAKey types
- added Curve concept with DefaultCurve and ECCurve implementations for use across EC/EdEC SignatureAlgorithm and KeyAlgorithm implementations
- disabled Zulu JDK 10 - compiler was failing, and that is a short-term-supported version anyway
- added Temurin JDK 19 and Zulu JDK 19 to the build

* Removed JDK 19 builds due to error with Groovy compiler version compatibility

* removed unused test, created https://github.com/jwtk/jjwt/issues/765 to address later

* updated test case to reflect Edwards keys algorithm name differences between BouncyCastle and JDK 11/15

* - Reordered JwtBuilder .encryptWith method arguments to match signWith conventions
- Added key validation logic to DefaultRsaKeyAlgorithm

* - Reordered JwtBuilder .encryptWith method arguments to match signWith conventions
- Added key validation logic to DefaultRsaKeyAlgorithm
- Removed mutation methods on InvalidClaimException and its subclasses, IncorrectClaimException and MissingClaimException
- renamed Jwks.parserBuilder() to Jwks.parser() to reduce verbosity since we'll never likely offer a Jwts.parser() that returns a Parser instance directly.

* Documentation updates

* Documentation updates

* Documentation updates

* Adding Java-based tests for README.md code snippets (e.g. new Examples section).

* Testing README code snippet in examples

* Testing README code snippet in examples

* Testing README code snippet in examples

* Ensured README.md examples would compile/run by testing code in a JavaReadmeTest.java class

* Ensured README.md examples would compile/run by testing code in a JavaReadmeTest.java class

* Ensured README.md examples would compile/run by testing code in a JavaReadmeTest.java class

* Ensured README.md examples would compile/run by testing code in a JavaReadmeTest.java class

* Ensured README.md examples would compile/run by testing code in a JavaReadmeTest.java class

* Ensured README.md examples would compile/run by testing code in a JavaReadmeTest.java class

* Ensured README.md examples would compile/run by testing code in a JavaReadmeTest.java class

* Ensured README.md examples would compile/run by testing code in a JavaReadmeTest.java class

* Ensured README.md examples would compile/run by testing code in a JavaReadmeTest.java class

* Ensured README.md examples would compile/run by testing code in a JavaReadmeTest.java class

* Ensured README.md examples would compile/run by testing code in a JavaReadmeTest.java class

* Ensured README.md examples would compile/run by testing code in a JavaReadmeTest.java class

* Ensured README.md examples would compile/run by testing code in a JavaReadmeTest.java class

* Testing coveralls failure fix for JavaReadmeTest (unnecessary for coverage)

* Message* API refactoring cleanup

* Refactored SignatureAlgorithm(s) concept as a new DigestAlgorithm hierarchy to support non-keyed digests, as well as to reflect correct cryptographic taxonomy.  Renamed SignatureAlgorithms utility class to JwsAlgorithms.

* - Added JwkThumbprint and JWK Thumbprint URI support
- Fixed various copyright headers

* Updated README with JWK examples

* - Added logic and test to ensure Parser builder does not allow both a signature verification key and SigningKeyResolver to be configured.
- Updated README code example, and added test for verification

* - Added proactive checks to ensure PublicKey instances cannot be specified on JwtBuilder to create digital signatures
- Added additional tests to ensure that Password instances cannot be used with Mac algorithm instances (HS256, HS384, HS512, etc)

* minor edit to reflect latest # of test cases

* Minor JavaDoc improvement

* Disabled parsing of unsecured compressed payloads by default, with a JwtParserBuilder#enableUnsecuredDecompression method added for override if necessary.

* Refactored JcaTemplate to avoid reflection

* minor readme clarification

* Edwards Curve keys checkpoint

* Edwards Curve keys and related functionality checkpoint.  Added lots of tests.

* Edwards Curve keys and related functionality checkpoint.  Reached 100% code coverage.

* Addressed EdwardsCurve differences (and a JDK 11 PKCS8 encoding bug) between JDK versions (1.7 through 15+).

* Addressed EdwardsCurve differences (and a JDK 11 PKCS8 encoding bug) between JDK versions (1.7 through 18).

* Adding --add-opens lines to surefire/test config to avoid unnecessary build warnings

* Improved Edwards Key encoding error checks

* added some code to test errors in CI

* added some code to test errors in CI

* added some code to test errors in CI

* added some code to test errors in CI

* added some code to test errors in CI

* added some code to test errors in CI

* added some code to test errors in CI

* added some code to test errors in CI

* added some code to test errors in CI

* added some code to test errors in CI

* added some code to test errors in CI

* added some code to test errors in CI

* added some code to test errors in CI

* Improved Field/FieldBuilder implementation to make it more robust and catch type errors.  JwkBuilder/Factory refactoring is likely to follow on subsequent commits.

* Updating CI config to use the Oracle no-fee JDK builds

* Updating CI config to use the Oracle no-fee JDK builds

* Updating CI config to use the Oracle no-fee JDK builds

* JavaDoc fixes

* JavaDoc fixes

* JavaDoc fixes

* Ensured license headers are updated with correct dates based on git history, as well as ensured this is enforced in CI

* fixed various build warnings and javadoc errors

* Maven license plugin config cleanup. Removed now-unused header_format.xml file.

* fixed javadoc error causing build failures

* Changed Algorithms + Bridge concept to allow nested inner classes for simpler authoring all stemming from an 'Algorithms' class (easier to find algorithms via code completion than knowing off the top of your head which *Algorithms classes to reference).

* Added license headers

* Moved JwsAlgorithms to an inner class of Algorithms.  Will rename to `StandardSecureDigestAlgorithms` to maintain the convention used with the other Algorithms inner classes.

* Extracting Algorithms inner classes up a level - cleaner/easier to maintain, document and reference as JavaDoc links.

* Extracting Algorithms inner classes up a level - cleaner/easier to maintain, document and reference as JavaDoc links.  Renamed JwsAlgorithms to StandardSecureDigestAlgorithms to retain the JDK 'Standard*' convention.

* Removed Algorithms class in favor of direct `Standard*` `Registry` references in `Jwts` and `Jwks` helper classes to keep the references 'close' to where they are used the most.

* Minor README.md documentation updates.

* Copying over 6e74486 to test on the jwe branch

* Ensured CI license-check build pulls full (non-shallow) git history to perform full year checks.

* JavaDoc + impl + test checkpoint

* JavaDoc + impl + test checkpoint. Returned to 100% code coverage

* JavaDoc + impl + test checkpoint. Returned to 100% code coverage

* - Ensured Edwards Curve keys (X25519 and X448) worked with ECDH-ES algorithms
- Ensured JWT Header ephemeral PublicKey ('epk' field) could be any Public JWK, not just an EcPublicJwk
- Updated README.md to ensure the installation instructions for uncommenting BouncyCastle were a little less confusing (having commented out stuff be at the end of the code block so it couldn't be confused with other lines)

* Ensured correct message assertion on all JDKs (value was different on JDK 15 and later)

* Ensured correct message assertion on all JDKs (value was different on JDK 15 and later)

* - enabled more IANA algorithms in StandardHashAlgorithms
- JavaDoc update

* - JavaDoc fixes/enhancements
- Fixed erroneous README.md method name reference
- ensured DefaultJwkContext#getName() supports Octet keys as well.

* - JavaDoc fixes/enhancements

* - JavaDoc fixes/enhancements required to pass the build on later JDKs

* Minor JavaDoc typo fix

* Finished implementing all [RFC 8037](https://www.rfc-editor.org/rfc/rfc8037) test vectors in the Appendix

* Removed accidentally-committed visibility modifier

* Added copyright header

* Enabled PublicKey derivation from Edwards curve PrivateKey

* Enabled PublicKey derivation from Edwards curve PrivateKey.  Updated documentation and code example showing this.

* Enabled PublicKey derivation from Edwards curve PrivateKey.  Updated documentation and code example showing this.

* Doc update to reference Octet JWK RFC.

* Minor JavaDoc fix

* Fixed OctetPrivateJwk discrepancy with generic parameter ordering (compared to other PrivateJwk interfaces)

* Changed Jwts.header to Jwts.unprotectedHeader, and Jwts.headerBuilder is now Jwts.header

---------

Co-authored-by: sonatype-lift[bot] <37194012+sonatype-lift[bot]@users.noreply.github.com>
Co-authored-by: Brian Demers <bdemers@apache.org>
This commit is contained in:
lhazlewood 2023-05-18 15:21:17 -07:00 committed by GitHub
parent c260e55f6e
commit 760c542a0b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
616 changed files with 46605 additions and 6529 deletions

View File

@ -73,6 +73,27 @@ jobs:
if [ "$JDK_MAJOR_VERSION" == "7" ]; then export MAVEN_OPTS="-Xmx512m -XX:MaxPermSize=128m"; fi if [ "$JDK_MAJOR_VERSION" == "7" ]; then export MAVEN_OPTS="-Xmx512m -XX:MaxPermSize=128m"; fi
${{env.MVN_CMD}} verify -Possrh -Dgpg.skip=true ${{env.MVN_CMD}} verify -Possrh -Dgpg.skip=true
# ensure all of our files have the correct/updated license header
license-check:
runs-on: 'ubuntu-latest'
env:
MVN_CMD: ./mvnw --no-transfer-progress -B
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0 # avoid license plugin history warnings (plus it needs full history)
- name: Set up JDK
uses: actions/setup-java@v3
with:
distribution: 'zulu'
java-version: '8'
cache: 'maven'
check-latest: true
- 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
code-coverage: code-coverage:
# (commented out for now - see the comments in 'Wait to start' below for why. Keeping this here as a placeholder # (commented out for now - see the comments in 'Wait to start' below for why. Keeping this here as a placeholder
# as it may be better to use instead of an artificial delay once we no longer need to build on JDK 7): # as it may be better to use instead of an artificial delay once we no longer need to build on JDK 7):
@ -107,4 +128,4 @@ jobs:
${{env.MVN_CMD}} -pl . clover:clover clover:check coveralls:report \ ${{env.MVN_CMD}} -pl . clover:clover clover:check coveralls:report \
-DrepoToken="${{ secrets.GITHUB_TOKEN }}" \ -DrepoToken="${{ secrets.GITHUB_TOKEN }}" \
-DserviceName=github \ -DserviceName=github \
-DserviceBuildNumber="${{ env.GITHUB_RUN_ID }}" -DserviceBuildNumber="${{ env.GITHUB_RUN_ID }}"

1
.gitignore vendored
View File

@ -1,4 +1,3 @@
*.class
.DS_Store .DS_Store
# Mobile Tools for Java (J2ME) # Mobile Tools for Java (J2ME)

View File

@ -1,4 +1,21 @@
ignoreRules = ["MissingOverride"] #
ignoreFiles = ''' # Copyright © 2022 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.
#
ignoreRules = ["MissingOverride", "MissingSummary", "InconsistentCapitalization", "JavaUtilDate", "TypeParameterUnusedInFormals", "JavaLangClash", "InlineFormatString"]
ignoreFiles = """
impl/**
**/test/** **/test/**
''' """

View File

@ -2,8 +2,215 @@
### JJWT_RELEASE_VERSION ### JJWT_RELEASE_VERSION
* Adds a simplified "starter" jar that automatically pulls in `jjwt-api`, `jjwt-impl` and `jjwt-jackson`, useful when This is a big release! JJWT now fully supports Encrypted JSON Web Tokens (JWE) and JSON Web Keys (JWK)! See the
upgrading from the older `io.jsonwebtoken:jjwt:*` to the project's current flexible module structure. sections below enumerating all new features as well as important notes on breaking changes or backwards-incompatible
changes made in preparation for the upcoming 1.0 release.
#### Simplified Starter Jar
Those upgrading to new modular JJWT versions from old single-jar versions will transparently obtain everything
they need in their Maven, Gradle or Android projects.
JJWT's early releases had one and only one .jar: `jjwt.jar`. Later releases moved to a modular design with 'api' and
'impl' jars including 'plugin' jars for Jackson, GSON, org.json, etc. Some users upgrading from the earlier single
jar to JJWT's later versions have been frustrated by being forced to learn how to configure the more modular .jars.
This release re-introduces the `jjwt.jar` artifact again, but this time it is simply an empty .jar with Maven
metadata that will automatically transitively download the following into a project, retaining the old single-jar
behavior:
* `jjwt-api.jar`
* `jjwt-impl.jar`
* `jjwt-jackson.jar`
Naturally, developers are still encouraged to configure the modular .jars as described in JJWT's documentation for
greater control and to enable their preferred JSON parser, but this stop-gap should help those unaware when upgrading.
#### JSON Web Encryption (JWE) Support!
This has been a long-awaited feature for JJWT, years in the making, and it is quite extensive - so many encryption
algorithms and key management algorithms are defined by the JWA specification, and new API concepts had to be
introduced for all of them, as well as extensive testing with RFC-defined test vectors. The wait is over!
All JWA-defined encryption algorithms and key management algorithms are fully implemented and supported and
available immediately. For example:
```java
AeadAlgorithm enc = Jwts.ENC.A256GCM;
SecretKey key = enc.keyBuilder().build();
String compact = Jwts.builder().setSubject("Joe").encryptWith(key, enc).compact();
Jwe<Claims> jwe = Jwts.parserBuilder().decryptWith(key).build().parseClaimsJwe(compact);
```
Many other RSA and Elliptic Curve examples are in the full README documentation.
#### JSON Web Key (JWK) Support!
Representing cryptographic keys - SecretKeys, RSA Public and Private Keys, Elliptic Curve Public and
Private keys - as fully encoded JSON objects according to the JWK specification - is now fully implemented and
supported. The new `Jwks` utility class exists to create JWK builders and parsers as desired. For example:
```java
SecretKey key = Jwts.SIG.HS256.keyBuilder().build();
SecretJwk jwk = Jwks.builder().forKey(key).build();
assert key.equals(jwk.toKey());
// or if receiving a JWK string:
Jwk<?> parsedJwk = Jwks.parser().build().parse(jwkString);
assert jwk.equals(parsedJwk);
assert key.equals(parsedJwk.toKey());
```
Many JJWT users won't need to use JWKs explicitly, but some JWA Key Management Algorithms (and lots of RFC test
vectors) utilize JWKs when transmitting JWEs. As this was required by JWE, it is now implemented in full for
JWE use as well as general-purpose JWK support.
#### Better PKCS11 and Hardware Security Module (HSM) support
Previous versions of JJWT enforced that Private Keys implemented the `RSAKey` and `ECKey` interfaces to enforce key
length requirements. With this release, JJWT will still perform those checks when those data types are available,
but if not, as is common with keys from PKCS11 and HSM KeyStores, JJWT will still allow those Keys to be used,
expecting the underlying Security Provider to enforce any key requirements. This should reduce or eliminate any
custom code previously written to extend JJWT to use keys from those KeyStores or Providers.
#### Custom Signature Algorithms
The `io.jsonwebtoken.SignatureAlgorithm` enum has been deprecated in favor of new
`io.jsonwebtoken.security.SecureDigestAlgorithm`, `io.jsonwebtoken.security.MacAlgorithm`, and
`io.jsonwebtoken.security.SignatureAlgorithm` interfaces to allow custom algorithm implementations. The new
`SIG` constant in the `Jwts` helper class is a registry of all standard JWS algorithms as expected, exactly like the
old enum. This change was made because enums are a static concept by design and cannot
support custom values: those who wanted to use custom signature algorithms could not do so until now. The new
interface now allows anyone to plug in and support custom algorithms with JJWT as desired.
#### KeyBuilder and KeyPairBuilder
Because the `io.jsonwebtoken.security.Keys#secretKeyFor` and `io.jsonwebtoken.security.Keys#keyPairFor` methods
accepted the now-deprecated `io.jsonwebtoken.SignatureAlgorithm` enum, they have also been deprecated in favor of
calling new `keyBuilder()` or `keyPairBuilder()` methods on `MacAlgorithm` and `SignatureAlgorithm` instances directly.
For example:
```java
SecretKey key = Jwts.SIG.HS256.keyBuilder().build();
KeyPair pair = Jwts.SIG.RS256.keyPairBuilder().build();
```
The builders allow for customization of the JCA `Provider` and `SecureRandom` during Key or KeyPair generation if desired, whereas
the old enum-based static utility methods did not.
#### Preparation for 1.0
Now that the JWE and JWK specifications are implemented, only a few things remain for JJWT to be considered at
version 1.0. We have been waiting to apply the 1.0 release version number until the entire set of JWT specifications
are fully supported and we drop JDK 7 support (to allow users to use JDK 8 APIs). To that end, we have had to
deprecate some concepts, or in some rare cases, completely break backwards compatibility to ensure the transition to
1.0 (and JDK 8 APIs) are possible. Any backwards-incompatible changes are listed in the next section below.
#### Backwards Compatibility Breaking Changes, Warnings and Deprecations
* `io.jsonwebtoken.Jwts`'s `header(Map)`, `jwsHeader()` and `jwsHeader(Map)` methods have been deprecated in favor
of the new `header()` builder-based method to support method chaining and dynamic Header type creation.
* `io.jsonwebtoken.Jwt`'s `getBody()` method has been deprecated in favor of a new `getPayload()` method to
reflect correct JWT specification nomenclature/taxonomy.
* `io.jsonwebtoken.CompressionCodec` now inherits a new `io.jsonwebtoken.Identifiable` interface and its `getId()`
method is preferred over the now-deprecated `getAlgorithmName()` method. This is to guarantee API congruence with
all other JWT-identifiable algorithm names that can be set as a header value.
* `io.jsonwebtoken.Header` has been changed to accept a type-parameter for sub-type method return values, i.e.
`io.jsonwebtoken.Header<T extends Header>` and a new `io.jsonwebtoken.UnprotectedHeader` interface has been
introduced to represent the concrete type of header without integrity protection. This new `UnprotectedHeader` is
to be used where the previous generic `Header` (non-`JweHeader` and non-`JwsHeader`) interface was used.
* Accordingly, the `Jwts.header()` and `Jwts.header(Map<String,?>)` now return instances of `UnprotectedHeader` instead
of just `Header`.
#### Breaking Changes
* **JWTs that do not contain JSON Claims now have a payload type of `byte[]` instead of `String`** (that is,
`Jwt<byte[]>` instead of `Jwt<String>`). This is because JWTs, especially when used with the
`cty` (Content Type) header, are capable of handling _any_ type of payload, not just Strings. The previous JJWT
releases didn't account for this, and now the API accurately reflects the JWT RFC specification payload
capabilities. Additionally, the name of `plaintext` has been changed to `content` in method names and JavaDoc to
reflect this taxonomy. This change has impacted the following JJWT APIs:
* The `JwtBuilder`'s `setPayload(String)` method has been deprecated in favor of two new methods:
* `setContent(byte[])`, and
* `setContent(byte[], String contentType)`
These new methods allow any kind of content
within a JWT, not just Strings. The existing `setPayload(String)` method implementation has been changed to
delegate to this new `setContent(byte[])` method with the argument's UTF-8 bytes, for example
`setContent(payloadString.getBytes(StandardCharsets.UTF_8))`.
* The `JwtParser`'s `Jwt<Header, String> parsePlaintextJwt(String plaintextJwt)` and
`Jws<String> parsePlaintextJws(String plaintextJws)` methods have been changed to
`Jwt<UnprotectedHeader, byte[]> parseContentJwt(String plaintextJwt)` and
`Jws<byte[]> parseContentJws(String plaintextJws)` respectively.
* `JwtHandler`'s `onPlaintextJwt(String)` and `onPlaintextJws(String)` methods have been changed to
`onContentJwt(byte[])` and `onContentJws(byte[])` respectively.
* `io.jsonwebtoken.JwtHandlerAdapter` has been changed to reflect the above-mentioned name and `String`-to-`byte[]`
argument changes, as well adding the `abstract` modifier. This class was never intended
to be instantiated directly, and is provided for subclassing only. The missing modifier has been added to ensure
the class is used as it had always been intended.
* `io.jsonwebtoken.SigningKeyResolver`'s `resolveSigningKey(JwsHeader, String)` method has been changed to
`resolveSigningKey(JwsHeader, byte[])`.
* `io.jsonwebtoken.Jwts`'s `parser()` method deprecated 4 years ago has been renamed to `legacyParser()` to
allow an updated `parser()` method to return a `JwtParserBuilder` instead of a direct `JwtParser` instance.
This `legacyParser()` method will be removed entirely for the 1.0 release - please change your code to use the
updated `parser()` method that returns a builder as soon as possible.
* `io.jsonwebtoken.Jwts`'s `header()` method has been renamed to `unprotectedHeader()` to allow a newer/updated
`header()` method to return a `DynamicHeaderBuilder` instead of a direct `Header` instance. This new method /
return value is the recommended approach for building headers, as it will dynamically create an `UnprotectedHeader`,
`JwsHeader` or `JweHeader` automatically based on builder state.
* `io.jsonwebtoken.Jwts`'s `headerBuilder()` method has been renamed to `header()` and returns a
`DynamicHeaderBuilder` instead of a direct `Header` instance. This builder method is the recommended approach
for building headers in the future, as it will dynamically create an `UnprotectedHeader`, `JwsHeader` or `JweHeader`
automatically based on builder state.
* `io.jsonwebtoken.Jwts`'s `header()` method now returns a `DynamicHeaderBuilder` instead of a
direct `Header` instance. This new method / return value is the recommended approach for building headers
in the future, as it will dynamically create an `UnprotectedHeader`, `JwsHeader` or `JweHeader` automatically
based on builder state.
* Prior to this release, if there was a serialization problem when serializing the JWT Header, an `IllegalStateException`
was thrown. If there was a problem when serializing the JWT claims, an `IllegalArgumentException` was
thrown. This has been changed up to ensure consistency: any serialization error with either headers or claims
will now throw a `io.jsonwebtoken.io.SerializationException`.
* Parsing of unsecured JWTs (`alg` header of `none`) are now disabled by default as mandated by
[RFC 7518, Section 3.6](https://www.rfc-editor.org/rfc/rfc7518.html#section-3.6). If you require parsing of
unsecured JWTs, you must call the `enableUnsecuredJws` method on the `JwtParserBuilder`, but note the security
implications mentioned in that method's JavaDoc before doing so.
* `io.jsonwebtoken.gson.io.GsonSerializer` now requires `Gson` instances that have a registered
`GsonSupplierSerializer` type adapter, for example:
```java
new GsonBuilder()
.registerTypeHierarchyAdapter(io.jsonwebtoken.lang.Supplier.class, GsonSupplierSerializer.INSTANCE)
.disableHtmlEscaping().create();
```
This is to ensure JWKs have `toString()` and application log safety (do not print secure material), but still
serialize to JSON correctly.
* `io.jsonwebtoken.InvalidClaimException` and it's two subclasses (`IncorrectClaimException` and `MissingClaimException`)
were previously mutable, allowing the corresponding claim name and claim value to be set on the exception after
creation. These should have always been immutable without those setters (just getters), and this was a previous
implementation oversight. This release has ensured they are immutable without the setters.
### 0.11.5 ### 0.11.5

3164
README.md

File diff suppressed because it is too large Load Diff

View File

@ -16,36 +16,83 @@
package io.jsonwebtoken; package io.jsonwebtoken;
/** /**
* ClaimJwtException is a subclass of the {@link JwtException} that is thrown after a validation of an JTW claim failed. * ClaimJwtException is a subclass of the {@link JwtException} that is thrown after a validation of an JWT claim failed.
* *
* @since 0.5 * @since 0.5
*/ */
public abstract class ClaimJwtException extends JwtException { public abstract class ClaimJwtException extends JwtException {
/**
* Deprecated as this is an implementation detail accidentally exposed in the JJWT 0.5 public API. It is no
* longer referenced anywhere in JJWT's implementation and will be removed in a future release.
*
* @deprecated will be removed in a future release.
*/
@Deprecated
public static final String INCORRECT_EXPECTED_CLAIM_MESSAGE_TEMPLATE = "Expected %s claim to be: %s, but was: %s."; public static final String INCORRECT_EXPECTED_CLAIM_MESSAGE_TEMPLATE = "Expected %s claim to be: %s, but was: %s.";
/**
* Deprecated as this is an implementation detail accidentally exposed in the JJWT 0.5 public API. It is no
* longer referenced anywhere in JJWT's implementation and will be removed in a future release.
*
* @deprecated will be removed in a future release.
*/
@Deprecated
public static final String MISSING_EXPECTED_CLAIM_MESSAGE_TEMPLATE = "Expected %s claim to be: %s, but was not present in the JWT claims."; public static final String MISSING_EXPECTED_CLAIM_MESSAGE_TEMPLATE = "Expected %s claim to be: %s, but was not present in the JWT claims.";
private final Header header; /**
* The header associated with the Claims that failed validation.
*/
private final Header<?> header;
/**
* The Claims that failed validation.
*/
private final Claims claims; private final Claims claims;
protected ClaimJwtException(Header header, Claims claims, String message) { /**
* Creates a new instance with the specified header, claims and exception message.
*
* @param header the header inspected
* @param claims the claims obtained
* @param message the exception message
*/
protected ClaimJwtException(Header<?> header, Claims claims, String message) {
super(message); super(message);
this.header = header; this.header = header;
this.claims = claims; this.claims = claims;
} }
protected ClaimJwtException(Header header, Claims claims, String message, Throwable cause) { /**
* Creates a new instance with the specified header, claims and exception message as a result of encountering
* the specified {@code cause}.
*
* @param header the header inspected
* @param claims the claims obtained
* @param message the exception message
* @param cause the exception that caused this ClaimJwtException to be thrown.
*/
protected ClaimJwtException(Header<?> header, Claims claims, String message, Throwable cause) {
super(message, cause); super(message, cause);
this.header = header; this.header = header;
this.claims = claims; this.claims = claims;
} }
/**
* Returns the {@link Claims} that failed validation.
*
* @return the {@link Claims} that failed validation.
*/
public Claims getClaims() { public Claims getClaims() {
return claims; return claims;
} }
public Header getHeader() { /**
* Returns the header associated with the {@link #getClaims() claims} that failed validation.
*
* @return the header associated with the {@link #getClaims() claims} that failed validation.
*/
public Header<?> getHeader() {
return header; return header;
} }
} }

View File

@ -24,12 +24,11 @@ import java.util.Map;
* <p>This is ultimately a JSON map and any values can be added to it, but JWT standard names are provided as * <p>This is ultimately a JSON map and any values can be added to it, but JWT standard names are provided as
* type-safe getters and setters for convenience.</p> * type-safe getters and setters for convenience.</p>
* *
* <p>Because this interface extends {@code Map&lt;String, Object&gt;}, if you would like to add your own properties, * <p>Because this interface extends <code>Map&lt;String, Object&gt;</code>, if you would like to add your own properties,
* you simply use map methods, for example:</p> * you simply use map methods, for example:</p>
* *
* <pre> * <blockquote><pre>
* claims.{@link Map#put(Object, Object) put}("someKey", "someValue"); * claims.{@link Map#put(Object, Object) put}("someKey", "someValue");</pre></blockquote>
* </pre>
* *
* <h2>Creation</h2> * <h2>Creation</h2>
* *
@ -41,25 +40,25 @@ import java.util.Map;
public interface Claims extends Map<String, Object>, ClaimsMutator<Claims> { public interface Claims extends Map<String, Object>, ClaimsMutator<Claims> {
/** JWT {@code Issuer} claims parameter name: <code>"iss"</code> */ /** JWT {@code Issuer} claims parameter name: <code>"iss"</code> */
public static final String ISSUER = "iss"; String ISSUER = "iss";
/** JWT {@code Subject} claims parameter name: <code>"sub"</code> */ /** JWT {@code Subject} claims parameter name: <code>"sub"</code> */
public static final String SUBJECT = "sub"; String SUBJECT = "sub";
/** JWT {@code Audience} claims parameter name: <code>"aud"</code> */ /** JWT {@code Audience} claims parameter name: <code>"aud"</code> */
public static final String AUDIENCE = "aud"; String AUDIENCE = "aud";
/** JWT {@code Expiration} claims parameter name: <code>"exp"</code> */ /** JWT {@code Expiration} claims parameter name: <code>"exp"</code> */
public static final String EXPIRATION = "exp"; String EXPIRATION = "exp";
/** JWT {@code Not Before} claims parameter name: <code>"nbf"</code> */ /** JWT {@code Not Before} claims parameter name: <code>"nbf"</code> */
public static final String NOT_BEFORE = "nbf"; String NOT_BEFORE = "nbf";
/** JWT {@code Issued At} claims parameter name: <code>"iat"</code> */ /** JWT {@code Issued At} claims parameter name: <code>"iat"</code> */
public static final String ISSUED_AT = "iat"; String ISSUED_AT = "iat";
/** JWT {@code JWT ID} claims parameter name: <code>"jti"</code> */ /** JWT {@code JWT ID} claims parameter name: <code>"jti"</code> */
public static final String ID = "jti"; String ID = "jti";
/** /**
* Returns the JWT <a href="https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-25#section-4.1.1"> * Returns the JWT <a href="https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-25#section-4.1.1">

View File

@ -25,7 +25,7 @@ import java.util.Date;
* @see io.jsonwebtoken.Claims * @see io.jsonwebtoken.Claims
* @since 0.2 * @since 0.2
*/ */
public interface ClaimsMutator<T extends ClaimsMutator> { public interface ClaimsMutator<T extends ClaimsMutator<T>> {
/** /**
* Sets the JWT <a href="https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-25#section-4.1.1"> * Sets the JWT <a href="https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-25#section-4.1.1">

View File

@ -18,38 +18,47 @@ package io.jsonwebtoken;
/** /**
* Compresses and decompresses byte arrays according to a compression algorithm. * Compresses and decompresses byte arrays according to a compression algorithm.
* *
* <p><b>&quot;zip&quot; identifier</b></p>
*
* <p>{@code CompressionCodec} extends {@code Identifiable}; the value returned from
* {@link Identifiable#getId() getId()} will be used as the JWT
* <a href="https://tools.ietf.org/html/rfc7516#section-4.1.3"><code>zip</code></a> header value.</p>
*
* @see CompressionCodecs#DEFLATE * @see CompressionCodecs#DEFLATE
* @see CompressionCodecs#GZIP * @see CompressionCodecs#GZIP
* @since 0.6.0 * @since 0.6.0
*/ */
public interface CompressionCodec { public interface CompressionCodec extends Identifiable {
/** /**
* The compression algorithm name to use as the JWT's {@code zip} header value. * The algorithm name to use as the JWT
* <a href="https://tools.ietf.org/html/rfc7516#section-4.1.3"><code>zip</code></a> header value.
* *
* @return the compression algorithm name to use as the JWT's {@code zip} header value. * @return the algorithm name to use as the JWT
* <a href="https://tools.ietf.org/html/rfc7516#section-4.1.3"><code>zip</code></a> header value.
* @deprecated since JJWT_RELEASE_VERSION in favor of {@link Identifiable#getId()} to ensure congruence with
* all other identifiable algorithms.
*/ */
@SuppressWarnings("DeprecatedIsStillUsed")
@Deprecated
String getAlgorithmName(); String getAlgorithmName();
/** /**
* Compresses the specified byte array according to the compression {@link #getAlgorithmName() algorithm}. * Compresses the specified byte array, returning the compressed byte array result.
* *
* @param payload bytes to compress * @param content bytes to compress
* @return compressed bytes * @return compressed bytes
* @throws CompressionException if the specified byte array cannot be compressed according to the compression * @throws CompressionException if the specified byte array cannot be compressed.
* {@link #getAlgorithmName() algorithm}.
*/ */
byte[] compress(byte[] payload) throws CompressionException; byte[] compress(byte[] content) throws CompressionException;
/** /**
* Decompresses the specified compressed byte array according to the compression * Decompresses the specified compressed byte array, returning the decompressed byte array result. The
* {@link #getAlgorithmName() algorithm}. The specified byte array must already be in compressed form * specified byte array must already be in compressed form.
* according to the {@link #getAlgorithmName() algorithm}.
* *
* @param compressed compressed bytes * @param compressed compressed bytes
* @return decompressed bytes * @return decompressed bytes
* @throws CompressionException if the specified byte array cannot be decompressed according to the compression * @throws CompressionException if the specified byte array cannot be decompressed.
* {@link #getAlgorithmName() algorithm}.
*/ */
byte[] decompress(byte[] compressed) throws CompressionException; byte[] decompress(byte[] compressed) throws CompressionException;
} }

View File

@ -30,7 +30,11 @@ package io.jsonwebtoken;
* {@link io.jsonwebtoken.JwtParser#setCompressionCodecResolver(CompressionCodecResolver) parsing} JWTs.</p> * {@link io.jsonwebtoken.JwtParser#setCompressionCodecResolver(CompressionCodecResolver) parsing} JWTs.</p>
* *
* @since 0.6.0 * @since 0.6.0
* @deprecated in favor of {@link Locator}
* @see JwtParserBuilder#setCompressionCodecLocator(Locator)
*/ */
@SuppressWarnings("DeprecatedIsStillUsed")
@Deprecated
public interface CompressionCodecResolver { public interface CompressionCodecResolver {
/** /**
@ -41,6 +45,6 @@ public interface CompressionCodecResolver {
* @return CompressionCodec matching the {@code zip} header, or null if there is no {@code zip} header. * @return CompressionCodec matching the {@code zip} header, or null if there is no {@code zip} header.
* @throws CompressionException if a {@code zip} header value is found and not supported. * @throws CompressionException if a {@code zip} header value is found and not supported.
*/ */
CompressionCodec resolveCompressionCodec(Header header) throws CompressionException; CompressionCodec resolveCompressionCodec(Header<?> header) throws CompressionException;
} }

View File

@ -15,17 +15,30 @@
*/ */
package io.jsonwebtoken; package io.jsonwebtoken;
import io.jsonwebtoken.io.IOException;
/** /**
* Exception indicating that either compressing or decompressing an JWT body failed. * Exception indicating that either compressing or decompressing a JWT body failed.
* *
* @since 0.6.0 * @since 0.6.0
*/ */
public class CompressionException extends JwtException { public class CompressionException extends IOException {
/**
* Creates a new instance with the specified explanation message.
*
* @param message the message explaining why the exception is thrown.
*/
public CompressionException(String message) { public CompressionException(String message) {
super(message); super(message);
} }
/**
* Creates a new instance with the specified explanation message and underlying cause.
*
* @param message the message explaining why the exception is thrown.
* @param cause the underlying cause that resulted in this exception being thrown.
*/
public CompressionException(String message, Throwable cause) { public CompressionException(String message, Throwable cause) {
super(message, cause); super(message, cause);
} }

View File

@ -0,0 +1,39 @@
/*
* Copyright (C) 2021 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;
import io.jsonwebtoken.lang.Builder;
import io.jsonwebtoken.lang.MapMutator;
import io.jsonwebtoken.security.X509Builder;
/**
* A {@link Builder} that dynamically determines the type of {@link Header} to create based on builder state.
* <ul>
* <li>If only standard {@link Header} properties have been set (that is, no
* {@link JwsHeader} or {@link JweHeader} properties have been set), an {@link UnprotectedHeader} will be created.</li>
* <li>If any {@link ProtectedHeader} properties have been set (but no {@link JweHeader} properties), a
* {@link JwsHeader} will be created.</li>
* <li>If any {@link JweHeader} properties have been set, a {@link JweHeader} will be created.</li>
* </ul>
*
* @since JJWT_RELEASE_VERSION
*/
public interface DynamicHeaderBuilder extends
MapMutator<String, Object, DynamicHeaderBuilder>,
X509Builder<DynamicHeaderBuilder>,
JweHeaderMutator<DynamicHeaderBuilder>,
Builder<Header<?>> {
}

View File

@ -22,18 +22,27 @@ package io.jsonwebtoken;
*/ */
public class ExpiredJwtException extends ClaimJwtException { public class ExpiredJwtException extends ClaimJwtException {
public ExpiredJwtException(Header header, Claims claims, String message) { /**
* Creates a new instance with the specified header, claims, and explanation message.
*
* @param header jwt header
* @param claims jwt claims (body)
* @param message the message explaining why the exception is thrown.
*/
public ExpiredJwtException(Header<?> header, Claims claims, String message) {
super(header, claims, message); super(header, claims, message);
} }
/** /**
* @param header jwt header * Creates a new instance with the specified header, claims, explanation message and underlying cause.
* @param claims jwt claims (body) *
* @param message exception message * @param message the message explaining why the exception is thrown.
* @param cause cause * @param cause the underlying cause that resulted in this exception being thrown.
* @param header jwt header
* @param claims jwt claims (body)
* @since 0.5 * @since 0.5
*/ */
public ExpiredJwtException(Header header, Claims claims, String message, Throwable cause) { public ExpiredJwtException(Header<?> header, Claims claims, String message, Throwable cause) {
super(header, claims, message, cause); super(header, claims, message, cause);
} }
} }

View File

@ -32,103 +32,122 @@ import java.util.Map;
* *
* <h2>Creation</h2> * <h2>Creation</h2>
* *
* <p>It is easiest to create a {@code Header} instance by calling one of the * <p>It is easiest to create a {@code Header} instance by using {@link Jwts#header()}.</p>
* {@link Jwts#header() JWTs.header()} factory methods.</p>
* *
* @since 0.1 * @since 0.1
*/ */
public interface Header<T extends Header<T>> extends Map<String,Object> { public interface Header<T extends Header<T>> extends Map<String, Object>, HeaderMutator<T> {
/** JWT {@code Type} (typ) value: <code>"JWT"</code> */
public static final String JWT_TYPE = "JWT";
/** JWT {@code Type} header parameter name: <code>"typ"</code> */
public static final String TYPE = "typ";
/** JWT {@code Content Type} header parameter name: <code>"cty"</code> */
public static final String CONTENT_TYPE = "cty";
/** JWT {@code Compression Algorithm} header parameter name: <code>"zip"</code> */
public static final String COMPRESSION_ALGORITHM = "zip";
/** JJWT legacy/deprecated compression algorithm header parameter name: <code>"calg"</code>
* @deprecated use {@link #COMPRESSION_ALGORITHM} instead. */
@Deprecated
public static final String DEPRECATED_COMPRESSION_ALGORITHM = "calg";
/** /**
* Returns the <a href="https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-25#section-5.1"> * JWT {@code Type} (typ) value: <code>"JWT"</code>
* <code>typ</code></a> (type) header value or {@code null} if not present. *
* @deprecated since JJWT_RELEASE_VERSION - this constant is never used within the JJWT codebase.
*/
@Deprecated
String JWT_TYPE = "JWT";
/**
* JWT {@code Type} header parameter name: <code>"typ"</code>
*/
String TYPE = "typ";
/**
* JWT {@code Content Type} header parameter name: <code>"cty"</code>
*/
String CONTENT_TYPE = "cty";
/**
* JWT {@code Algorithm} header parameter name: <code>"alg"</code>.
*
* @see <a href="https://tools.ietf.org/html/rfc7515#section-4.1.1">JWS Algorithm Header</a>
* @see <a href="https://tools.ietf.org/html/rfc7516#section-4.1.1">JWE Algorithm Header</a>
*/
String ALGORITHM = "alg";
/**
* JWT {@code Compression Algorithm} header parameter name: <code>"zip"</code>
*/
String COMPRESSION_ALGORITHM = "zip";
/**
* JJWT legacy/deprecated compression algorithm header parameter name: <code>"calg"</code>
*
* @deprecated use {@link #COMPRESSION_ALGORITHM} instead.
*/
@SuppressWarnings("DeprecatedIsStillUsed")
@Deprecated
String DEPRECATED_COMPRESSION_ALGORITHM = "calg";
/**
* Returns the <a href="https://www.rfc-editor.org/rfc/rfc7519.html#section-5.1">
* <code>typ</code> (Type)</a> header value or {@code null} if not present.
* *
* @return the {@code typ} header value or {@code null} if not present. * @return the {@code typ} header value or {@code null} if not present.
*/ */
String getType(); String getType();
/** /**
* Sets the JWT <a href="https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-25#section-5.1"> * Returns the <a href="https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.10">
* <code>typ</code></a> (Type) header value. A {@code null} value will remove the property from the JSON map. * <code>cty</code> (Content Type)</a> header value or {@code null} if not present.
* *
* @param typ the JWT JOSE {@code typ} header value or {@code null} to remove the property from the JSON map. * <p>The <code>cty</code> (Content Type) Header Parameter is used by applications to declare the
* @return the {@code Header} instance for method chaining. * <a href="https://www.iana.org/assignments/media-types/media-types.xhtml">IANA MediaType</a> of the content
*/ * (the payload). This is intended for use by the application when more than
T setType(String typ); * one kind of object could be present in the Payload; the application can use this value to disambiguate among
* the different kinds of objects that might be present. It will typically not be used by applications when
/** * the kind of object is already known. This parameter is ignored by JWT implementations (like JJWT); any
* Returns the <a href="https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-25#section-5.2"> * processing of this parameter is performed by the JWS application. Use of this Header Parameter is OPTIONAL.</p>
* <code>cty</code></a> (Content Type) header value or {@code null} if not present.
* *
* <p>In the normal case where nested signing or encryption operations are not employed (i.e. a compact * <p>To keep messages compact in common situations, it is RECOMMENDED that producers omit an
* serialization JWT), the use of this header parameter is NOT RECOMMENDED. In the case that nested * <b><code>application/</code></b> prefix of a media type value in a {@code cty} Header Parameter when
* signing or encryption is employed, this Header Parameter MUST be present; in this case, the value MUST be * no other '<b>/</b>' appears in the media type value. A recipient using the media type value <em>MUST</em>
* {@code JWT}, to indicate that a Nested JWT is carried in this JWT. While media type names are not * treat it as if <b><code>application/</code></b> were prepended to any {@code cty} value not containing a
* case-sensitive, it is RECOMMENDED that {@code JWT} always be spelled using uppercase characters for * '<b>/</b>'. For instance, a {@code cty} value of <b><code>example</code></b> <em>SHOULD</em> be used to
* compatibility with legacy implementations. See * represent the <b><code>application/example</code></b> media type, whereas the media type
* <a href="https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-25#appendix-A.2">JWT Appendix A.2</a> for * <b><code>application/example;part=&quot;1/2&quot;</code></b> cannot be shortened to
* an example of a Nested JWT.</p> * <b><code>example;part=&quot;1/2&quot;</code></b>.</p>
* *
* @return the {@code typ} header parameter value or {@code null} if not present. * @return the {@code typ} header parameter value or {@code null} if not present.
*/ */
String getContentType(); String getContentType();
/** /**
* Sets the JWT <a href="https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-25#section-5.2"> * Returns the JWT {@code alg} (Algorithm) header value or {@code null} if not present.
* <code>cty</code></a> (Content Type) header parameter value. A {@code null} value will remove the property from
* the JSON map.
* *
* <p>In the normal case where nested signing or encryption operations are not employed (i.e. a compact * <ul>
* serialization JWT), the use of this header parameter is NOT RECOMMENDED. In the case that nested * <li>If the JWT is a Signed JWT (a JWS), the <a href="https://tools.ietf.org/html/rfc7515#section-4.1.1">
* signing or encryption is employed, this Header Parameter MUST be present; in this case, the value MUST be * <code>alg</code></a> (Algorithm) header parameter identifies the cryptographic algorithm used to secure the
* {@code JWT}, to indicate that a Nested JWT is carried in this JWT. While media type names are not * JWS. Consider using {@link Jwts#SIG}.{@link io.jsonwebtoken.lang.Registry#find(Object) find(id)}
* case-sensitive, it is RECOMMENDED that {@code JWT} always be spelled using uppercase characters for * to convert this string value to a type-safe {@code SecureDigestAlgorithm} instance.</li>
* compatibility with legacy implementations. See * <li>If the JWT is an Encrypted JWT (a JWE), the
* <a href="https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-25#appendix-A.2">JWT Appendix A.2</a> for * <a href="https://tools.ietf.org/html/rfc7516#section-4.1.1"><code>alg</code></a> (Algorithm) header parameter
* an example of a Nested JWT.</p> * identifies the cryptographic key management algorithm used to encrypt or determine the value of the Content
* Encryption Key (CEK). The encrypted content is not usable if the <code>alg</code> value does not represent a
* supported algorithm, or if the recipient does not have a key that can be used with that algorithm. Consider
* using {@link Jwts#KEY}.{@link io.jsonwebtoken.lang.Registry#find(Object) find(id)} to convert this string value
* to a type-safe {@link io.jsonwebtoken.security.KeyAlgorithm KeyAlgorithm} instance.</li>
* </ul>
* *
* @param cty the JWT JOSE {@code cty} header value or {@code null} to remove the property from the JSON map. * @return the {@code alg} header value or {@code null} if not present. This will always be
* @return the {@code Header} instance for method chaining. * {@code non-null} on validly constructed JWT instances, but could be {@code null} during construction.
* @since JJWT_RELEASE_VERSION
*/ */
T setContentType(String cty); String getAlgorithm();
/** /**
* Returns the JWT <code>zip</code> (Compression Algorithm) header value or {@code null} if not present. * Returns the JWT <a href="https://tools.ietf.org/html/rfc7516#section-4.1.3"><code>zip</code></a>
* (Compression Algorithm) header parameter value or {@code null} if not present.
*
* <p><b>Compatibility Note</b></p>
*
* <p>While the JWT family of specifications only defines the <code>zip</code> header in the JWE
* (JSON Web Encryption) specification, JJWT will also support compression for JWS as well if you choose to use it.
* However, be aware that <b>if you use compression when creating a JWS token, other libraries may not be able to
* parse the JWS</b>. However, compression when creating JWE tokens should be universally accepted for any library
* that supports JWE.</p>
* *
* @return the {@code zip} header parameter value or {@code null} if not present. * @return the {@code zip} header parameter value or {@code null} if not present.
* @since 0.6.0 * @since 0.6.0
*/ */
String getCompressionAlgorithm(); String getCompressionAlgorithm();
/**
* Sets the JWT <code>zip</code> (Compression Algorithm) header parameter value. A {@code null} value will remove
* the property from the JSON map.
*
* <p>The compression algorithm is NOT part of the <a href="https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-25">JWT specification</a>
* and must be used carefully since, is not expected that other libraries (including previous versions of this one)
* be able to deserialize a compressed JTW body correctly. </p>
*
* @param zip the JWT compression algorithm {@code zip} value or {@code null} to remove the property from the JSON map.
* @return the {@code Header} instance for method chaining.
* @since 0.6.0
*/
T setCompressionAlgorithm(String zip);
} }

View File

@ -0,0 +1,100 @@
/*
* Copyright (C) 2021 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;
/**
* Mutation (modifications) to a {@link Header Header} instance.
*
* @param <T> the mutator subtype, for method chaining
* @since JJWT_RELEASE_VERSION
*/
public interface HeaderMutator<T extends HeaderMutator<T>> {
/**
* Sets the JWT <a href="https://www.rfc-editor.org/rfc/rfc7519.html#section-5.1">
* <code>typ</code> (Type)</a> header value. A {@code null} value will remove the property from the JSON map.
*
* @param typ the JWT JOSE {@code typ} header value or {@code null} to remove the property from the JSON map.
* @return the {@code Header} instance for method chaining.
*/
T setType(String typ);
/**
* Sets the JWT <a href="https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.10">
* <code>cty</code> (Content Type)</a> header parameter value. A {@code null} value will remove the property from
* the JSON map.
*
* <p>The <code>cty</code> (Content Type) Header Parameter is used by applications to declare the
* <a href="https://www.iana.org/assignments/media-types/media-types.xhtml">IANA MediaType</a> of the content
* (the payload). This is intended for use by the application when more than
* one kind of object could be present in the Payload; the application can use this value to disambiguate among
* the different kinds of objects that might be present. It will typically not be used by applications when
* the kind of object is already known. This parameter is ignored by JWT implementations (like JJWT); any
* processing of this parameter is performed by the JWS application. Use of this Header Parameter is OPTIONAL.</p>
*
* <p>To keep messages compact in common situations, it is RECOMMENDED that producers omit an
* <b><code>application/</code></b> prefix of a media type value in a {@code cty} Header Parameter when
* no other '<b>/</b>' appears in the media type value. A recipient using the media type value <em>MUST</em>
* treat it as if <b><code>application/</code></b> were prepended to any {@code cty} value not containing a
* '<b>/</b>'. For instance, a {@code cty} value of <b><code>example</code></b> <em>SHOULD</em> be used to
* represent the <b><code>application/example</code></b> media type, whereas the media type
* <b><code>application/example;part=&quot;1/2&quot;</code></b> cannot be shortened to
* <b><code>example;part=&quot;1/2&quot;</code></b>.</p>
*
* @param cty the JWT JOSE {@code cty} header value or {@code null} to remove the property from the JSON map.
* @return the {@code Header} instance for method chaining.
*/
T setContentType(String cty);
/**
* Sets the JWT {@code alg} (Algorithm) header value. A {@code null} value will remove the property
* from the JSON map.
* <ul>
* <li>If the JWT is a Signed JWT (a JWS), the
* <a href="https://tools.ietf.org/html/rfc7515#section-4.1.1">{@code alg}</a> (Algorithm) header
* parameter identifies the cryptographic algorithm used to secure the JWS.</li>
* <li>If the JWT is an Encrypted JWT (a JWE), the
* <a href="https://tools.ietf.org/html/rfc7516#section-4.1.1"><code>alg</code></a> (Algorithm) header parameter
* identifies the cryptographic key management algorithm used to encrypt or determine the value of the Content
* Encryption Key (CEK). The encrypted content is not usable if the <code>alg</code> value does not represent a
* supported algorithm, or if the recipient does not have a key that can be used with that algorithm.</li>
* </ul>
*
* @param alg the {@code alg} header value
* @return this header for method chaining
* @since JJWT_RELEASE_VERSION
*/
T setAlgorithm(String alg);
/**
* Sets the JWT <a href="https://tools.ietf.org/html/rfc7516#section-4.1.3"><code>zip</code></a>
* (Compression Algorithm) header parameter value. A {@code null} value will remove
* the property from the JSON map.
*
* <p><b>Compatibility Note</b></p>
*
* <p>While the JWT family of specifications only defines the <code>zip</code> header in the JWE
* (JSON Web Encryption) specification, JJWT will also support compression for JWS as well if you choose to use it.
* However, be aware that <b>if you use compression when creating a JWS token, other libraries may not be able to
* parse the JWS</b>. However, Compression when creating JWE tokens should be universally accepted for any library
* that supports JWE.</p>
*
* @param zip the JWT compression algorithm {@code zip} value or {@code null} to remove the property from the JSON map.
* @return the {@code Header} instance for method chaining.
* @since 0.6.0
*/
T setCompressionAlgorithm(String zip);
}

View File

@ -0,0 +1,70 @@
/*
* Copyright (C) 2021 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;
/**
* An object that may be uniquely identified by an {@link #getId() id} relative to other instances of the same type.
*
* <p>All JWT concepts that have a
* <a href="https://www.rfc-editor.org/rfc/rfc7518.html">JWA</a> identifier value implement this interface.
* Specifically, there are four JWT concepts that are {@code Identifiable}. The following table indicates how
* their {@link #getId() id} values are used.</p>
*
* <table>
* <caption>JWA Identifiable Concepts</caption>
* <thead>
* <tr>
* <th>JJWT Type</th>
* <th>How {@link #getId()} is Used</th>
* </tr>
* </thead>
* <tbody>
* <tr>
* <td>{@link io.jsonwebtoken.security.SignatureAlgorithm SignatureAlgorithm}</td>
* <td>JWS protected header's
* <a href="https://www.rfc-editor.org/rfc/rfc7518.html#section-3.1">{@code alg} (Algorithm)</a> parameter value.</td>
* </tr>
* <tr>
* <td>{@link io.jsonwebtoken.security.KeyAlgorithm KeyAlgorithm}</td>
* <td>JWE protected header's
* <a href="https://www.rfc-editor.org/rfc/rfc7518.html#section-4.1">{@code alg} (Key Management Algorithm)</a>
* parameter value.</td>
* </tr>
* <tr>
* <td>{@link io.jsonwebtoken.security.AeadAlgorithm AeadAlgorithm}</td>
* <td>JWE protected header's
* <a href="https://www.rfc-editor.org/rfc/rfc7518.html#section-5.1">{@code enc} (Encryption Algorithm)</a>
* parameter value.</td>
* </tr>
* <tr>
* <td>{@link io.jsonwebtoken.security.Jwk Jwk}</td>
* <td>JWK's <a href="https://www.rfc-editor.org/rfc/rfc7517.html#section-4.5">{@code kid} (Key ID)</a>
* parameter value.</td>
* </tr>
* </tbody>
* </table>
*
* @since JJWT_RELEASE_VERSION
*/
public interface Identifiable {
/**
* Returns the unique string identifier of the associated object.
*
* @return the unique string identifier of the associated object.
*/
String getId();
}

View File

@ -23,11 +23,30 @@ package io.jsonwebtoken;
*/ */
public class IncorrectClaimException extends InvalidClaimException { public class IncorrectClaimException extends InvalidClaimException {
public IncorrectClaimException(Header header, Claims claims, String message) { /**
super(header, claims, message); * Creates a new instance with the specified header, claims and explanation message.
*
* @param header the header inspected
* @param claims the claims with the incorrect claim value
* @param claimName the name of the claim that could not be validated
* @param claimValue the value of the claim that could not be validated
* @param message the exception message
*/
public IncorrectClaimException(Header<?> header, Claims claims, String claimName, Object claimValue, String message) {
super(header, claims, claimName, claimValue, message);
} }
public IncorrectClaimException(Header header, Claims claims, String message, Throwable cause) { /**
super(header, claims, message, cause); * Creates a new instance with the specified header, claims, explanation message and underlying cause.
*
* @param header the header inspected
* @param claims the claims with the incorrect claim value
* @param claimName the name of the claim that could not be validated
* @param claimValue the value of the claim that could not be validated
* @param message the exception message
* @param cause the underlying cause that resulted in this exception being thrown
*/
public IncorrectClaimException(Header<?> header, Claims claims, String claimName, Object claimValue, String message, Throwable cause) {
super(header, claims, claimName, claimValue, message, cause);
} }
} }

View File

@ -21,35 +21,66 @@ package io.jsonwebtoken;
* *
* @see IncorrectClaimException * @see IncorrectClaimException
* @see MissingClaimException * @see MissingClaimException
*
* @since 0.6 * @since 0.6
*/ */
public class InvalidClaimException extends ClaimJwtException { public class InvalidClaimException extends ClaimJwtException {
private String claimName; /**
private Object claimValue; * The name of the invalid claim.
*/
private final String claimName;
protected InvalidClaimException(Header header, Claims claims, String message) { /**
* The claim value that could not be validated.
*/
private final Object claimValue;
/**
* Creates a new instance with the specified header, claims and explanation message.
*
* @param header the header inspected
* @param claims the claims obtained
* @param claimName the name of the claim that could not be validated
* @param claimValue the value of the claim that could not be validated
* @param message the exception message
*/
protected InvalidClaimException(Header<?> header, Claims claims, String claimName, Object claimValue, String message) {
super(header, claims, message); super(header, claims, message);
this.claimName = claimName;
this.claimValue = claimValue;
} }
protected InvalidClaimException(Header header, Claims claims, String message, Throwable cause) { /**
* Creates a new instance with the specified header, claims, explanation message and underlying cause.
*
* @param header the header inspected
* @param claims the claims obtained
* @param claimName the name of the claim that could not be validated
* @param claimValue the value of the claim that could not be validated
* @param message the exception message
* @param cause the underlying cause that resulted in this exception being thrown
*/
protected InvalidClaimException(Header<?> header, Claims claims, String claimName, Object claimValue, String message, Throwable cause) {
super(header, claims, message, cause); super(header, claims, message, cause);
this.claimName = claimName;
this.claimValue = claimValue;
} }
/**
* Returns the name of the invalid claim.
*
* @return the name of the invalid claim.
*/
public String getClaimName() { public String getClaimName() {
return claimName; return claimName;
} }
public void setClaimName(String claimName) { /**
this.claimName = claimName; * Returns the claim value that could not be validated.
} *
* @return the claim value that could not be validated.
*/
public Object getClaimValue() { public Object getClaimValue() {
return claimValue; return claimValue;
} }
public void setClaimValue(Object claimValue) {
this.claimValue = claimValue;
}
} }

View File

@ -0,0 +1,42 @@
/*
* Copyright (C) 2021 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;
/**
* An encrypted JWT, called a &quot;JWE&quot;, per the
* <a href="https://www.rfc-editor.org/rfc/rfc7516.html">JWE (RFC 7516) Specification</a>.
*
* @param <B> payload type, either {@link Claims} or {@code byte[]} content.
* @since JJWT_RELEASE_VERSION
*/
public interface Jwe<B> extends Jwt<JweHeader, B> {
/**
* Returns the Initialization Vector used during JWE encryption and decryption.
*
* @return the Initialization Vector used during JWE encryption and decryption.
*/
byte[] getInitializationVector();
/**
* Returns the Additional Authenticated Data authentication Tag used for JWE header
* authenticity and integrity verification.
*
* @return the Additional Authenticated Data authentication Tag used for JWE header
* authenticity and integrity verification.
*/
byte[] getAadTag();
}

View File

@ -0,0 +1,171 @@
/*
* Copyright (C) 2021 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;
import io.jsonwebtoken.security.AeadAlgorithm;
import io.jsonwebtoken.security.KeyAlgorithm;
import io.jsonwebtoken.security.PublicJwk;
import io.jsonwebtoken.security.StandardKeyAlgorithms;
import javax.crypto.SecretKey;
import java.security.Key;
/**
* A <a href="https://tools.ietf.org/html/rfc7516">JWE</a> header.
*
* @since JJWT_RELEASE_VERSION
*/
public interface JweHeader extends ProtectedHeader<JweHeader>, JweHeaderMutator<JweHeader> {
/**
* Returns the JWE <a href="https://tools.ietf.org/html/rfc7516#section-4.1.2">{@code enc} (Encryption
* Algorithm)</a> header value or {@code null} if not present.
*
* <p>The JWE {@code enc} (encryption algorithm) Header Parameter identifies the content encryption algorithm
* used to perform authenticated encryption on the plaintext to produce the ciphertext and the JWE
* {@code Authentication Tag}.</p>
*
* <p>Note that there is no corresponding 'setter' method for this 'getter' because JJWT users set this value by
* supplying an {@link AeadAlgorithm} to a {@link JwtBuilder} via one of its
* {@link JwtBuilder#encryptWith(SecretKey, AeadAlgorithm) encryptWith(SecretKey, AeadAlgorithm)} or
* {@link JwtBuilder#encryptWith(Key, KeyAlgorithm, AeadAlgorithm) encryptWith(Key, KeyAlgorithm, AeadAlgorithm)}
* methods. JJWT will then set this {@code enc} header value automatically to the {@code AeadAlgorithm}'s
* {@link AeadAlgorithm#getId() getId()} value during encryption.</p>
*
* @return the JWE {@code enc} (Encryption Algorithm) header value or {@code null} if not present. This will
* always be {@code non-null} on validly-constructed JWE instances, but could be {@code null} during construction.
* @see JwtBuilder#encryptWith(SecretKey, AeadAlgorithm)
* @see JwtBuilder#encryptWith(Key, KeyAlgorithm, AeadAlgorithm)
*/
String getEncryptionAlgorithm();
/**
* Returns the <a href="https://www.rfc-editor.org/rfc/rfc7518.html#section-4.6.1.1">{@code epk} (Ephemeral
* Public Key)</a> header value created by the JWE originator for use with key agreement algorithms, or
* {@code null} if not present.
*
* <p>Note that there is no corresponding 'setter' method for this 'getter' because JJWT users set this value by
* supplying an ECDH-ES {@link KeyAlgorithm} to a {@link JwtBuilder} via its
* {@link JwtBuilder#encryptWith(Key, KeyAlgorithm, AeadAlgorithm) encryptWith(Key, KeyAlgorithm, AeadAlgorithm)}
* method. The ECDH-ES {@code KeyAlgorithm} implementation will then set this {@code epk} header value
* automatically when producing the encryption key.</p>
*
* @return the <a href="https://www.rfc-editor.org/rfc/rfc7518.html#section-4.6.1.1">{@code epk} (Ephemeral
* Public Key)</a> header value created by the JWE originator for use with key agreement algorithms, or
* {@code null} if not present.
* @see Jwts#KEY
* @see StandardKeyAlgorithms#ECDH_ES
* @see StandardKeyAlgorithms#ECDH_ES_A128KW
* @see StandardKeyAlgorithms#ECDH_ES_A192KW
* @see StandardKeyAlgorithms#ECDH_ES_A256KW
*/
PublicJwk<?> getEphemeralPublicKey();
/**
* Returns any information about the JWE producer for use with key agreement algorithms, or {@code null} if not
* present.
*
* @return any information about the JWE producer for use with key agreement algorithms, or {@code null} if not
* present.
* @see <a href="https://www.rfc-editor.org/rfc/rfc7518.html#section-4.6.1.2">JWE <code>apu</code> (Agreement PartyUInfo) Header Parameter</a>
* @see StandardKeyAlgorithms#ECDH_ES
* @see StandardKeyAlgorithms#ECDH_ES_A128KW
* @see StandardKeyAlgorithms#ECDH_ES_A192KW
* @see StandardKeyAlgorithms#ECDH_ES_A256KW
*/
byte[] getAgreementPartyUInfo();
/**
* Returns any information about the JWE recipient for use with key agreement algorithms, or {@code null} if not
* present.
*
* @return any information about the JWE recipient for use with key agreement algorithms, or {@code null} if not
* present.
* @see <a href="https://www.rfc-editor.org/rfc/rfc7518.html#section-4.6.1.3">JWE <code>apv</code> (Agreement PartyVInfo) Header Parameter</a>
* @see StandardKeyAlgorithms#ECDH_ES
* @see StandardKeyAlgorithms#ECDH_ES_A128KW
* @see StandardKeyAlgorithms#ECDH_ES_A192KW
* @see StandardKeyAlgorithms#ECDH_ES_A256KW
*/
byte[] getAgreementPartyVInfo();
/**
* Returns the 96-bit <a href="https://www.rfc-editor.org/rfc/rfc7518.html#section-4.7.1.1">&quot;iv&quot;
* (Initialization Vector)</a> generated during key encryption, or {@code null} if not present.
* Set by AES GCM {@link io.jsonwebtoken.security.KeyAlgorithm KeyAlgorithm} implementations.
*
* <p>Note that there is no corresponding 'setter' method for this 'getter' because JJWT users set this value by
* supplying an AES GCM Wrap {@link KeyAlgorithm} to a {@link JwtBuilder} via its
* {@link JwtBuilder#encryptWith(Key, KeyAlgorithm, AeadAlgorithm) encryptWith(Key, KeyAlgorithm, AeadAlgorithm)}
* method. The AES GCM Wrap {@code KeyAlgorithm} implementation will then set this {@code iv} header value
* automatically when producing the encryption key.</p>
*
* @return the 96-bit initialization vector generated during key encryption, or {@code null} if not present.
* @see StandardKeyAlgorithms#A128GCMKW
* @see StandardKeyAlgorithms#A192GCMKW
* @see StandardKeyAlgorithms#A256GCMKW
*/
byte[] getInitializationVector();
/**
* Returns the 128-bit <a href="https://www.rfc-editor.org/rfc/rfc7518.html#section-4.7.1.2">&quot;tag&quot;
* (Authentication Tag)</a> resulting from key encryption, or {@code null} if not present.
*
* <p>Note that there is no corresponding 'setter' method for this 'getter' because JJWT users set this value by
* supplying an AES GCM Wrap {@link KeyAlgorithm} to a {@link JwtBuilder} via its
* {@link JwtBuilder#encryptWith(Key, KeyAlgorithm, AeadAlgorithm) encryptWith(Key, KeyAlgorithm, AeadAlgorithm)}
* method. The AES GCM Wrap {@code KeyAlgorithm} implementation will then set this {@code tag} header value
* automatically when producing the encryption key.</p>
*
* @return the 128-bit authentication tag resulting from key encryption, or {@code null} if not present.
* @see StandardKeyAlgorithms#A128GCMKW
* @see StandardKeyAlgorithms#A192GCMKW
* @see StandardKeyAlgorithms#A256GCMKW
*/
byte[] getAuthenticationTag();
/**
* Returns the number of PBKDF2 iterations necessary to derive the key used during JWE encryption, or {@code null}
* if not present. Used with password-based {@link io.jsonwebtoken.security.KeyAlgorithm KeyAlgorithm}s.
*
* @return the number of PBKDF2 iterations necessary to derive the key used during JWE encryption, or {@code null}
* if not present.
* @see <a href="https://www.rfc-editor.org/rfc/rfc7518.html#section-4.8.1.2">JWE <code>p2c</code> (PBES2 Count) Header Parameter</a>
* @see StandardKeyAlgorithms#PBES2_HS256_A128KW
* @see StandardKeyAlgorithms#PBES2_HS384_A192KW
* @see StandardKeyAlgorithms#PBES2_HS512_A256KW
*/
Integer getPbes2Count();
/**
* Returns the PBKDF2 {@code Salt Input} value necessary to derive the key used during JWE encryption, or
* {@code null} if not present.
*
* <p>Note that there is no corresponding 'setter' method for this 'getter' because JJWT users set this value by
* supplying a password-based {@link KeyAlgorithm} to a {@link JwtBuilder} via its
* {@link JwtBuilder#encryptWith(Key, KeyAlgorithm, AeadAlgorithm) encryptWith(Key, KeyAlgorithm, AeadAlgorithm)}
* method. The password-based {@code KeyAlgorithm} implementation will then set this {@code p2s} header value
* automatically when producing the encryption key.</p>
*
* @return the PBKDF2 {@code Salt Input} value necessary to derive the key used during JWE encryption, or
* {@code null} if not present.
* @see <a href="https://www.rfc-editor.org/rfc/rfc7518.html#section-4.8.1.1">JWE <code>p2s</code> (PBES2 Salt Input) Header Parameter</a>
* @see StandardKeyAlgorithms#PBES2_HS256_A128KW
* @see StandardKeyAlgorithms#PBES2_HS384_A192KW
* @see StandardKeyAlgorithms#PBES2_HS512_A256KW
*/
byte[] getPbes2Salt();
}

View File

@ -0,0 +1,146 @@
/*
* Copyright (C) 2021 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;
import io.jsonwebtoken.security.KeyAlgorithm;
import io.jsonwebtoken.security.StandardKeyAlgorithms;
/**
* Mutation (modifications) to a {@link JweHeader} instance.
*
* @param <T> the mutator subtype, for method chaining
* @since JJWT_RELEASE_VERSION
*/
public interface JweHeaderMutator<T extends JweHeaderMutator<T>> extends ProtectedHeaderMutator<T> {
// /**
// * Sets the JWE <a href="https://tools.ietf.org/html/rfc7516#section-4.1.2">{@code enc} (Encryption
// * Algorithm)</a> header value. A {@code null} value will remove the property from the JSON map.
// *
// * <p>This should almost never be set by JJWT users directly - JJWT will always set this value to the value
// * returned by {@link AeadAlgorithm#getId()} when performing encryption, overwriting any potential previous
// * value.</p>
// *
// * @param enc the encryption algorithm identifier obtained from {@link AeadAlgorithm#getId()}.
// * @return this header for method chaining
// */
// @SuppressWarnings("UnusedReturnValue")
// JweHeader setEncryptionAlgorithm(String enc);
/**
* Sets any information about the JWE producer for use with key agreement algorithms. A {@code null} or empty value
* removes the property from the JSON map.
*
* @param info information about the JWE producer to use with key agreement algorithms.
* @return the header for method chaining.
* @see <a href="https://www.rfc-editor.org/rfc/rfc7518.html#section-4.6.1.2">JWE <code>apu</code> (Agreement PartyUInfo) Header Parameter</a>
* @see StandardKeyAlgorithms#ECDH_ES
* @see StandardKeyAlgorithms#ECDH_ES_A128KW
* @see StandardKeyAlgorithms#ECDH_ES_A192KW
* @see StandardKeyAlgorithms#ECDH_ES_A256KW
*/
T setAgreementPartyUInfo(byte[] info);
/**
* Sets any information about the JWE producer for use with key agreement algorithms. A {@code null} value removes
* the property from the JSON map.
*
* <p>If not {@code null}, this is a convenience method that calls the equivalent of the following:</p>
* <blockquote><pre>
* {@link #setAgreementPartyUInfo(byte[]) setAgreementPartyUInfo}(info.getBytes(StandardCharsets.UTF_8))</pre></blockquote>
*
* @param info information about the JWE producer to use with key agreement algorithms.
* @return the header for method chaining.
* @see <a href="https://www.rfc-editor.org/rfc/rfc7518.html#section-4.6.1.2">JWE <code>apu</code> (Agreement PartyUInfo) Header Parameter</a>
* @see StandardKeyAlgorithms#ECDH_ES
* @see StandardKeyAlgorithms#ECDH_ES_A128KW
* @see StandardKeyAlgorithms#ECDH_ES_A192KW
* @see StandardKeyAlgorithms#ECDH_ES_A256KW
*/
T setAgreementPartyUInfo(String info);
/**
* Sets any information about the JWE recipient for use with key agreement algorithms. A {@code null} value removes
* the property from the JSON map.
*
* @param info information about the JWE recipient to use with key agreement algorithms.
* @return the header for method chaining.
* @see <a href="https://www.rfc-editor.org/rfc/rfc7518.html#section-4.6.1.3">JWE <code>apv</code> (Agreement PartyVInfo) Header Parameter</a>
* @see StandardKeyAlgorithms#ECDH_ES
* @see StandardKeyAlgorithms#ECDH_ES_A128KW
* @see StandardKeyAlgorithms#ECDH_ES_A192KW
* @see StandardKeyAlgorithms#ECDH_ES_A256KW
*/
T setAgreementPartyVInfo(byte[] info);
/**
* Sets any information about the JWE recipient for use with key agreement algorithms. A {@code null} value removes
* the property from the JSON map.
*
* <p>If not {@code null}, this is a convenience method that calls the equivalent of the following:</p>
* <blockquote><pre>
* {@link #setAgreementPartyVInfo(byte[]) setAgreementPartVUInfo}(info.getBytes(StandardCharsets.UTF_8))</pre></blockquote>
*
* @param info information about the JWE recipient to use with key agreement algorithms.
* @return the header for method chaining.
* @see <a href="https://www.rfc-editor.org/rfc/rfc7518.html#section-4.6.1.3">JWE <code>apv</code> (Agreement PartyVInfo) Header Parameter</a>
* @see StandardKeyAlgorithms#ECDH_ES
* @see StandardKeyAlgorithms#ECDH_ES_A128KW
* @see StandardKeyAlgorithms#ECDH_ES_A192KW
* @see StandardKeyAlgorithms#ECDH_ES_A256KW
*/
T setAgreementPartyVInfo(String info);
/**
* Sets the number of PBKDF2 iterations necessary to derive the key used during JWE encryption. If this value
* is not set when a password-based {@link KeyAlgorithm} is used, JJWT will automatically choose a suitable
* number of iterations based on
* <a href="https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2">OWASP PBKDF2 Iteration Recommendations</a>.
*
* <p><b>Minimum Count</b></p>
*
* <p>{@code IllegalArgumentException} will be thrown during encryption if a specified {@code count} is
* less than 1000 (one thousand), which is the
* <a href="https://www.rfc-editor.org/rfc/rfc7518.html#section-4.8.1.2">minimum number recommended</a> by the
* JWA specification. Anything less is susceptible to security attacks so the default PBKDF2
* {@code KeyAlgorithm} implementations reject such values.</p>
*
* @param count the number of PBKDF2 iterations necessary to derive the key used during JWE encryption, must be
* greater than or equal to 1000 (one thousand).
* @return the header for method chaining
* @see <a href="https://www.rfc-editor.org/rfc/rfc7518.html#section-4.8.1.2">JWE <code>p2c</code> (PBES2 Count) Header Parameter</a>
* @see StandardKeyAlgorithms#PBES2_HS256_A128KW
* @see StandardKeyAlgorithms#PBES2_HS384_A192KW
* @see StandardKeyAlgorithms#PBES2_HS512_A256KW
* @see <a href="https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2">OWASP PBKDF2 Iteration Recommendations</a>
*/
T setPbes2Count(int count);
// /**
// * Sets the PBKDF2 {@code Salt Input} value necessary to derive the key used during JWE encryption. This should
// * almost never be used by JJWT users directly - it should instead be automatically generated and set within a
// * PBKDF2-based {@link io.jsonwebtoken.security.KeyAlgorithm KeyAlgorithm} implementation.
// *
// * @param salt the PBKDF2 {@code Salt Input} value necessary to derive the key used during JWE encryption.
// * @return the header for method chaining
// * @see <a href="https://www.rfc-editor.org/rfc/rfc7518.html#section-4.8.1.1">JWE <code>p2s</code> (PBES2 Salt Input) Header Parameter</a>
// * @see Jwts.KEY#PBES2_HS256_A128KW
// * @see Jwts.KEY#PBES2_HS384_A192KW
// * @see Jwts.KEY#PBES2_HS512_A256KW
// */
// JweHeader setPbes2Salt(byte[] salt);
}

View File

@ -19,10 +19,14 @@ package io.jsonwebtoken;
* An expanded (not compact/serialized) Signed JSON Web Token. * An expanded (not compact/serialized) Signed JSON Web Token.
* *
* @param <B> the type of the JWS body contents, either a String or a {@link Claims} instance. * @param <B> the type of the JWS body contents, either a String or a {@link Claims} instance.
*
* @since 0.1 * @since 0.1
*/ */
public interface Jws<B> extends Jwt<JwsHeader,B> { public interface Jws<B> extends Jwt<JwsHeader, B> {
String getSignature(); /**
* Returns the verified JWS signature as a Base64Url string.
*
* @return the verified JWS signature as a Base64Url string.
*/
String getSignature(); //TODO for 1.0: return a byte[]
} }

View File

@ -16,92 +16,54 @@
package io.jsonwebtoken; package io.jsonwebtoken;
/** /**
* A <a href="https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-31">JWS</a> header. * A <a href="https://tools.ietf.org/html/rfc7515">JWS</a> header.
* *
* @param <T> header type
* @since 0.1 * @since 0.1
*/ */
public interface JwsHeader<T extends JwsHeader<T>> extends Header<T> { public interface JwsHeader extends ProtectedHeader<JwsHeader> {
/** JWS {@code Algorithm} header parameter name: <code>"alg"</code> */
public static final String ALGORITHM = "alg";
/** JWS {@code JWT Set URL} header parameter name: <code>"jku"</code> */
public static final String JWK_SET_URL = "jku";
/** JWS {@code JSON Web Key} header parameter name: <code>"jwk"</code> */
public static final String JSON_WEB_KEY = "jwk";
/** JWS {@code Key ID} header parameter name: <code>"kid"</code> */
public static final String KEY_ID = "kid";
/** JWS {@code X.509 URL} header parameter name: <code>"x5u"</code> */
public static final String X509_URL = "x5u";
/** JWS {@code X.509 Certificate Chain} header parameter name: <code>"x5c"</code> */
public static final String X509_CERT_CHAIN = "x5c";
/** JWS {@code X.509 Certificate SHA-1 Thumbprint} header parameter name: <code>"x5t"</code> */
public static final String X509_CERT_SHA1_THUMBPRINT = "x5t";
/** JWS {@code X.509 Certificate SHA-256 Thumbprint} header parameter name: <code>"x5t#S256"</code> */
public static final String X509_CERT_SHA256_THUMBPRINT = "x5t#S256";
/** JWS {@code Critical} header parameter name: <code>"crit"</code> */
public static final String CRITICAL = "crit";
/** /**
* Returns the JWS <a href="https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-31#section-4.1.1"> * JWS <a href="https://tools.ietf.org/html/rfc7515#section-4.1.1">Algorithm Header</a> name: the string literal <b><code>alg</code></b>
* <code>alg</code></a> (algorithm) header value or {@code null} if not present.
*
* <p>The algorithm header parameter identifies the cryptographic algorithm used to secure the JWS. Consider
* using {@link io.jsonwebtoken.SignatureAlgorithm#forName(String) SignatureAlgorithm.forName} to convert this
* string value to a type-safe enum instance.</p>
*
* @return the JWS {@code alg} header value or {@code null} if not present. This will always be
* {@code non-null} on validly constructed JWS instances, but could be {@code null} during construction.
*/ */
String getAlgorithm(); String ALGORITHM = "alg";
/** /**
* Sets the JWT <a href="https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-31#section-4.1.1"> * JWS <a href="https://tools.ietf.org/html/rfc7515#section-4.1.2">JWK Set URL Header</a> name: the string literal <b><code>jku</code></b>
* <code>alg</code></a> (Algorithm) header value. A {@code null} value will remove the property from the JSON map.
*
* <p>The algorithm header parameter identifies the cryptographic algorithm used to secure the JWS. Consider
* using a type-safe {@link io.jsonwebtoken.SignatureAlgorithm SignatureAlgorithm} instance and using its
* {@link io.jsonwebtoken.SignatureAlgorithm#getValue() value} as the argument to this method.</p>
*
* @param alg the JWS {@code alg} header value or {@code null} to remove the property from the JSON map.
* @return the {@code Header} instance for method chaining.
*/ */
T setAlgorithm(String alg); String JWK_SET_URL = "jku";
/** /**
* Returns the JWS <a href="https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-31#section-4.1.4"> * JWS <a href="https://tools.ietf.org/html/rfc7515#section-4.1.3">JSON Web Key Header</a> name: the string literal <b><code>jwk</code></b>
* <code>kid</code></a> (Key ID) header value or {@code null} if not present.
*
* <p>The keyId header parameter is a hint indicating which key was used to secure the JWS. This parameter allows
* originators to explicitly signal a change of key to recipients. The structure of the keyId value is
* unspecified.</p>
*
* <p>When used with a JWK, the keyId value is used to match a JWK {@code keyId} parameter value.</p>
*
* @return the JWS {@code kid} header value or {@code null} if not present.
*/ */
String getKeyId(); String JSON_WEB_KEY = "jwk";
/** /**
* Sets the JWT <a href="https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-31#section-4.1.4"> * JWS <a href="https://tools.ietf.org/html/rfc7516#section-4.1.4">Key ID Header</a> name: the string literal <b><code>kid</code></b>
* <code>kid</code></a> (Key ID) header value. A {@code null} value will remove the property from the JSON map.
*
* <p>The keyId header parameter is a hint indicating which key was used to secure the JWS. This parameter allows
* originators to explicitly signal a change of key to recipients. The structure of the keyId value is
* unspecified.</p>
*
* <p>When used with a JWK, the keyId value is used to match a JWK {@code keyId} parameter value.</p>
*
* @param kid the JWS {@code kid} header value or {@code null} to remove the property from the JSON map.
* @return the {@code Header} instance for method chaining.
*/ */
T setKeyId(String kid); String KEY_ID = "kid";
/**
* JWS <a href="https://tools.ietf.org/html/rfc7516#section-4.1.5">X.509 URL Header</a> name: the string literal <b><code>x5u</code></b>
*/
String X509_URL = "x5u";
/**
* JWS <a href="https://tools.ietf.org/html/rfc7516#section-4.1.6">X.509 Certificate Chain Header</a> name: the string literal <b><code>x5c</code></b>
*/
String X509_CERT_CHAIN = "x5c";
/**
* JWS <a href="https://tools.ietf.org/html/rfc7516#section-4.1.7">X.509 Certificate SHA-1 Thumbprint Header</a> name: the string literal <b><code>x5t</code></b>
*/
String X509_CERT_SHA1_THUMBPRINT = "x5t";
/**
* JWS <a href="https://tools.ietf.org/html/rfc7516#section-4.1.8">X.509 Certificate SHA-256 Thumbprint Header</a> name: the string literal <b><code>x5t#S256</code></b>
*/
String X509_CERT_SHA256_THUMBPRINT = "x5t#S256";
/**
* JWS <a href="https://tools.ietf.org/html/rfc7516#section-4.1.11">Critical Header</a> name: the string literal <b><code>crit</code></b>
*/
String CRITICAL = "crit";
} }

View File

@ -18,11 +18,10 @@ package io.jsonwebtoken;
/** /**
* An expanded (not compact/serialized) JSON Web Token. * An expanded (not compact/serialized) JSON Web Token.
* *
* @param <B> the type of the JWT body contents, either a String or a {@link Claims} instance. * @param <P> the type of the JWT payload, either a byte array or a {@link Claims} instance.
*
* @since 0.1 * @since 0.1
*/ */
public interface Jwt<H extends Header, B> { public interface Jwt<H extends Header<H>, P> {
/** /**
* Returns the JWT {@link Header} or {@code null} if not present. * Returns the JWT {@link Header} or {@code null} if not present.
@ -32,9 +31,26 @@ public interface Jwt<H extends Header, B> {
H getHeader(); H getHeader();
/** /**
* Returns the JWT body, either a {@code String} or a {@code Claims} instance. * Returns the JWT payload, either a {@code byte[]} or a {@code Claims} instance. Use
* {@link #getPayload()} instead, as this method will be removed prior to the 1.0 release.
* *
* @return the JWT body, either a {@code String} or a {@code Claims} instance. * @return the JWT payload, either a {@code byte[]} or a {@code Claims} instance.
* @deprecated since JJWT_RELEASE_VERSION because it has been renamed to {@link #getPayload()}. 'Payload' (not
* body) is what the JWT specifications call this property, so it has been renamed to reflect the correct JWT
* nomenclature/taxonomy.
*/ */
B getBody(); @SuppressWarnings("DeprecatedIsStillUsed")
@Deprecated
P getBody(); // TODO: remove for 1.0
/**
* Returns the JWT payload, either a {@code byte[]} or a {@code Claims} instance. If the payload is a byte
* array, and <em>if</em> the JWT creator set the (optional) {@link Header#getContentType() contentType} header
* value, the application may inspect the {@code contentType} value to determine how to convert the byte array to
* the final content type as desired.
*
* @return the JWT payload, either a {@code byte[]} or a {@code Claims} instance.
* @since JJWT_RELEASE_VERSION
*/
P getPayload();
} }

View File

@ -19,21 +19,55 @@ import io.jsonwebtoken.io.Decoder;
import io.jsonwebtoken.io.Decoders; import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.io.Encoder; import io.jsonwebtoken.io.Encoder;
import io.jsonwebtoken.io.Serializer; import io.jsonwebtoken.io.Serializer;
import io.jsonwebtoken.lang.Builder;
import io.jsonwebtoken.security.AeadAlgorithm;
import io.jsonwebtoken.security.InvalidKeyException; import io.jsonwebtoken.security.InvalidKeyException;
import io.jsonwebtoken.security.KeyAlgorithm;
import io.jsonwebtoken.security.Keys; import io.jsonwebtoken.security.Keys;
import io.jsonwebtoken.security.Password;
import io.jsonwebtoken.security.SecureDigestAlgorithm;
import io.jsonwebtoken.security.StandardKeyAlgorithms;
import io.jsonwebtoken.security.StandardSecureDigestAlgorithms;
import io.jsonwebtoken.security.WeakKeyException;
import javax.crypto.SecretKey;
import java.security.Key; import java.security.Key;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.SecureRandom;
import java.security.interfaces.ECKey;
import java.security.interfaces.RSAKey;
import java.util.Date; import java.util.Date;
import java.util.Map; import java.util.Map;
/** /**
* A builder for constructing JWTs. * A builder for constructing Unprotected JWTs, Signed JWTs (aka 'JWS's) and Encrypted JWTs (aka 'JWE's).
* *
* @since 0.1 * @since 0.1
*/ */
public interface JwtBuilder extends ClaimsMutator<JwtBuilder> { public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
//replaces any existing header with the specified header. /**
* Sets the JCA Provider to use during cryptographic signing or encryption operations, or {@code null} if the
* JCA subsystem preferred provider should be used.
*
* @param provider the JCA Provider to use during cryptographic signing or encryption operations, or {@code null} if the
* JCA subsystem preferred provider should be used.
* @return the builder for method chaining.
* @since JJWT_RELEASE_VERSION
*/
JwtBuilder setProvider(Provider provider);
/**
* Sets the {@link SecureRandom} to use during cryptographic signing or encryption operations, or {@code null} if
* a default {@link SecureRandom} should be used.
*
* @param secureRandom the {@link SecureRandom} to use during cryptographic signing or encryption operations, or
* {@code null} if a default {@link SecureRandom} should be used.
* @return the builder for method chaining.
* @since JJWT_RELEASE_VERSION
*/
JwtBuilder setSecureRandom(SecureRandom secureRandom);
/** /**
* Sets (and replaces) any existing header with the specified header. If you do not want to replace the existing * Sets (and replaces) any existing header with the specified header. If you do not want to replace the existing
@ -42,7 +76,7 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
* @param header the header to set (and potentially replace any existing header). * @param header the header to set (and potentially replace any existing header).
* @return the builder for method chaining. * @return the builder for method chaining.
*/ */
JwtBuilder setHeader(Header header); JwtBuilder setHeader(Header<?> header); //replaces any existing header with the specified header.
/** /**
* Sets (and replaces) any existing header with the specified header. If you do not want to replace the existing * Sets (and replaces) any existing header with the specified header. If you do not want to replace the existing
@ -51,7 +85,17 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
* @param header the header to set (and potentially replace any existing header). * @param header the header to set (and potentially replace any existing header).
* @return the builder for method chaining. * @return the builder for method chaining.
*/ */
JwtBuilder setHeader(Map<String, Object> header); JwtBuilder setHeader(Map<String, ?> header);
/**
* Sets (and replaces) any existing header with the header resulting from the specified builder's
* {@link Builder#build()} result.
*
* @param builder the builder to use to obtain the header
* @return the JwtBuilder for method chaining.
* @since JJWT_RELEASE_VERSION
*/
JwtBuilder setHeader(Builder<? extends Header<?>> builder);
/** /**
* Applies the specified name/value pairs to the header. If a header does not yet exist at the time this method * Applies the specified name/value pairs to the header. If a header does not yet exist at the time this method
@ -60,9 +104,7 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
* @param params the header name/value pairs to append to the header. * @param params the header name/value pairs to append to the header.
* @return the builder for method chaining. * @return the builder for method chaining.
*/ */
JwtBuilder setHeaderParams(Map<String, Object> params); JwtBuilder setHeaderParams(Map<String, ?> params);
//sets the specified header parameter, overwriting any previous value under the same name.
/** /**
* Applies the specified name/value pair to the header. If a header does not yet exist at the time this method * Applies the specified name/value pair to the header. If a header does not yet exist at the time this method
@ -75,35 +117,97 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
JwtBuilder setHeaderParam(String name, Object value); JwtBuilder setHeaderParam(String name, Object value);
/** /**
* Sets the JWT's payload to be a plaintext (non-JSON) string. If you want the JWT body to be JSON, use the * Sets the JWT payload to the string's UTF-8-encoded bytes. It is strongly recommended to also set the
* {@link #setClaims(Claims)} or {@link #setClaims(java.util.Map)} methods instead. * {@link Header#getContentType() contentType} header value so the JWT recipient may inspect that value to
* determine how to convert the byte array to the final data type as desired. In this case, consider using
* {@link #setContent(byte[], String)} instead.
*
* <p>This is a convenience method that is effectively the same as:</p>
* <blockquote><pre>
* {@link #setContent(byte[]) setPayload}(payload.getBytes(StandardCharsets.UTF_8));</pre></blockquote>
*
* <p>If you want the JWT payload to be JSON, use the
* {@link #setClaims(Claims)} or {@link #setClaims(java.util.Map)} methods instead.</p>
* *
* <p>The payload and claims properties are mutually exclusive - only one of the two may be used.</p> * <p>The payload and claims properties are mutually exclusive - only one of the two may be used.</p>
* *
* @param payload the plaintext (non-JSON) string that will be the body of the JWT. * @param payload the string used to set UTF-8-encoded bytes as the JWT payload.
* @return the builder for method chaining. * @return the builder for method chaining.
* @see #setContent(byte[])
* @see #setContent(byte[], String)
* @deprecated since JJWT_RELEASE VERSION in favor of {@link #setContent(byte[])} or {@link #setContent(byte[], String)}
* because both Claims and Content are technically 'payloads', so this method name is misleading. This method will
* be removed before the 1.0 release.
*/ */
@Deprecated
JwtBuilder setPayload(String payload); JwtBuilder setPayload(String payload);
/** /**
* Sets the JWT payload to be a JSON Claims instance. If you do not want the JWT body to be JSON and instead want * Sets the JWT payload to be the specified content byte array.
* it to be a plaintext string, use the {@link #setPayload(String)} method instead. *
* <p><b>Content Type Recommendation</b></p>
*
* <p>Unless you are confident that the JWT recipient will <em>always</em> know how to use
* the given byte array without additional metadata, it is strongly recommended to use the
* {@link #setContent(byte[], String)} method instead of this one. That method ensures that a JWT recipient
* can inspect the {@code cty} header to know how to handle the byte array without ambiguity.</p>
*
* <p>Note that the content and claims properties are mutually exclusive - only one of the two may be used.</p>
*
* @param content the content byte array to use as the JWT payload
* @return the builder for method chaining.
* @since JJWT_RELEASE_VERSION
*/
JwtBuilder setContent(byte[] content);
/**
* Convenience method that sets the JWT payload to be the specified content byte array and also sets the
* {@link Header#setContentType(String) contentType} header value to a compact {@code cty} media type
* identifier to indicate the data format of the byte array. The JWT recipient can inspect the
* {@code cty} value to determine how to convert the byte array to the final content type as desired.
*
* <p><b>Compact Media Type Identifier</b></p>
*
* <p>As a convenience, this method will automatically trim any <code><b>application/</b></code> prefix from the
* {@code cty} string if possible according to the
* <a href="https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.10">JWT specification recommendations</a>.</p>
*
* <p>If for some reason you do not wish to adhere to the JWT specification recommendation, do not call this
* method - instead call {@link #setContent(byte[])} and {@link Header#setContentType(String)} independently.</p>
*
* <p>If you want the JWT payload to be JSON claims, use the {@link #setClaims(Claims)} or
* {@link #setClaims(java.util.Map)} methods instead.</p>
*
* <p>Note that the content and claims properties are mutually exclusive - only one of the two may be used.</p>
*
* @param content the content byte array that will be the JWT payload. Cannot be null or empty.
* @param cty the content type (media type) identifier attributed to the byte array. Cannot be null or empty.
* @return the builder for method chaining.
* @throws IllegalArgumentException if either {@code payload} or {@code cty} are null or empty.
* @since JJWT_RELEASE_VERSION
*/
JwtBuilder setContent(byte[] content, String cty) throws IllegalArgumentException;
/**
* Sets the JWT payload to be a JSON Claims instance. If you do not want the JWT payload to be JSON claims and
* instead want it to be a byte array representing any type of content, use the {@link #setContent(byte[])}
* method instead.
* *
* <p>The payload and claims properties are mutually exclusive - only one of the two may be used.</p> * <p>The payload and claims properties are mutually exclusive - only one of the two may be used.</p>
* *
* @param claims the JWT claims to be set as the JWT body. * @param claims the JWT claims to be set as the JWT payload.
* @return the builder for method chaining. * @return the builder for method chaining.
*/ */
JwtBuilder setClaims(Claims claims); JwtBuilder setClaims(Claims claims);
/** /**
* Sets the JWT payload to be a JSON Claims instance populated by the specified name/value pairs. If you do not * Sets the JWT payload to be a JSON Claims instance populated by the specified name/value pairs. If you do not
* want the JWT body to be JSON and instead want it to be a plaintext string, use the {@link #setPayload(String)} * want the JWT payload to be JSON claims and instead want it to be a byte array for any content, use the
* method instead. * {@link #setContent(byte[])} or {@link #setContent(byte[], String)} methods instead.
* *
* <p>The payload* and claims* properties are mutually exclusive - only one of the two may be used.</p> * <p>The payload and claims properties are mutually exclusive - only one of the two may be used.</p>
* *
* @param claims the JWT claims to be set as the JWT body. * @param claims the JWT Claims to be set as the JWT payload.
* @return the builder for method chaining. * @return the builder for method chaining.
*/ */
JwtBuilder setClaims(Map<String, ?> claims); JwtBuilder setClaims(Map<String, ?> claims);
@ -114,17 +218,17 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
* *
* <p>The payload and claims properties are mutually exclusive - only one of the two may be used.</p> * <p>The payload and claims properties are mutually exclusive - only one of the two may be used.</p>
* *
* @param claims the JWT claims to be added to the JWT body. * @param claims the JWT Claims to be added to the JWT payload.
* @return the builder for method chaining. * @return the builder for method chaining.
* @since 0.8 * @since 0.8
*/ */
JwtBuilder addClaims(Map<String, Object> claims); JwtBuilder addClaims(Map<String, ?> claims);
/** /**
* Sets the JWT Claims <a href="https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-25#section-4.1.1"> * Sets the JWT Claims <a href="https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-25#section-4.1.1">
* <code>iss</code></a> (issuer) value. A {@code null} value will remove the property from the Claims. * <code>iss</code></a> (issuer) value. A {@code null} value will remove the property from the Claims.
* *
* <p>This is a convenience method. It will first ensure a Claims instance exists as the JWT body and then set * <p>This is a convenience method. It will first ensure a Claims instance exists as the JWT payload and then set
* the Claims {@link Claims#setIssuer(String) issuer} field with the specified value. This allows you to write * the Claims {@link Claims#setIssuer(String) issuer} field with the specified value. This allows you to write
* code like this:</p> * code like this:</p>
* *
@ -151,7 +255,7 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
* Sets the JWT Claims <a href="https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-25#section-4.1.2"> * Sets the JWT Claims <a href="https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-25#section-4.1.2">
* <code>sub</code></a> (subject) value. A {@code null} value will remove the property from the Claims. * <code>sub</code></a> (subject) value. A {@code null} value will remove the property from the Claims.
* *
* <p>This is a convenience method. It will first ensure a Claims instance exists as the JWT body and then set * <p>This is a convenience method. It will first ensure a Claims instance exists as the JWT payload and then set
* the Claims {@link Claims#setSubject(String) subject} field with the specified value. This allows you to write * the Claims {@link Claims#setSubject(String) subject} field with the specified value. This allows you to write
* code like this:</p> * code like this:</p>
* *
@ -178,7 +282,7 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
* Sets the JWT Claims <a href="https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-25#section-4.1.3"> * Sets the JWT Claims <a href="https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-25#section-4.1.3">
* <code>aud</code></a> (audience) value. A {@code null} value will remove the property from the Claims. * <code>aud</code></a> (audience) value. A {@code null} value will remove the property from the Claims.
* *
* <p>This is a convenience method. It will first ensure a Claims instance exists as the JWT body and then set * <p>This is a convenience method. It will first ensure a Claims instance exists as the JWT payload and then set
* the Claims {@link Claims#setAudience(String) audience} field with the specified value. This allows you to write * the Claims {@link Claims#setAudience(String) audience} field with the specified value. This allows you to write
* code like this:</p> * code like this:</p>
* *
@ -207,7 +311,7 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
* *
* <p>A JWT obtained after this timestamp should not be used.</p> * <p>A JWT obtained after this timestamp should not be used.</p>
* *
* <p>This is a convenience method. It will first ensure a Claims instance exists as the JWT body and then set * <p>This is a convenience method. It will first ensure a Claims instance exists as the JWT payload and then set
* the Claims {@link Claims#setExpiration(java.util.Date) expiration} field with the specified value. This allows * the Claims {@link Claims#setExpiration(java.util.Date) expiration} field with the specified value. This allows
* you to write code like this:</p> * you to write code like this:</p>
* *
@ -236,7 +340,7 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
* *
* <p>A JWT obtained before this timestamp should not be used.</p> * <p>A JWT obtained before this timestamp should not be used.</p>
* *
* <p>This is a convenience method. It will first ensure a Claims instance exists as the JWT body and then set * <p>This is a convenience method. It will first ensure a Claims instance exists as the JWT payload and then set
* the Claims {@link Claims#setNotBefore(java.util.Date) notBefore} field with the specified value. This allows * the Claims {@link Claims#setNotBefore(java.util.Date) notBefore} field with the specified value. This allows
* you to write code like this:</p> * you to write code like this:</p>
* *
@ -265,7 +369,7 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
* *
* <p>The value is the timestamp when the JWT was created.</p> * <p>The value is the timestamp when the JWT was created.</p>
* *
* <p>This is a convenience method. It will first ensure a Claims instance exists as the JWT body and then set * <p>This is a convenience method. It will first ensure a Claims instance exists as the JWT payload and then set
* the Claims {@link Claims#setIssuedAt(java.util.Date) issuedAt} field with the specified value. This allows * the Claims {@link Claims#setIssuedAt(java.util.Date) issuedAt} field with the specified value. This allows
* you to write code like this:</p> * you to write code like this:</p>
* *
@ -296,7 +400,7 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
* manner that ensures that there is a negligible probability that the same value will be accidentally * manner that ensures that there is a negligible probability that the same value will be accidentally
* assigned to a different data object. The ID can be used to prevent the JWT from being replayed.</p> * assigned to a different data object. The ID can be used to prevent the JWT from being replayed.</p>
* *
* <p>This is a convenience method. It will first ensure a Claims instance exists as the JWT body and then set * <p>This is a convenience method. It will first ensure a Claims instance exists as the JWT payload and then set
* the Claims {@link Claims#setId(String) id} field with the specified value. This allows * the Claims {@link Claims#setId(String) id} field with the specified value. This allows
* you to write code like this:</p> * you to write code like this:</p>
* *
@ -322,7 +426,7 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
/** /**
* Sets a custom JWT Claims parameter value. A {@code null} value will remove the property from the Claims. * Sets a custom JWT Claims parameter value. A {@code null} value will remove the property from the Claims.
* *
* <p>This is a convenience method. It will first ensure a Claims instance exists as the JWT body and then set the * <p>This is a convenience method. It will first ensure a Claims instance exists as the JWT payload and then set the
* named property on the Claims instance using the Claims {@link Claims#put(Object, Object) put} method. This allows * named property on the Claims instance using the Claims {@link Claims#put(Object, Object) put} method. This allows
* you to write code like this:</p> * you to write code like this:</p>
* *
@ -345,20 +449,144 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
JwtBuilder claim(String name, Object value); JwtBuilder claim(String name, Object value);
/** /**
* Signs the constructed JWT with the specified key using the key's * Signs the constructed JWT with the specified key using the key's <em>recommended signature algorithm</em>
* {@link SignatureAlgorithm#forSigningKey(Key) recommended signature algorithm}, producing a JWS. If the * as defined below, producing a JWS. If the recommended signature algorithm isn't sufficient for your needs,
* recommended signature algorithm isn't sufficient for your needs, consider using * consider using {@link #signWith(Key, SecureDigestAlgorithm)} instead.
* {@link #signWith(Key, SignatureAlgorithm)} instead.
* *
* <p>If you are looking to invoke this method with a byte array that you are confident may be used for HMAC-SHA * <p>If you are looking to invoke this method with a byte array that you are confident may be used for HMAC-SHA
* algorithms, consider using {@link Keys Keys}.{@link Keys#hmacShaKeyFor(byte[]) hmacShaKeyFor(bytes)} to * algorithms, consider using {@link Keys Keys}.{@link Keys#hmacShaKeyFor(byte[]) hmacShaKeyFor(bytes)} to
* convert the byte array into a valid {@code Key}.</p> * convert the byte array into a valid {@code Key}.</p>
* *
* <p><b><a id="recsigalg">Recommended Signature Algorithm</a></b></p>
*
* <p>The recommended signature algorithm used with a given key is chosen based on the following:</p>
* <table>
* <caption>Key Recommended Signature Algorithm</caption>
* <thead>
* <tr>
* <th>If the Key is a:</th>
* <th>And:</th>
* <th>With a key size of:</th>
* <th>The SignatureAlgorithm used will be:</th>
* </tr>
* </thead>
* <tbody>
* <tr>
* <td>{@link SecretKey}</td>
* <td><code>{@link Key#getAlgorithm() getAlgorithm()}.equals("HmacSHA256")</code><sup>1</sup></td>
* <td>256 &lt;= size &lt;= 383 <sup>2</sup></td>
* <td>{@link StandardSecureDigestAlgorithms#HS256 HS256}</td>
* </tr>
* <tr>
* <td>{@link SecretKey}</td>
* <td><code>{@link Key#getAlgorithm() getAlgorithm()}.equals("HmacSHA384")</code><sup>1</sup></td>
* <td>384 &lt;= size &lt;= 511</td>
* <td>{@link StandardSecureDigestAlgorithms#HS384 HS384}</td>
* </tr>
* <tr>
* <td>{@link SecretKey}</td>
* <td><code>{@link Key#getAlgorithm() getAlgorithm()}.equals("HmacSHA512")</code><sup>1</sup></td>
* <td>512 &lt;= size</td>
* <td>{@link StandardSecureDigestAlgorithms#HS512 HS512}</td>
* </tr>
* <tr>
* <td>{@link ECKey}</td>
* <td><code>instanceof {@link PrivateKey}</code></td>
* <td>256 &lt;= size &lt;= 383 <sup>3</sup></td>
* <td>{@link StandardSecureDigestAlgorithms#ES256 ES256}</td>
* </tr>
* <tr>
* <td>{@link ECKey}</td>
* <td><code>instanceof {@link PrivateKey}</code></td>
* <td>384 &lt;= size &lt;= 520 <sup>4</sup></td>
* <td>{@link StandardSecureDigestAlgorithms#ES384 ES384}</td>
* </tr>
* <tr>
* <td>{@link ECKey}</td>
* <td><code>instanceof {@link PrivateKey}</code></td>
* <td><b>521</b> &lt;= size <sup>4</sup></td>
* <td>{@link StandardSecureDigestAlgorithms#ES512 ES512}</td>
* </tr>
* <tr>
* <td>{@link RSAKey}</td>
* <td><code>instanceof {@link PrivateKey}</code></td>
* <td>2048 &lt;= size &lt;= 3071 <sup>5,6</sup></td>
* <td>{@link StandardSecureDigestAlgorithms#RS256 RS256}</td>
* </tr>
* <tr>
* <td>{@link RSAKey}</td>
* <td><code>instanceof {@link PrivateKey}</code></td>
* <td>3072 &lt;= size &lt;= 4095 <sup>6</sup></td>
* <td>{@link StandardSecureDigestAlgorithms#RS384 RS384}</td>
* </tr>
* <tr>
* <td>{@link RSAKey}</td>
* <td><code>instanceof {@link PrivateKey}</code></td>
* <td>4096 &lt;= size <sup>5</sup></td>
* <td>{@link StandardSecureDigestAlgorithms#RS512 RS512}</td>
* </tr>
* <tr>
* <td><a href="https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/security/interfaces/EdECKey.html">EdECKey</a><sup>7</sup></td>
* <td><code>instanceof {@link PrivateKey}</code></td>
* <td>256</td>
* <td>{@link StandardSecureDigestAlgorithms#Ed25519 Ed25519}</td>
* </tr>
* <tr>
* <td><a href="https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/security/interfaces/EdECKey.html">EdECKey</a><sup>7</sup></td>
* <td><code>instanceof {@link PrivateKey}</code></td>
* <td>456</td>
* <td>{@link StandardSecureDigestAlgorithms#Ed448 Ed448}</td>
* </tr>
* </tbody>
* </table>
* <p>Notes:</p>
* <ol>
* <li>{@code SecretKey} instances must have an {@link Key#getAlgorithm() algorithm} name equal
* to {@code HmacSHA256}, {@code HmacSHA384} or {@code HmacSHA512}. If not, the key bytes might not be
* suitable for HMAC signatures will be rejected with a {@link InvalidKeyException}. </li>
* <li>The JWT <a href="https://tools.ietf.org/html/rfc7518#section-3.2">JWA Specification (RFC 7518,
* Section 3.2)</a> mandates that HMAC-SHA-* signing keys <em>MUST</em> be 256 bits or greater.
* {@code SecretKey}s with key lengths less than 256 bits will be rejected with an
* {@link WeakKeyException}.</li>
* <li>The JWT <a href="https://tools.ietf.org/html/rfc7518#section-3.4">JWA Specification (RFC 7518,
* Section 3.4)</a> mandates that ECDSA signing key lengths <em>MUST</em> be 256 bits or greater.
* {@code ECKey}s with key lengths less than 256 bits will be rejected with a
* {@link WeakKeyException}.</li>
* <li>The ECDSA {@code P-521} curve does indeed use keys of <b>521</b> bits, not 512 as might be expected. ECDSA
* keys of 384 &lt; size &lt;= 520 are suitable for ES384, while ES512 requires keys &gt;= 521 bits. The '512' part of the
* ES512 name reflects the usage of the SHA-512 algorithm, not the ECDSA key length. ES512 with ECDSA keys less
* than 521 bits will be rejected with a {@link WeakKeyException}.</li>
* <li>The JWT <a href="https://tools.ietf.org/html/rfc7518#section-3.3">JWA Specification (RFC 7518,
* Section 3.3)</a> mandates that RSA signing key lengths <em>MUST</em> be 2048 bits or greater.
* {@code RSAKey}s with key lengths less than 2048 bits will be rejected with a
* {@link WeakKeyException}.</li>
* <li>Technically any RSA key of length &gt;= 2048 bits may be used with the
* {@link StandardSecureDigestAlgorithms#RS256 RS256}, {@link StandardSecureDigestAlgorithms#RS384 RS384}, and
* {@link StandardSecureDigestAlgorithms#RS512 RS512} algorithms, so we assume an RSA signature algorithm based on the key
* length to parallel similar decisions in the JWT specification for HMAC and ECDSA signature algorithms.
* This is not required - just a convenience.</li>
* <li><a href="https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/security/interfaces/EdECKey.html">EdECKey</a>s
* require JDK &gt;= 15 or BouncyCastle in the runtime classpath.</li>
* </ol>
*
* <p>This implementation does not use the {@link StandardSecureDigestAlgorithms#PS256 PS256},
* {@link StandardSecureDigestAlgorithms#PS384 PS384}, or {@link StandardSecureDigestAlgorithms#PS512 PS512} RSA variants for any
* specified {@link RSAKey} because the the {@link StandardSecureDigestAlgorithms#RS256 RS256},
* {@link StandardSecureDigestAlgorithms#RS384 RS384}, and {@link StandardSecureDigestAlgorithms#RS512 RS512} algorithms are
* available in the JDK by default while the {@code PS}* variants require either JDK 11 or an additional JCA
* Provider (like BouncyCastle). If you wish to use a {@code PS}* variant with your key, use the
* {@link #signWith(Key, SecureDigestAlgorithm)} method instead.</p>
*
* <p>Finally, this method will throw an {@link InvalidKeyException} for any key that does not match the
* heuristics and requirements documented above, since that inevitably means the Key is either insufficient,
* unsupported, or explicitly disallowed by the JWT specification.</p>
*
* @param key the key to use for signing * @param key the key to use for signing
* @return the builder instance for method chaining. * @return the builder instance for method chaining.
* @throws InvalidKeyException if the Key is insufficient or explicitly disallowed by the JWT specification as * @throws InvalidKeyException if the Key is insufficient, unsupported, or explicitly disallowed by the JWT
* described by {@link SignatureAlgorithm#forSigningKey(Key)}. * specification as described above in <em>recommended signature algorithms</em>.
* @see #signWith(Key, SignatureAlgorithm) * @see Jwts#SIG
* @see #signWith(Key, SecureDigestAlgorithm)
* @since 0.10.0 * @since 0.10.0
*/ */
JwtBuilder signWith(Key key) throws InvalidKeyException; JwtBuilder signWith(Key key) throws InvalidKeyException;
@ -369,17 +597,19 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
* <p><b>Deprecation Notice: Deprecated as of 0.10.0</b></p> * <p><b>Deprecation Notice: Deprecated as of 0.10.0</b></p>
* *
* <p>Use {@link Keys Keys}.{@link Keys#hmacShaKeyFor(byte[]) hmacShaKeyFor(bytes)} to * <p>Use {@link Keys Keys}.{@link Keys#hmacShaKeyFor(byte[]) hmacShaKeyFor(bytes)} to
* obtain the {@code Key} and then invoke {@link #signWith(Key)} or {@link #signWith(Key, SignatureAlgorithm)}.</p> * obtain the {@code Key} and then invoke {@link #signWith(Key)} or
* {@link #signWith(Key, SecureDigestAlgorithm)}.</p>
* *
* <p>This method will be removed in the 1.0 release.</p> * <p>This method will be removed in the 1.0 release.</p>
* *
* @param alg the JWS algorithm to use to digitally sign the JWT, thereby producing a JWS. * @param alg the JWS algorithm to use to digitally sign the JWT, thereby producing a JWS.
* @param secretKey the algorithm-specific signing key to use to digitally sign the JWT. * @param secretKey the algorithm-specific signing key to use to digitally sign the JWT.
* @return the builder for method chaining. * @return the builder for method chaining.
* @throws InvalidKeyException if the Key is insufficient or explicitly disallowed by the JWT specification as * @throws InvalidKeyException if the Key is insufficient for the specified algorithm or explicitly disallowed by
* described by {@link SignatureAlgorithm#forSigningKey(Key)}. * the JWT specification.
* @deprecated as of 0.10.0: use {@link Keys Keys}.{@link Keys#hmacShaKeyFor(byte[]) hmacShaKeyFor(bytes)} to * @deprecated as of 0.10.0: use {@link Keys Keys}.{@link Keys#hmacShaKeyFor(byte[]) hmacShaKeyFor(bytes)} to
* obtain the {@code Key} and then invoke {@link #signWith(Key)} or {@link #signWith(Key, SignatureAlgorithm)}. * obtain the {@code Key} and then invoke {@link #signWith(Key)} or
* {@link #signWith(Key, SecureDigestAlgorithm)}.
* This method will be removed in the 1.0 release. * This method will be removed in the 1.0 release.
*/ */
@Deprecated @Deprecated
@ -403,7 +633,7 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
* <p>{@code String base64EncodedSecretKey = base64Encode(secretKeyBytes);}</p> * <p>{@code String base64EncodedSecretKey = base64Encode(secretKeyBytes);}</p>
* *
* <p>However, a non-trivial number of JJWT users were confused by the method signature and attempted to * <p>However, a non-trivial number of JJWT users were confused by the method signature and attempted to
* use raw password strings as the key argument - for example {@code signWith(HS256, myPassword)} - which is * use raw password strings as the key argument - for example {@code with(HS256, myPassword)} - which is
* almost always incorrect for cryptographic hashes and can produce erroneous or insecure results.</p> * almost always incorrect for cryptographic hashes and can produce erroneous or insecure results.</p>
* *
* <p>See this * <p>See this
@ -415,7 +645,7 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
* <pre><code> * <pre><code>
* byte[] keyBytes = {@link Decoders Decoders}.{@link Decoders#BASE64 BASE64}.{@link Decoder#decode(Object) decode(base64EncodedSecretKey)}; * byte[] keyBytes = {@link Decoders Decoders}.{@link Decoders#BASE64 BASE64}.{@link Decoder#decode(Object) decode(base64EncodedSecretKey)};
* Key key = {@link Keys Keys}.{@link Keys#hmacShaKeyFor(byte[]) hmacShaKeyFor(keyBytes)}; * Key key = {@link Keys Keys}.{@link Keys#hmacShaKeyFor(byte[]) hmacShaKeyFor(keyBytes)};
* jwtBuilder.signWith(key); //or {@link #signWith(Key, SignatureAlgorithm)} * jwtBuilder.with(key); //or {@link #signWith(Key, SignatureAlgorithm)}
* </code></pre> * </code></pre>
* *
* <p>This method will be removed in the 1.0 release.</p> * <p>This method will be removed in the 1.0 release.</p>
@ -445,14 +675,21 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
* @throws InvalidKeyException if the Key is insufficient or explicitly disallowed by the JWT specification for * @throws InvalidKeyException if the Key is insufficient or explicitly disallowed by the JWT specification for
* the specified algorithm. * the specified algorithm.
* @see #signWith(Key) * @see #signWith(Key)
* @deprecated since 0.10.0: use {@link #signWith(Key, SignatureAlgorithm)} instead. This method will be removed * @deprecated since 0.10.0. Use {@link #signWith(Key, SecureDigestAlgorithm)} instead.
* in the 1.0 release. * This method will be removed before the 1.0 release.
*/ */
@Deprecated @Deprecated
JwtBuilder signWith(SignatureAlgorithm alg, Key key) throws InvalidKeyException; JwtBuilder signWith(SignatureAlgorithm alg, Key key) throws InvalidKeyException;
/** /**
* Signs the constructed JWT with the specified key using the specified algorithm, producing a JWS. * <p><b>Deprecation Notice</b></p>
*
* <p><b>This has been deprecated since JJWT_RELEASE_VERSION. Use
* {@link #signWith(Key, SecureDigestAlgorithm)} instead</b>. Standard JWA algorithms
* are represented as instances of this new interface in the {@link Jwts#SIG}
* algorithm registry.</p>
*
* <p>Signs the constructed JWT with the specified key using the specified algorithm, producing a JWS.</p>
* *
* <p>It is typically recommended to call the {@link #signWith(Key)} instead for simplicity. * <p>It is typically recommended to call the {@link #signWith(Key)} instead for simplicity.
* However, this method can be useful if the recommended algorithm heuristics do not meet your needs or if * However, this method can be useful if the recommended algorithm heuristics do not meet your needs or if
@ -465,11 +702,97 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
* the specified algorithm. * the specified algorithm.
* @see #signWith(Key) * @see #signWith(Key)
* @since 0.10.0 * @since 0.10.0
* @deprecated since JJWT_RELEASE_VERSION to use the more flexible {@link #signWith(Key, SecureDigestAlgorithm)}.
*/ */
@Deprecated
JwtBuilder signWith(Key key, SignatureAlgorithm alg) throws InvalidKeyException; JwtBuilder signWith(Key key, SignatureAlgorithm alg) throws InvalidKeyException;
/** /**
* Compresses the JWT body using the specified {@link CompressionCodec}. * Signs the constructed JWT with the specified key using the specified algorithm, producing a JWS.
*
* <p>The {@link Jwts#SIG} registry makes available all standard signature
* algorithms defined in the JWA specification.</p>
*
* <p>It is typically recommended to call the {@link #signWith(Key)} instead for simplicity.
* However, this method can be useful if the recommended algorithm heuristics do not meet your needs or if
* you want explicit control over the signature algorithm used with the specified key.</p>
*
* @param key the signing key to use to digitally sign the JWT.
* @param <K> The type of key accepted by the {@code SignatureAlgorithm}.
* @param alg the JWS algorithm to use with the key to digitally sign the JWT, thereby producing a JWS.
* @return the builder for method chaining.
* @throws InvalidKeyException if the Key is insufficient or explicitly disallowed by the JWT specification for
* the specified algorithm.
* @see #signWith(Key)
* @see Jwts#SIG
* @since JJWT_RELEASE_VERSION
*/
<K extends Key> JwtBuilder signWith(K key, io.jsonwebtoken.security.SecureDigestAlgorithm<? super K, ?> alg) throws InvalidKeyException;
/**
* Encrypts the constructed JWT with the specified symmetric {@code key} using the provided {@code enc}ryption
* algorithm, producing a JWE. Because it is a symmetric key, the JWE recipient
* must also have access to the same key to decrypt.
*
* <p>This method is a convenience method that delegates to
* {@link #encryptWith(Key, KeyAlgorithm, AeadAlgorithm) encryptWith(Key, KeyAlgorithm, AeadAlgorithm)}
* based on the {@code key} argument:</p>
* <ul>
* <li>If the provided {@code key} is a {@link Password Password} instance,
* the {@code KeyAlgorithm} used will be one of the three JWA-standard password-based key algorithms
* ({@link StandardKeyAlgorithms#PBES2_HS256_A128KW PBES2_HS256_A128KW},
* {@link StandardKeyAlgorithms#PBES2_HS384_A192KW PBES2_HS384_A192KW}, or
* {@link StandardKeyAlgorithms#PBES2_HS512_A256KW PBES2_HS512_A256KW}) as determined by the {@code enc} algorithm's
* {@link AeadAlgorithm#getKeyBitLength() key length} requirement.</li>
* <li>If the {@code key} is otherwise a standard {@code SecretKey}, the {@code KeyAlgorithm} will be
* {@link StandardKeyAlgorithms#DIRECT}, indicating that {@code key} should be used directly with the
* {@code enc} algorithm. In this case, the {@code key} argument <em>MUST</em> be of sufficient strength to
* use with the specified {@code enc} algorithm, otherwise an exception will be thrown during encryption. If
* desired, secure-random keys suitable for an {@link AeadAlgorithm} may be generated using the algorithm's
* {@link AeadAlgorithm#keyBuilder() keyBuilder}.</li>
* </ul>
*
* @param key the symmetric encryption key to use with the {@code enc} algorithm.
* @param enc the {@link AeadAlgorithm} algorithm used to encrypt the JWE, usually one of the JWA-standard
* algorithms accessible via {@link Jwts#ENC}.
* @return the JWE builder for method chaining.
* @see Jwts#ENC
*/
JwtBuilder encryptWith(SecretKey key, AeadAlgorithm enc);
/**
* Encrypts the constructed JWT using the specified {@code enc} algorithm with the symmetric key produced by the
* {@code keyAlg} when invoked with the given {@code key}, producing a JWE.
*
* <p>This behavior can be illustrated by the following pseudocode, a rough example of what happens during
* {@link #compact() compact}ion:</p>
* <blockquote><pre>
* SecretKey encryptionKey = keyAlg.getEncryptionKey(key); // (1)
* byte[] jweCiphertext = enc.encrypt(payloadBytes, encryptionKey); // (2)</pre></blockquote>
* <ol>
* <li>The {@code keyAlg} argument is first invoked with the provided {@code key} argument, resulting in a
* {@link SecretKey}.</li>
* <li>This {@code SecretKey} result is used to call the provided {@code enc} encryption algorithm argument,
* resulting in the final JWE ciphertext.</li>
* </ol>
*
* <p>Most application developers will reference one of the JWA
* {@link Jwts#KEY standard key algorithms} and {@link Jwts#ENC standard encryption algorithms}
* when invoking this method, but custom implementations are also supported.</p>
*
* @param <K> the type of key that must be used with the specified {@code keyAlg} instance.
* @param key the key used to invoke the provided {@code keyAlg} instance.
* @param keyAlg the key management algorithm that will produce the symmetric {@code SecretKey} to use with the
* {@code enc} algorithm
* @param enc the {@link AeadAlgorithm} algorithm used to encrypt the JWE
* @return the JWE builder for method chaining.
* @see Jwts#ENC
* @see Jwts#KEY
*/
<K extends Key> JwtBuilder encryptWith(K key, KeyAlgorithm<? super K, ?> keyAlg, AeadAlgorithm enc);
/**
* Compresses the JWT payload using the specified {@link CompressionCodec}.
* *
* <p>If your compact JWTs are large, and you want to reduce their total size during network transmission, this * <p>If your compact JWTs are large, and you want to reduce their total size during network transmission, this
* can be useful. For example, when embedding JWTs in URLs, some browsers may not support URLs longer than a * can be useful. For example, when embedding JWTs in URLs, some browsers may not support URLs longer than a
@ -477,7 +800,7 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
* *
* <p><b>Compatibility Warning</b></p> * <p><b>Compatibility Warning</b></p>
* *
* <p>The JWT family of specifications defines compression only for JWE (Json Web Encryption) * <p>The JWT family of specifications defines compression only for JWE (JSON Web Encryption)
* tokens. Even so, JJWT will also support compression for JWS tokens as well if you choose to use it. * tokens. Even so, JJWT will also support compression for JWS tokens as well if you choose to use it.
* However, be aware that <b>if you use compression when creating a JWS token, other libraries may not be able to * However, be aware that <b>if you use compression when creating a JWS token, other libraries may not be able to
* parse that JWS token</b>. When using compression for JWS tokens, be sure that all parties accessing the * parse that JWS token</b>. When using compression for JWS tokens, be sure that all parties accessing the
@ -507,7 +830,7 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
/** /**
* Performs object-to-JSON serialization with the specified Serializer. This is used by the builder to convert * Performs object-to-JSON serialization with the specified Serializer. This is used by the builder to convert
* JWT/JWS/JWT headers and claims Maps to JSON strings as required by the JWT specification. * JWT/JWS/JWE headers and claims Maps to JSON strings as required by the JWT specification.
* *
* <p>If this method is not called, JJWT will use whatever serializer it can find at runtime, checking for the * <p>If this method is not called, JJWT will use whatever serializer it can find at runtime, checking for the
* presence of well-known implementations such Jackson, Gson, and org.json. If one of these is not found * presence of well-known implementations such Jackson, Gson, and org.json. If one of these is not found

View File

@ -22,10 +22,21 @@ package io.jsonwebtoken;
*/ */
public class JwtException extends RuntimeException { public class JwtException extends RuntimeException {
/**
* Creates a new instance with the specified explanation message.
*
* @param message the message explaining why the exception is thrown.
*/
public JwtException(String message) { public JwtException(String message) {
super(message); super(message);
} }
/**
* Creates a new instance with the specified explanation message and underlying cause.
*
* @param message the message explaining why the exception is thrown.
* @param cause the underlying cause that resulted in this exception being thrown.
*/
public JwtException(String message, Throwable cause) { public JwtException(String message, Throwable cause) {
super(message, cause); super(message, cause);
} }

View File

@ -17,7 +17,7 @@ package io.jsonwebtoken;
/** /**
* A JwtHandler is invoked by a {@link io.jsonwebtoken.JwtParser JwtParser} after parsing a JWT to indicate the exact * A JwtHandler is invoked by a {@link io.jsonwebtoken.JwtParser JwtParser} after parsing a JWT to indicate the exact
* type of JWT or JWS parsed. * type of JWT, JWS or JWE parsed.
* *
* @param <T> the type of object to return to the parser caller after handling the parsed JWT. * @param <T> the type of object to return to the parser caller after handling the parsed JWT.
* @since 0.2 * @since 0.2
@ -26,37 +26,42 @@ public interface JwtHandler<T> {
/** /**
* This method is invoked when a {@link io.jsonwebtoken.JwtParser JwtParser} determines that the parsed JWT is * This method is invoked when a {@link io.jsonwebtoken.JwtParser JwtParser} determines that the parsed JWT is
* a plaintext JWT. A plaintext JWT has a String (non-JSON) body payload and it is not cryptographically signed. * an Unprotected content JWT. An Unprotected content JWT has a byte array payload that is not
* cryptographically signed or encrypted. If the JWT creator set the (optional)
* {@link Header#getContentType() contentType} header value, the application may inspect that value to determine
* how to convert the byte array to the final content type as desired.
* *
* @param jwt the parsed plaintext JWT * @param jwt the parsed Unprotected content JWT
* @return any object to be used after inspecting the JWT, or {@code null} if no return value is necessary. * @return any object to be used after inspecting the JWT, or {@code null} if no return value is necessary.
*/ */
T onPlaintextJwt(Jwt<Header, String> jwt); T onContentJwt(Jwt<UnprotectedHeader, byte[]> jwt);
/** /**
* This method is invoked when a {@link io.jsonwebtoken.JwtParser JwtParser} determines that the parsed JWT is * This method is invoked when a {@link io.jsonwebtoken.JwtParser JwtParser} determines that the parsed JWT is
* a Claims JWT. A Claims JWT has a {@link Claims} body and it is not cryptographically signed. * a Claims JWT. A Claims JWT has a {@link Claims} payload that is not cryptographically signed or encrypted.
* *
* @param jwt the parsed claims JWT * @param jwt the parsed claims JWT
* @return any object to be used after inspecting the JWT, or {@code null} if no return value is necessary. * @return any object to be used after inspecting the JWT, or {@code null} if no return value is necessary.
*/ */
T onClaimsJwt(Jwt<Header, Claims> jwt); T onClaimsJwt(Jwt<UnprotectedHeader, Claims> jwt);
/** /**
* This method is invoked when a {@link io.jsonwebtoken.JwtParser JwtParser} determines that the parsed JWT is * This method is invoked when a {@link io.jsonwebtoken.JwtParser JwtParser} determines that the parsed JWT is
* a plaintext JWS. A plaintext JWS is a JWT with a String (non-JSON) body (payload) that has been * a content JWS. A content JWS is a JWT with a byte array payload that has been cryptographically signed.
* cryptographically signed. * If the JWT creator set the (optional) {@link Header#getContentType() contentType} header value, the
* application may inspect that value to determine how to convert the byte array to the final content type
* as desired.
* *
* <p>This method will only be invoked if the cryptographic signature can be successfully verified.</p> * <p>This method will only be invoked if the cryptographic signature can be successfully verified.</p>
* *
* @param jws the parsed plaintext JWS * @param jws the parsed content JWS
* @return any object to be used after inspecting the JWS, or {@code null} if no return value is necessary. * @return any object to be used after inspecting the JWS, or {@code null} if no return value is necessary.
*/ */
T onPlaintextJws(Jws<String> jws); T onContentJws(Jws<byte[]> jws);
/** /**
* This method is invoked when a {@link io.jsonwebtoken.JwtParser JwtParser} determines that the parsed JWT is * This method is invoked when a {@link io.jsonwebtoken.JwtParser JwtParser} determines that the parsed JWT is
* a valid Claims JWS. A Claims JWS is a JWT with a {@link Claims} body that has been cryptographically signed. * a valid Claims JWS. A Claims JWS is a JWT with a {@link Claims} payload that has been cryptographically signed.
* *
* <p>This method will only be invoked if the cryptographic signature can be successfully verified.</p> * <p>This method will only be invoked if the cryptographic signature can be successfully verified.</p>
* *
@ -65,4 +70,30 @@ public interface JwtHandler<T> {
*/ */
T onClaimsJws(Jws<Claims> jws); T onClaimsJws(Jws<Claims> jws);
/**
* This method is invoked when a {@link io.jsonwebtoken.JwtParser JwtParser} determines that the parsed JWT is
* a content JWE. A content JWE is a JWE with a byte array payload that has been encrypted. If the JWT creator set
* the (optional) {@link Header#getContentType() contentType} header value, the application may inspect that
* value to determine how to convert the byte array to the final content type as desired.
*
* <p>This method will only be invoked if the content JWE can be successfully decrypted.</p>
*
* @param jwe the parsed content jwe
* @return any object to be used after inspecting the JWE, or {@code null} if no return value is necessary.
* @since JJWT_RELEASE_VERSION
*/
T onContentJwe(Jwe<byte[]> jwe);
/**
* This method is invoked when a {@link io.jsonwebtoken.JwtParser JwtParser} determines that the parsed JWT is
* a valid Claims JWE. A Claims JWE is a JWT with a {@link Claims} payload that has been encrypted.
*
* <p>This method will only be invoked if the Claims JWE can be successfully decrypted.</p>
*
* @param jwe the parsed claims jwe
* @return any object to be used after inspecting the JWE, or {@code null} if no return value is necessary.
* @since JJWT_RELEASE_VERSION
*/
T onClaimsJwe(Jwe<Claims> jwe);
} }

View File

@ -21,32 +21,42 @@ package io.jsonwebtoken;
* known/expected for a particular use case. * known/expected for a particular use case.
* *
* <p>All of the methods in this implementation throw exceptions: overridden methods represent * <p>All of the methods in this implementation throw exceptions: overridden methods represent
* scenarios expected by calling code in known situations. It would be unexpected to receive a JWS or JWT that did * scenarios expected by calling code in known situations. It would be unexpected to receive a JWT that did
* not match parsing expectations, so all non-overridden methods throw exceptions to indicate that the JWT * not match parsing expectations, so all non-overridden methods throw exceptions to indicate that the JWT
* input was unexpected.</p> * input was unexpected.</p>
* *
* @param <T> the type of object to return to the parser caller after handling the parsed JWT. * @param <T> the type of object to return to the parser caller after handling the parsed JWT.
* @since 0.2 * @since 0.2
*/ */
public class JwtHandlerAdapter<T> implements JwtHandler<T> { public abstract class JwtHandlerAdapter<T> implements JwtHandler<T> {
@Override @Override
public T onPlaintextJwt(Jwt<Header, String> jwt) { public T onContentJwt(Jwt<UnprotectedHeader, byte[]> jwt) {
throw new UnsupportedJwtException("Unsigned plaintext JWTs are not supported."); throw new UnsupportedJwtException("Unprotected content JWTs are not supported.");
} }
@Override @Override
public T onClaimsJwt(Jwt<Header, Claims> jwt) { public T onClaimsJwt(Jwt<UnprotectedHeader, Claims> jwt) {
throw new UnsupportedJwtException("Unsigned Claims JWTs are not supported."); throw new UnsupportedJwtException("Unprotected Claims JWTs are not supported.");
} }
@Override @Override
public T onPlaintextJws(Jws<String> jws) { public T onContentJws(Jws<byte[]> jws) {
throw new UnsupportedJwtException("Signed plaintext JWSs are not supported."); throw new UnsupportedJwtException("Signed content JWTs are not supported.");
} }
@Override @Override
public T onClaimsJws(Jws<Claims> jws) { public T onClaimsJws(Jws<Claims> jws) {
throw new UnsupportedJwtException("Signed Claims JWSs are not supported."); throw new UnsupportedJwtException("Signed Claims JWTs are not supported.");
}
@Override
public T onContentJwe(Jwe<byte[]> jwe) {
throw new UnsupportedJwtException("Encrypted content JWTs are not supported.");
}
@Override
public T onClaimsJwe(Jwe<Claims> jwe) {
throw new UnsupportedJwtException("Encrypted Claims JWTs are not supported.");
} }
} }

View File

@ -17,6 +17,7 @@ package io.jsonwebtoken;
import io.jsonwebtoken.io.Decoder; import io.jsonwebtoken.io.Decoder;
import io.jsonwebtoken.io.Deserializer; import io.jsonwebtoken.io.Deserializer;
import io.jsonwebtoken.security.SecurityException;
import io.jsonwebtoken.security.SignatureException; import io.jsonwebtoken.security.SignatureException;
import java.security.Key; import java.security.Key;
@ -28,9 +29,17 @@ import java.util.Map;
* *
* @since 0.1 * @since 0.1
*/ */
@SuppressWarnings("DeprecatedIsStillUsed")
public interface JwtParser { public interface JwtParser {
public static final char SEPARATOR_CHAR = '.'; /**
* Deprecated - this was an implementation detail accidentally added to the public interface. This
* will be removed in a future release.
*
* @deprecated since JJWT_RELEASE_VERSION, to be removed in a future relase.
*/
@Deprecated
char SEPARATOR_CHAR = '.';
/** /**
* Ensures that the specified {@code jti} exists in the parsed JWT. If missing or if the parsed * Ensures that the specified {@code jti} exists in the parsed JWT. If missing or if the parsed
@ -213,7 +222,7 @@ public interface JwtParser {
* @param key the algorithm-specific signature verification key used to validate any discovered JWS digital * @param key the algorithm-specific signature verification key used to validate any discovered JWS digital
* signature. * signature.
* @return the parser for method chaining. * @return the parser for method chaining.
* @deprecated see {@link JwtParserBuilder#setSigningKey(byte[])}. * @deprecated in favor of {@link JwtParserBuilder#verifyWith(Key)}.
* To construct a JwtParser use the corresponding builder via {@link Jwts#parserBuilder()}. This will construct an * To construct a JwtParser use the corresponding builder via {@link Jwts#parserBuilder()}. This will construct an
* immutable JwtParser. * immutable JwtParser.
* <p><b>NOTE: this method will be removed before version 1.0</b> * <p><b>NOTE: this method will be removed before version 1.0</b>
@ -259,7 +268,7 @@ public interface JwtParser {
* @param base64EncodedSecretKey the BASE64-encoded algorithm-specific signature verification key to use to validate * @param base64EncodedSecretKey the BASE64-encoded algorithm-specific signature verification key to use to validate
* any discovered JWS digital signature. * any discovered JWS digital signature.
* @return the parser for method chaining. * @return the parser for method chaining.
* @deprecated see {@link JwtParserBuilder#setSigningKey(String)}. * @deprecated in favor of {@link JwtParserBuilder#verifyWith(Key)}.
* To construct a JwtParser use the corresponding builder via {@link Jwts#parserBuilder()}. This will construct an * To construct a JwtParser use the corresponding builder via {@link Jwts#parserBuilder()}. This will construct an
* immutable JwtParser. * immutable JwtParser.
* <p><b>NOTE: this method will be removed before version 1.0</b> * <p><b>NOTE: this method will be removed before version 1.0</b>
@ -279,7 +288,7 @@ public interface JwtParser {
* @param key the algorithm-specific signature verification key to use to validate any discovered JWS digital * @param key the algorithm-specific signature verification key to use to validate any discovered JWS digital
* signature. * signature.
* @return the parser for method chaining. * @return the parser for method chaining.
* @deprecated see {@link JwtParserBuilder#setSigningKey(Key)}. * @deprecated in favor of {@link JwtParserBuilder#verifyWith(Key)}.
* To construct a JwtParser use the corresponding builder via {@link Jwts#parserBuilder()}. This will construct an * To construct a JwtParser use the corresponding builder via {@link Jwts#parserBuilder()}. This will construct an
* immutable JwtParser. * immutable JwtParser.
* <p><b>NOTE: this method will be removed before version 1.0</b> * <p><b>NOTE: this method will be removed before version 1.0</b>
@ -292,7 +301,7 @@ public interface JwtParser {
* a JWS's signature. If the parsed String is not a JWS (no signature), this resolver is not used. * a JWS's signature. If the parsed String is not a JWS (no signature), this resolver is not used.
* *
* <p>Specifying a {@code SigningKeyResolver} is necessary when the signing key is not already known before parsing * <p>Specifying a {@code SigningKeyResolver} is necessary when the signing key is not already known before parsing
* the JWT and the JWT header or payload (plaintext body or Claims) must be inspected first to determine how to * the JWT and the JWT header or payload (content byte array or Claims) must be inspected first to determine how to
* look up the signing key. Once returned by the resolver, the JwtParser will then verify the JWS signature with the * look up the signing key. Once returned by the resolver, the JwtParser will then verify the JWS signature with the
* returned key. For example:</p> * returned key. For example:</p>
* *
@ -314,22 +323,24 @@ public interface JwtParser {
* @param signingKeyResolver the signing key resolver used to retrieve the signing key. * @param signingKeyResolver the signing key resolver used to retrieve the signing key.
* @return the parser for method chaining. * @return the parser for method chaining.
* @since 0.4 * @since 0.4
* @deprecated see {@link JwtParserBuilder#setSigningKeyResolver(SigningKeyResolver)}. * @deprecated in favor of {@link JwtParserBuilder#setKeyLocator(Locator)}.
* To construct a JwtParser use the corresponding builder via {@link Jwts#parserBuilder()}. This will construct an * To construct a JwtParser use the corresponding builder via {@link Jwts#parserBuilder()}. This will construct an
* immutable JwtParser. * immutable JwtParser.
* <p><b>NOTE: this method will be removed before version 1.0</b> * <p><b>NOTE: this method will be removed before version 1.0</b>
*/ */
@SuppressWarnings("DeprecatedIsStillUsed")
@Deprecated @Deprecated
// TODO: remove for 1.0
JwtParser setSigningKeyResolver(SigningKeyResolver signingKeyResolver); JwtParser setSigningKeyResolver(SigningKeyResolver signingKeyResolver);
/** /**
* Sets the {@link CompressionCodecResolver} used to acquire the {@link CompressionCodec} that should be used to * Sets the {@link CompressionCodecResolver} used to acquire the {@link CompressionCodec} that should be used to
* decompress the JWT body. If the parsed JWT is not compressed, this resolver is not used. * decompress the JWT payload. If the parsed JWT is not compressed, this resolver is not used.
* *
* <p><b>NOTE:</b> Compression is not defined by the JWT Specification, and it is not expected that other libraries * <p><b>NOTE:</b> Compression is not defined by the JWS Specification - only the JWE Specification - and it is
* (including JJWT versions &lt; 0.6.0) are able to consume a compressed JWT body correctly. This method is only * not expected that other libraries (including JJWT versions &lt; 0.6.0) are able to consume a compressed JWS
* useful if the compact JWT was compressed with JJWT &gt;= 0.6.0 or another library that you know implements * payload correctly. This method is only useful if the compact JWT was compressed with JJWT &gt;= 0.6.0 or another
* the same behavior.</p> * library that you know implements the same behavior.</p>
* *
* <p><b>Default Support</b></p> * <p><b>Default Support</b></p>
* *
@ -341,10 +352,10 @@ public interface JwtParser {
* your own {@link CompressionCodecResolver} and specify that via this method and also when * your own {@link CompressionCodecResolver} and specify that via this method and also when
* {@link io.jsonwebtoken.JwtBuilder#compressWith(CompressionCodec) building} JWTs.</p> * {@link io.jsonwebtoken.JwtBuilder#compressWith(CompressionCodec) building} JWTs.</p>
* *
* @param compressionCodecResolver the compression codec resolver used to decompress the JWT body. * @param compressionCodecResolver the compression codec resolver used to decompress the JWT payload.
* @return the parser for method chaining. * @return the parser for method chaining.
* @since 0.6.0 * @since 0.6.0
* @deprecated see {@link JwtParserBuilder#setCompressionCodecResolver(CompressionCodecResolver)}. * @deprecated in favor of {@link JwtParserBuilder#setCompressionCodecLocator(Locator)}.
* To construct a JwtParser use the corresponding builder via {@link Jwts#parserBuilder()}. This will construct an * To construct a JwtParser use the corresponding builder via {@link Jwts#parserBuilder()}. This will construct an
* immutable JwtParser. * immutable JwtParser.
* <p><b>NOTE: this method will be removed before version 1.0</b> * <p><b>NOTE: this method will be removed before version 1.0</b>
@ -397,42 +408,45 @@ public interface JwtParser {
* <p>Note that if you are reasonably sure that the token is signed, it is more efficient to attempt to * <p>Note that if you are reasonably sure that the token is signed, it is more efficient to attempt to
* parse the token (and catching exceptions if necessary) instead of calling this method first before parsing.</p> * parse the token (and catching exceptions if necessary) instead of calling this method first before parsing.</p>
* *
* @param jwt the compact serialized JWT to check * @param compact the compact serialized JWT to check
* @return {@code true} if the specified JWT compact string represents a signed JWT (aka a 'JWS'), {@code false} * @return {@code true} if the specified JWT compact string represents a signed JWT (aka a 'JWS'), {@code false}
* otherwise. * otherwise.
*/ */
boolean isSigned(String jwt); boolean isSigned(String compact);
/** /**
* Parses the specified compact serialized JWT string based on the builder's current configuration state and * Parses the specified compact serialized JWT string based on the builder's current configuration state and
* returns the resulting JWT or JWS instance. * returns the resulting JWT, JWS, or JWE instance.
* *
* <p>This method returns a JWT or JWS based on the parsed string. Because it may be cumbersome to determine if it * <p>This method returns a JWT, JWS, or JWE based on the parsed string. Because it may be cumbersome to
* is a JWT or JWS, or if the body/payload is a Claims or String with {@code instanceof} checks, the * determine if it is a JWT, JWS or JWE, or if the payload is a Claims or byte array with {@code instanceof} checks,
* {@link #parse(String, JwtHandler) parse(String,JwtHandler)} method allows for a type-safe callback approach that * the {@link #parse(String, JwtHandler) parse(String,JwtHandler)} method allows for a type-safe callback approach
* may help reduce code or instanceof checks.</p> * that may help reduce code or instanceof checks.</p>
* *
* @param jwt the compact serialized JWT to parse * @param jwt the compact serialized JWT to parse
* @return the specified compact serialized JWT string based on the builder's current configuration state. * @return the specified compact serialized JWT string based on the builder's current configuration state.
* @throws MalformedJwtException if the specified JWT was incorrectly constructed (and therefore invalid). * @throws MalformedJwtException if the specified JWT was incorrectly constructed (and therefore invalid).
* Invalid * Invalid JWTs should not be trusted and should be discarded.
* JWTs should not be trusted and should be discarded.
* @throws SignatureException if a JWS signature was discovered, but could not be verified. JWTs that fail * @throws SignatureException if a JWS signature was discovered, but could not be verified. JWTs that fail
* signature validation should not be trusted and should be discarded. * signature validation should not be trusted and should be discarded.
* @throws SecurityException if the specified JWT string is a JWE and decryption fails
* @throws ExpiredJwtException if the specified JWT is a Claims JWT and the Claims has an expiration time * @throws ExpiredJwtException if the specified JWT is a Claims JWT and the Claims has an expiration time
* before the time this method is invoked. * before the time this method is invoked.
* @throws IllegalArgumentException if the specified string is {@code null} or empty or only whitespace. * @throws IllegalArgumentException if the specified string is {@code null} or empty or only whitespace.
* @see #parse(String, JwtHandler) * @see #parse(String, JwtHandler)
* @see #parsePlaintextJwt(String) * @see #parseContentJwt(String)
* @see #parseClaimsJwt(String) * @see #parseClaimsJwt(String)
* @see #parsePlaintextJws(String) * @see #parseContentJws(String)
* @see #parseClaimsJws(String) * @see #parseClaimsJws(String)
* @see #parseContentJwe(String)
* @see #parseClaimsJwe(String)
*/ */
Jwt parse(String jwt) throws ExpiredJwtException, MalformedJwtException, SignatureException, IllegalArgumentException; Jwt<?, ?> parse(String jwt) throws ExpiredJwtException, MalformedJwtException, SignatureException,
SecurityException, IllegalArgumentException;
/** /**
* Parses the specified compact serialized JWT string based on the builder's current configuration state and * Parses the specified compact serialized JWT string based on the builder's current configuration state and
* invokes the specified {@code handler} with the resulting JWT or JWS instance. * invokes the specified {@code handler} with the resulting JWT, JWS, or JWE instance.
* *
* <p>If you are confident of the format of the JWT before parsing, you can create an anonymous subclass using the * <p>If you are confident of the format of the JWT before parsing, you can create an anonymous subclass using the
* {@link io.jsonwebtoken.JwtHandlerAdapter JwtHandlerAdapter} and override only the methods you know are relevant * {@link io.jsonwebtoken.JwtHandlerAdapter JwtHandlerAdapter} and override only the methods you know are relevant
@ -453,145 +467,220 @@ public interface JwtParser {
* following convenience methods instead of this one:</p> * following convenience methods instead of this one:</p>
* *
* <ul> * <ul>
* <li>{@link #parsePlaintextJwt(String)}</li> * <li>{@link #parseContentJwt(String)}</li>
* <li>{@link #parseClaimsJwt(String)}</li> * <li>{@link #parseClaimsJwt(String)}</li>
* <li>{@link #parsePlaintextJws(String)}</li> * <li>{@link #parseContentJws(String)}</li>
* <li>{@link #parseClaimsJws(String)}</li> * <li>{@link #parseClaimsJws(String)}</li>
* <li>{@link #parseContentJwe(String)}</li>
* <li>{@link #parseClaimsJwe(String)}</li>
* </ul> * </ul>
* *
* @param jwt the compact serialized JWT to parse * @param jwt the compact serialized JWT to parse
* @param handler the handler to invoke when encountering a specific type of JWT * @param handler the handler to invoke when encountering a specific type of JWT
* @param <T> the type of object returned from the {@code handler} * @param <T> the type of object returned from the {@code handler}
* @return the result returned by the {@code JwtHandler} * @return the result returned by the {@code JwtHandler}
* @throws MalformedJwtException if the specified JWT was incorrectly constructed (and therefore invalid). * @throws MalformedJwtException if the specified JWT was incorrectly constructed (and therefore invalid).
* Invalid JWTs should not be trusted and should be discarded. * Invalid JWTs should not be trusted and should be discarded.
* @throws SignatureException if a JWS signature was discovered, but could not be verified. JWTs that fail * @throws SignatureException if a JWS signature was discovered, but could not be verified. JWTs that fail
* signature validation should not be trusted and should be discarded. * signature validation should not be trusted and should be discarded.
* @throws SecurityException if the specified JWT string is a JWE and decryption fails
* @throws ExpiredJwtException if the specified JWT is a Claims JWT and the Claims has an expiration time * @throws ExpiredJwtException if the specified JWT is a Claims JWT and the Claims has an expiration time
* before the time this method is invoked. * before the time this method is invoked.
* @throws IllegalArgumentException if the specified string is {@code null} or empty or only whitespace, or if the * @throws IllegalArgumentException if the specified string is {@code null} or empty or only whitespace, or if the
* {@code handler} is {@code null}. * {@code handler} is {@code null}.
* @see #parsePlaintextJwt(String) * @see #parseContentJwt(String)
* @see #parseClaimsJwt(String) * @see #parseClaimsJwt(String)
* @see #parsePlaintextJws(String) * @see #parseContentJws(String)
* @see #parseClaimsJws(String) * @see #parseClaimsJws(String)
* @see #parseContentJwe(String)
* @see #parseClaimsJwe(String)
* @see #parse(String) * @see #parse(String)
* @since 0.2 * @since 0.2
*/ */
<T> T parse(String jwt, JwtHandler<T> handler) <T> T parse(String jwt, JwtHandler<T> handler) throws ExpiredJwtException, UnsupportedJwtException,
throws ExpiredJwtException, UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException; MalformedJwtException, SignatureException, SecurityException, IllegalArgumentException;
/** /**
* Parses the specified compact serialized JWT string based on the builder's current configuration state and * Parses the specified compact serialized JWT string based on the builder's current configuration state and
* returns the resulting unsigned plaintext JWT instance. * returns the resulting unprotected content JWT instance. If the JWT creator set the (optional)
* {@link Header#getContentType() contentType} header value, the application may inspect that value to determine
* how to convert the byte array to the final content type as desired.
* *
* <p>This is a convenience method that is usable if you are confident that the compact string argument reflects an * <p>This is a convenience method that is usable if you are confident that the compact string argument reflects an
* unsigned plaintext JWT. An unsigned plaintext JWT has a String (non-JSON) body payload and it is not * unprotected content JWT. An unprotected content JWT has a byte array payload and it is not
* cryptographically signed.</p> * cryptographically signed or encrypted. If the JWT creator set the (optional)
* {@link Header#getContentType() contentType} header value, the application may inspect that value to determine
* how to convert the byte array to the final content type as desired.</p>
* *
* <p><b>If the compact string presented does not reflect an unsigned plaintext JWT with non-JSON string body, * <p><b>If the compact string presented does not reflect an unprotected content JWT with byte array payload,
* an {@link UnsupportedJwtException} will be thrown.</b></p> * an {@link UnsupportedJwtException} will be thrown.</b></p>
* *
* @param plaintextJwt a compact serialized unsigned plaintext JWT string. * @param jwt a compact serialized unprotected content JWT string.
* @return the {@link Jwt Jwt} instance that reflects the specified compact JWT string. * @return the {@link Jwt Jwt} instance that reflects the specified compact JWT string.
* @throws UnsupportedJwtException if the {@code plaintextJwt} argument does not represent an unsigned plaintext * @throws UnsupportedJwtException if the {@code jwt} argument does not represent an unprotected content JWT
* JWT * @throws MalformedJwtException if the {@code jwt} string is not a valid JWT
* @throws MalformedJwtException if the {@code plaintextJwt} string is not a valid JWT * @throws SignatureException if the {@code jwt} string is actually a JWS and signature validation fails
* @throws SignatureException if the {@code plaintextJwt} string is actually a JWS and signature validation * @throws SecurityException if the {@code jwt} string is actually a JWE and decryption fails
* fails * @throws IllegalArgumentException if the {@code jwt} string is {@code null} or empty or only whitespace
* @throws IllegalArgumentException if the {@code plaintextJwt} string is {@code null} or empty or only whitespace
* @see #parseClaimsJwt(String) * @see #parseClaimsJwt(String)
* @see #parsePlaintextJws(String) * @see #parseContentJws(String)
* @see #parseClaimsJws(String) * @see #parseClaimsJws(String)
* @see #parse(String, JwtHandler) * @see #parse(String, JwtHandler)
* @see #parse(String) * @see #parse(String)
* @since 0.2 * @since 0.2
*/ */
Jwt<Header, String> parsePlaintextJwt(String plaintextJwt) Jwt<UnprotectedHeader, byte[]> parseContentJwt(String jwt) throws UnsupportedJwtException, MalformedJwtException,
throws UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException; SignatureException, SecurityException, IllegalArgumentException;
/** /**
* Parses the specified compact serialized JWT string based on the builder's current configuration state and * Parses the specified compact serialized JWT string based on the builder's current configuration state and
* returns the resulting unsigned plaintext JWT instance. * returns the resulting unprotected Claims JWT instance.
* *
* <p>This is a convenience method that is usable if you are confident that the compact string argument reflects an * <p>This is a convenience method that is usable if you are confident that the compact string argument reflects an
* unsigned Claims JWT. An unsigned Claims JWT has a {@link Claims} body and it is not cryptographically * unprotected Claims JWT. An unprotected Claims JWT has a {@link Claims} payload and it is not cryptographically
* signed.</p> * signed or encrypted.</p>
* *
* <p><b>If the compact string presented does not reflect an unsigned Claims JWT, an * <p><b>If the compact string presented does not reflect an unprotected Claims JWT, an
* {@link UnsupportedJwtException} will be thrown.</b></p> * {@link UnsupportedJwtException} will be thrown.</b></p>
* *
* @param claimsJwt a compact serialized unsigned Claims JWT string. * @param jwt a compact serialized unprotected Claims JWT string.
* @return the {@link Jwt Jwt} instance that reflects the specified compact JWT string. * @return the {@link Jwt Jwt} instance that reflects the specified compact JWT string.
* @throws UnsupportedJwtException if the {@code claimsJwt} argument does not represent an unsigned Claims JWT * @throws UnsupportedJwtException if the {@code jwt} argument does not represent an unprotected Claims JWT
* @throws MalformedJwtException if the {@code claimsJwt} string is not a valid JWT * @throws MalformedJwtException if the {@code jwt} string is not a valid JWT
* @throws SignatureException if the {@code claimsJwt} string is actually a JWS and signature validation * @throws SignatureException if the {@code jwt} string is actually a JWS and signature validation fails
* fails * @throws SecurityException if the {@code jwt} string is actually a JWE and decryption fails
* @throws ExpiredJwtException if the specified JWT is a Claims JWT and the Claims has an expiration time * @throws ExpiredJwtException if the specified JWT is a Claims JWT and the Claims has an expiration time
* before the time this method is invoked. * before the time this method is invoked.
* @throws IllegalArgumentException if the {@code claimsJwt} string is {@code null} or empty or only whitespace * @throws IllegalArgumentException if the {@code jwt} string is {@code null} or empty or only whitespace
* @see #parsePlaintextJwt(String) * @see #parseContentJwt(String)
* @see #parsePlaintextJws(String) * @see #parseContentJws(String)
* @see #parseClaimsJws(String) * @see #parseClaimsJws(String)
* @see #parse(String, JwtHandler) * @see #parse(String, JwtHandler)
* @see #parse(String) * @see #parse(String)
* @since 0.2 * @since 0.2
*/ */
Jwt<Header, Claims> parseClaimsJwt(String claimsJwt) Jwt<UnprotectedHeader, Claims> parseClaimsJwt(String jwt) throws ExpiredJwtException, UnsupportedJwtException,
throws ExpiredJwtException, UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException; MalformedJwtException, SignatureException, SecurityException, IllegalArgumentException;
/** /**
* Parses the specified compact serialized JWS string based on the builder's current configuration state and * Parses the specified compact serialized JWS string based on the builder's current configuration state and
* returns the resulting plaintext JWS instance. * returns the resulting content JWS instance. If the JWT creator set the (optional)
* {@link Header#getContentType() contentType} header value, the application may inspect that value to determine
* how to convert the byte array to the final content type as desired.
* *
* <p>This is a convenience method that is usable if you are confident that the compact string argument reflects a * <p>This is a convenience method that is usable if you are confident that the compact string argument reflects a
* plaintext JWS. A plaintext JWS is a JWT with a String (non-JSON) body (payload) that has been * content JWS. A content JWS is a JWT with a byte array payload that has been cryptographically signed.</p>
* cryptographically signed.</p>
* *
* <p><b>If the compact string presented does not reflect a plaintext JWS, an {@link UnsupportedJwtException} * <p><b>If the compact string presented does not reflect a content JWS, an {@link UnsupportedJwtException}
* will be thrown.</b></p> * will be thrown.</b></p>
* *
* @param plaintextJws a compact serialized JWS string. * @param jws a compact serialized JWS string.
* @return the {@link Jws Jws} instance that reflects the specified compact JWS string. * @return the {@link Jws Jws} instance that reflects the specified compact JWS string.
* @throws UnsupportedJwtException if the {@code plaintextJws} argument does not represent an plaintext JWS * @throws UnsupportedJwtException if the {@code jws} argument does not represent a content JWS
* @throws MalformedJwtException if the {@code plaintextJws} string is not a valid JWS * @throws MalformedJwtException if the {@code jws} string is not a valid JWS
* @throws SignatureException if the {@code plaintextJws} JWS signature validation fails * @throws SignatureException if the {@code jws} JWS signature validation fails
* @throws IllegalArgumentException if the {@code plaintextJws} string is {@code null} or empty or only whitespace * @throws SecurityException if the {@code jws} string is actually a JWE and decryption fails
* @see #parsePlaintextJwt(String) * @throws IllegalArgumentException if the {@code jws} string is {@code null} or empty or only whitespace
* @see #parseContentJwt(String)
* @see #parseContentJwe(String)
* @see #parseClaimsJwt(String) * @see #parseClaimsJwt(String)
* @see #parseClaimsJws(String) * @see #parseClaimsJws(String)
* @see #parseClaimsJwe(String)
* @see #parse(String, JwtHandler) * @see #parse(String, JwtHandler)
* @see #parse(String) * @see #parse(String)
* @since 0.2 * @since 0.2
*/ */
Jws<String> parsePlaintextJws(String plaintextJws) Jws<byte[]> parseContentJws(String jws) throws UnsupportedJwtException, MalformedJwtException, SignatureException,
throws UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException; SecurityException, IllegalArgumentException;
/** /**
* Parses the specified compact serialized JWS string based on the builder's current configuration state and * Parses the specified compact serialized JWS string based on the builder's current configuration state and
* returns the resulting Claims JWS instance. * returns the resulting Claims JWS instance.
* *
* <p>This is a convenience method that is usable if you are confident that the compact string argument reflects a * <p>This is a convenience method that is usable if you are confident that the compact string argument reflects a
* Claims JWS. A Claims JWS is a JWT with a {@link Claims} body that has been cryptographically signed.</p> * Claims JWS. A Claims JWS is a JWT with a {@link Claims} payload that has been cryptographically signed.</p>
* *
* <p><b>If the compact string presented does not reflect a Claims JWS, an {@link UnsupportedJwtException} will be * <p><b>If the compact string presented does not reflect a Claims JWS, an {@link UnsupportedJwtException} will be
* thrown.</b></p> * thrown.</b></p>
* *
* @param claimsJws a compact serialized Claims JWS string. * @param jws a compact serialized Claims JWS string.
* @return the {@link Jws Jws} instance that reflects the specified compact Claims JWS string. * @return the {@link Jws Jws} instance that reflects the specified compact Claims JWS string.
* @throws UnsupportedJwtException if the {@code claimsJws} argument does not represent an Claims JWS * @throws UnsupportedJwtException if the {@code claimsJws} argument does not represent an Claims JWS
* @throws MalformedJwtException if the {@code claimsJws} string is not a valid JWS * @throws MalformedJwtException if the {@code claimsJws} string is not a valid JWS
* @throws SignatureException if the {@code claimsJws} JWS signature validation fails * @throws SignatureException if the {@code claimsJws} JWS signature validation fails
* @throws SecurityException if the {@code jws} string is actually a JWE and decryption fails
* @throws ExpiredJwtException if the specified JWT is a Claims JWT and the Claims has an expiration time * @throws ExpiredJwtException if the specified JWT is a Claims JWT and the Claims has an expiration time
* before the time this method is invoked. * before the time this method is invoked.
* @throws IllegalArgumentException if the {@code claimsJws} string is {@code null} or empty or only whitespace * @throws IllegalArgumentException if the {@code claimsJws} string is {@code null} or empty or only whitespace
* @see #parsePlaintextJwt(String) * @see #parseContentJwt(String)
* @see #parseContentJws(String)
* @see #parseContentJwe(String)
* @see #parseClaimsJwt(String) * @see #parseClaimsJwt(String)
* @see #parsePlaintextJws(String) * @see #parseClaimsJwe(String)
* @see #parse(String, JwtHandler) * @see #parse(String, JwtHandler)
* @see #parse(String) * @see #parse(String)
* @since 0.2 * @since 0.2
*/ */
Jws<Claims> parseClaimsJws(String claimsJws) Jws<Claims> parseClaimsJws(String jws) throws ExpiredJwtException, UnsupportedJwtException, MalformedJwtException,
throws ExpiredJwtException, UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException; SignatureException, SecurityException, IllegalArgumentException;
/**
* Parses the specified compact serialized JWE string based on the builder's current configuration state and
* returns the resulting content JWE instance. If the JWT creator set the (optional)
* {@link Header#getContentType() contentType} header value, the application may inspect that value to determine
* how to convert the byte array to the final content type as desired.
*
* <p>This is a convenience method that is usable if you are confident that the compact string argument reflects a
* content JWE. A content JWE is a JWT with a byte array payload that has been encrypted.</p>
*
* <p><b>If the compact string presented does not reflect a content JWE, an {@link UnsupportedJwtException}
* will be thrown.</b></p>
*
* @param jwe a compact serialized JWE string.
* @return the {@link Jwe Jwe} instance that reflects the specified compact JWE string.
* @throws UnsupportedJwtException if the {@code jwe} argument does not represent a content JWE
* @throws MalformedJwtException if the {@code jwe} string is not a valid JWE
* @throws SecurityException if the {@code jwe} JWE decryption fails
* @throws IllegalArgumentException if the {@code jwe} string is {@code null} or empty or only whitespace
* @see #parseContentJwt(String)
* @see #parseContentJws(String)
* @see #parseClaimsJwt(String)
* @see #parseClaimsJws(String)
* @see #parseClaimsJwe(String)
* @see #parse(String, JwtHandler)
* @see #parse(String)
* @since JJWT_RELEASE_VERSION
*/
Jwe<byte[]> parseContentJwe(String jwe) throws ExpiredJwtException, UnsupportedJwtException, MalformedJwtException,
SecurityException, IllegalArgumentException;
/**
* Parses the specified compact serialized JWE string based on the builder's current configuration state and
* returns the resulting Claims JWE instance.
*
* <p>This is a convenience method that is usable if you are confident that the compact string argument reflects a
* Claims JWE. A Claims JWE is a JWT with a {@link Claims} payload that has been encrypted.</p>
*
* <p><b>If the compact string presented does not reflect a Claims JWE, an {@link UnsupportedJwtException} will be
* thrown.</b></p>
*
* @param jwe a compact serialized Claims JWE string.
* @return the {@link Jwe Jwe} instance that reflects the specified compact Claims JWE string.
* @throws UnsupportedJwtException if the {@code claimsJwe} argument does not represent a Claims JWE
* @throws MalformedJwtException if the {@code claimsJwe} string is not a valid JWE
* @throws SignatureException if the {@code claimsJwe} JWE decryption fails
* @throws ExpiredJwtException if the specified JWT is a Claims JWE and the Claims has an expiration time
* before the time this method is invoked.
* @throws IllegalArgumentException if the {@code claimsJwe} string is {@code null} or empty or only whitespace
* @see #parseContentJwt(String)
* @see #parseContentJws(String)
* @see #parseContentJwe(String)
* @see #parseClaimsJwt(String)
* @see #parseClaimsJws(String)
* @see #parse(String, JwtHandler)
* @see #parse(String)
* @since JJWT_RELEASE_VERSION
*/
Jwe<Claims> parseClaimsJwe(String jwe) throws ExpiredJwtException, UnsupportedJwtException, MalformedJwtException,
SecurityException, IllegalArgumentException;
} }

View File

@ -17,8 +17,16 @@ package io.jsonwebtoken;
import io.jsonwebtoken.io.Decoder; import io.jsonwebtoken.io.Decoder;
import io.jsonwebtoken.io.Deserializer; import io.jsonwebtoken.io.Deserializer;
import io.jsonwebtoken.lang.Builder;
import io.jsonwebtoken.security.AeadAlgorithm;
import io.jsonwebtoken.security.KeyAlgorithm;
import io.jsonwebtoken.security.SecureDigestAlgorithm;
import io.jsonwebtoken.security.StandardKeyAlgorithms;
import io.jsonwebtoken.security.StandardSecureDigestAlgorithms;
import java.security.Key; import java.security.Key;
import java.security.Provider;
import java.util.Collection;
import java.util.Date; import java.util.Date;
import java.util.Map; import java.util.Map;
@ -26,8 +34,8 @@ import java.util.Map;
* A builder to construct a {@link JwtParser}. Example usage: * A builder to construct a {@link JwtParser}. Example usage:
* <pre>{@code * <pre>{@code
* Jwts.parserBuilder() * Jwts.parserBuilder()
* .setSigningKey(...)
* .requireIssuer("https://issuer.example.com") * .requireIssuer("https://issuer.example.com")
* .verifyWith(...)
* .build() * .build()
* .parse(jwtString) * .parse(jwtString)
* }</pre> * }</pre>
@ -35,7 +43,65 @@ import java.util.Map;
* @since 0.11.0 * @since 0.11.0
*/ */
@SuppressWarnings("JavadocLinkAsPlainText") @SuppressWarnings("JavadocLinkAsPlainText")
public interface JwtParserBuilder { public interface JwtParserBuilder extends Builder<JwtParser> {
/**
* Enables parsing of Unsecured JWSs (JWTs with an 'alg' (Algorithm) header value of
* 'none'). <b>Be careful when calling this method - one should fully understand
* <a href="https://www.rfc-editor.org/rfc/rfc7518.html#section-8.5">Unsecured JWS Security Considerations</a>
* before enabling this feature.</b>
* <p>If this method is not called, Unsecured JWSs are disabled by default as mandated by
* <a href="https://www.rfc-editor.org/rfc/rfc7518.html#section-3.6">RFC 7518, Section
* 3.6</a>.</p>
*
* @return the builder for method chaining.
* @see <a href="https://www.rfc-editor.org/rfc/rfc7518.html#section-8.5">Unsecured JWS Security Considerations</a>
* @see <a href="https://www.rfc-editor.org/rfc/rfc7518.html#section-3.6">Using the Algorithm &quot;none&quot;</a>
* @see StandardSecureDigestAlgorithms#NONE
* @see #enableUnsecuredDecompression()
* @since JJWT_RELEASE_VERSION
*/
JwtParserBuilder enableUnsecuredJws();
/**
* If {@link #enableUnsecuredJws() enabledUnsecuredJws} is enabled, calling this method additionally enables
* payload decompression of Unsecured JWSs (JWTs with an 'alg' (Algorithm) header value of 'none') that also have
* a 'zip' (Compression) header. This behavior is disabled by default because using compression
* algorithms with data from unverified (unauthenticated) parties can be susceptible to Denial of Service attacks
* and other data integrity problems as described in
* <a href="https://www.usenix.org/system/files/conference/usenixsecurity15/sec15-paper-pellegrino.pdf">In the
* Compression Hornets Nest: A Security Study of Data Compression in Network Services</a>.
*
* <p>Because this behavior is only relevant if {@link #enableUnsecuredJws() enabledUnsecuredJws} is specified,
* calling this method without also calling {@code enableUnsecuredJws()} will result in a build exception, as the
* incongruent state could reflect a misunderstanding of both behaviors which should be remedied by the
* application developer.</p>
*
* <b>As is the case for {@link #enableUnsecuredJws()}, be careful when calling this method - one should fully
* understand
* <a href="https://www.rfc-editor.org/rfc/rfc7518.html#section-8.5">Unsecured JWS Security Considerations</a>
* before enabling this feature.</b>
*
* @return the builder for method chaining.
* @see <a href="https://www.rfc-editor.org/rfc/rfc7518.html#section-8.5">Unsecured JWS Security Considerations</a>
* @see <a href="https://www.usenix.org/system/files/conference/usenixsecurity15/sec15-paper-pellegrino.pdf">In the
* Compression Hornets Nest: A Security Study of Data Compression in Network Services</a>
* @see StandardSecureDigestAlgorithms#NONE
* @see #enableUnsecuredJws()
* @since JJWT_RELEASE_VERSION
*/
JwtParserBuilder enableUnsecuredDecompression();
/**
* Sets the JCA Provider to use during cryptographic signature and decryption operations, or {@code null} if the
* JCA subsystem preferred provider should be used.
*
* @param provider the JCA Provider to use during cryptographic signature and decryption operations, or {@code null}
* if the JCA subsystem preferred provider should be used.
* @return the builder for method chaining.
* @since JJWT_RELEASE_VERSION
*/
JwtParserBuilder setProvider(Provider provider);
/** /**
* Ensures that the specified {@code jti} exists in the parsed JWT. If missing or if the parsed * Ensures that the specified {@code jti} exists in the parsed JWT. If missing or if the parsed
@ -156,8 +222,17 @@ public interface JwtParserBuilder {
JwtParserBuilder setAllowedClockSkewSeconds(long seconds) throws IllegalArgumentException; JwtParserBuilder setAllowedClockSkewSeconds(long seconds) throws IllegalArgumentException;
/** /**
* Sets the signing key used to verify any discovered JWS digital signature. If the specified JWT string is not * <p><b>Deprecation Notice</b></p>
* a JWS (no signature), this key is not used. *
* <p>This method has been deprecated since JJWT_RELEASE_VERSION and will be removed before 1.0. It was not
* readily obvious to many JJWT users that this method was for bytes that pertained <em>only</em> to HMAC
* {@code SecretKey}s, and could be confused with keys of other types. It is better to obtain a type-safe
* {@link Key} instance and call the {@link #verifyWith(Key)} instead.</p>
*
* <p>Previous Documentation</p>
*
* <p>Sets the signing key used to verify any discovered JWS digital signature. If the specified JWT string is not
* a JWS (no signature), this key is not used.</p>
* *
* <p>Note that this key <em>MUST</em> be a valid key for the signature algorithm found in the JWT header * <p>Note that this key <em>MUST</em> be a valid key for the signature algorithm found in the JWT header
* (as the {@code alg} header parameter).</p> * (as the {@code alg} header parameter).</p>
@ -167,21 +242,13 @@ public interface JwtParserBuilder {
* @param key the algorithm-specific signature verification key used to validate any discovered JWS digital * @param key the algorithm-specific signature verification key used to validate any discovered JWS digital
* signature. * signature.
* @return the parser builder for method chaining. * @return the parser builder for method chaining.
* @deprecated since JJWT_RELEASE_VERSION in favor of {@link #verifyWith(Key)} for type safety and name congruence
* with the {@link #decryptWith(Key)} method.
*/ */
@Deprecated
JwtParserBuilder setSigningKey(byte[] key); JwtParserBuilder setSigningKey(byte[] key);
/** /**
* Sets the signing key used to verify any discovered JWS digital signature. If the specified JWT string is not
* a JWS (no signature), this key is not used.
*
* <p>Note that this key <em>MUST</em> be a valid key for the signature algorithm found in the JWT header
* (as the {@code alg} header parameter).</p>
*
* <p>This method overwrites any previously set key.</p>
*
* <p>This is a convenience method: the string argument is first BASE64-decoded to a byte array and this resulting
* byte array is used to invoke {@link #setSigningKey(byte[])}.</p>
*
* <p><b>Deprecation Notice: Deprecated as of 0.10.0, will be removed in 1.0.0</b></p> * <p><b>Deprecation Notice: Deprecated as of 0.10.0, will be removed in 1.0.0</b></p>
* *
* <p>This method has been deprecated because the {@code key} argument for this method can be confusing: keys for * <p>This method has been deprecated because the {@code key} argument for this method can be confusing: keys for
@ -202,39 +269,140 @@ public interface JwtParserBuilder {
* StackOverflow answer</a> explaining why raw (non-base64-encoded) strings are almost always incorrect for * StackOverflow answer</a> explaining why raw (non-base64-encoded) strings are almost always incorrect for
* signature operations.</p> * signature operations.</p>
* *
* <p>Finally, please use the {@link #setSigningKey(Key) setSigningKey(Key)} instead, as this method (and likely the * <p>Finally, please use the {@link #verifyWith(Key)} method instead, as this method (and likely
* {@code byte[]} variant) will be removed before the 1.0.0 release.</p> * {@link #setSigningKey(byte[])}) will be removed before the 1.0.0 release.</p>
* *
* @param base64EncodedSecretKey the BASE64-encoded algorithm-specific signature verification key to use to validate * <p><b>Previous JavaDoc</b></p>
* any discovered JWS digital signature. *
* <p>This is a convenience method that equates to the following:</p>
*
* <blockquote><pre>
* byte[] bytes = Decoders.{@link io.jsonwebtoken.io.Decoders#BASE64 BASE64}.decode(base64EncodedSecretKey);
* Key key = Keys.{@link io.jsonwebtoken.security.Keys#hmacShaKeyFor(byte[]) hmacShaKeyFor}(bytes);
* return {@link #verifyWith(Key) verifyWith}(key);</pre></blockquote>
*
* @param base64EncodedSecretKey BASE64-encoded HMAC-SHA key bytes used to create a Key which will be used to
* verify all encountered JWS digital signatures.
* @return the parser builder for method chaining. * @return the parser builder for method chaining.
* @deprecated in favor of {@link #setSigningKey(Key)} as explained in the above <b>Deprecation Notice</b>, * @deprecated in favor of {@link #verifyWith(Key)} as explained in the above <b>Deprecation Notice</b>,
* and will be removed in 1.0.0. * and will be removed in 1.0.0.
*/ */
@Deprecated @Deprecated
JwtParserBuilder setSigningKey(String base64EncodedSecretKey); JwtParserBuilder setSigningKey(String base64EncodedSecretKey);
/** /**
* Sets the signing key used to verify any discovered JWS digital signature. If the specified JWT string is not * <p><b>Deprecation Notice</b></p>
* a JWS (no signature), this key is not used.
* *
* <p>Note that this key <em>MUST</em> be a valid key for the signature algorithm found in the JWT header * <p>This method is being renamed to accurately reflect its purpose - the key is not technically a signing key,
* (as the {@code alg} header parameter).</p> * it is a signature verification key, and the two concepts can be different, especially with asymmetric key
* cryptography. The method has been deprecated since JJWT_RELEASE_VERSION in favor of
* {@link #verifyWith(Key)} for type safety, to reflect accurate naming of the concept, and for name congruence
* with the {@link #decryptWith(Key)} method.</p>
* *
* <p>This method overwrites any previously set key.</p> * <p>This method merely delegates directly to {@link #verifyWith(Key)}.</p>
* *
* @param key the algorithm-specific signature verification key to use to validate any discovered JWS digital * @param key the algorithm-specific signature verification key to use to verify all encountered JWS digital
* signature. * signatures.
* @return the parser builder for method chaining. * @return the parser builder for method chaining.
* @deprecated since JJWT_RELEASE_VERSION in favor of {@link #verifyWith(Key)} for naming congruence with the
* {@link #decryptWith(Key)} method.
*/ */
@Deprecated
JwtParserBuilder setSigningKey(Key key); JwtParserBuilder setSigningKey(Key key);
/** /**
* Sets the {@link SigningKeyResolver} used to acquire the <code>signing key</code> that should be used to verify * Sets the signature verification key used to verify all encountered JWS signatures. If the encountered JWT
* a JWS's signature. If the parsed String is not a JWS (no signature), this resolver is not used. * string is not a JWS (e.g. unsigned or a JWE), this key is not used.
*
* <p>This is a convenience method to use in a specific scenario: when the parser will only ever encounter
* JWSs with signatures that can always be verified by a single key. This also implies that this key
* <em>MUST</em> be a valid key for the signature algorithm ({@code alg} header) used for the JWS.</p>
*
* <p>If there is any chance that the parser will encounter JWSs
* that need different signature verification keys based on the JWS being parsed, or JWEs, it is strongly
* recommended to configure your own {@link Locator} via the
* {@link #setKeyLocator(Locator) setKeyLocator} method instead of using this one.</p>
*
* <p>Calling this method overrides any previously set signature verification key.</p>
*
* @param key the signature verification key to use to verify all encountered JWS digital signatures.
* @return the parser builder for method chaining.
* @since JJWT_RELEASE_VERSION
*/
JwtParserBuilder verifyWith(Key key);
/**
* Sets the decryption key to be used to decrypt all encountered JWEs. If the encountered JWT string is not a
* JWE (e.g. a JWS), this key is not used.
*
* <p>This is a convenience method to use in specific circumstances: when the parser will only ever encounter
* JWEs that can always be decrypted by a single key. This also implies that this key <em>MUST</em> be a valid
* key for both the key management algorithm ({@code alg} header) and the content encryption algorithm
* ({@code enc} header) used for the JWE.</p>
*
* <p>If there is any chance that the parser will encounter JWEs that need different decryption keys based on the
* JWE being parsed, or JWSs, it is strongly recommended to configure
* your own {@link Locator Locator} via the {@link #setKeyLocator(Locator) setKeyLocator} method instead of
* using this one.</p>
*
* <p>Calling this method overrides any previously set decryption key.</p>
*
* @param key the algorithm-specific decryption key to use to decrypt all encountered JWEs.
* @return the parser builder for method chaining.
* @since JJWT_RELEASE_VERSION
*/
JwtParserBuilder decryptWith(Key key);
/**
* Sets the {@link Locator} used to acquire any signature verification or decryption key needed during parsing.
* <ul>
* <li>If the parsed String is a JWS, the {@code Locator} will be called to find the appropriate key
* necessary to verify the JWS signature.</li>
* <li>If the parsed String is a JWE, it will be called to find the appropriate decryption key.</li>
* </ul>
*
* <p>Specifying a key {@code Locator} is necessary when the signing or decryption key is not already known before
* parsing the JWT and the JWT header must be inspected first to determine how to
* look up the verification or decryption key. Once returned by the locator, the JwtParser will then either
* verify the JWS signature or decrypt the JWE payload with the returned key. For example:</p>
*
* <pre>
* Jws&lt;Claims&gt; jws = Jwts.parserBuilder().setKeyLocator(new Locator&lt;Key&gt;() {
* &#64;Override
* public Key locate(Header&lt;?&gt; header) {
* if (header instanceof JwsHeader) {
* return getSignatureVerificationKey((JwsHeader)header); // implement me
* } else {
* return getDecryptionKey((JweHeader)header); // implement me
* }
* }})
* .build()
* .parseClaimsJws(compact);
* </pre>
*
* <p>A Key {@code Locator} is invoked once during parsing before performing decryption or signature verification.</p>
*
* @param keyLocator the locator used to retrieve decryption or signature verification keys.
* @return the parser builder for method chaining.
* @since JJWT_RELEASE_VERSION
*/
JwtParserBuilder setKeyLocator(Locator<Key> keyLocator);
/**
* <p><b>Deprecation Notice</b></p>
*
* <p>This method has been deprecated as of JJWT version JJWT_RELEASE_VERSION because it only supports key location
* for JWSs (signed JWTs) instead of both signed (JWS) and encrypted (JWE) scenarios. Use the
* {@link #setKeyLocator(Locator) setKeyLocator} method instead to ensure a locator that can work for both JWS and
* JWE inputs. This method will be removed for the 1.0 release.</p>
*
* <p><b>Previous Documentation</b></p>
*
* <p>Sets the {@link SigningKeyResolver} used to acquire the <code>signing key</code> that should be used to verify
* a JWS's signature. If the parsed String is not a JWS (no signature), this resolver is not used.</p>
* *
* <p>Specifying a {@code SigningKeyResolver} is necessary when the signing key is not already known before parsing * <p>Specifying a {@code SigningKeyResolver} is necessary when the signing key is not already known before parsing
* the JWT and the JWT header or payload (plaintext body or Claims) must be inspected first to determine how to * the JWT and the JWT header or payload (content byte array or Claims) must be inspected first to determine how to
* look up the signing key. Once returned by the resolver, the JwtParser will then verify the JWS signature with the * look up the signing key. Once returned by the resolver, the JwtParser will then verify the JWS signature with the
* returned key. For example:</p> * returned key. For example:</p>
* *
@ -250,22 +418,104 @@ public interface JwtParserBuilder {
* *
* <p>A {@code SigningKeyResolver} is invoked once during parsing before the signature is verified.</p> * <p>A {@code SigningKeyResolver} is invoked once during parsing before the signature is verified.</p>
* *
* <p>This method should only be used if a signing key is not provided by the other {@code setSigningKey*} builder
* methods.</p>
*
* @param signingKeyResolver the signing key resolver used to retrieve the signing key. * @param signingKeyResolver the signing key resolver used to retrieve the signing key.
* @return the parser builder for method chaining. * @return the parser builder for method chaining.
* @deprecated since JJWT_RELEASE_VERSION in favor of {@link #setKeyLocator(Locator)}
*/ */
@SuppressWarnings("DeprecatedIsStillUsed")
@Deprecated
JwtParserBuilder setSigningKeyResolver(SigningKeyResolver signingKeyResolver); JwtParserBuilder setSigningKeyResolver(SigningKeyResolver signingKeyResolver);
/**
* Adds the specified compression codecs to the parser's total set of supported compression codecs,
* overwriting any previously-added compression codecs with the same {@link CompressionCodec#getId() id}s. If the
* parser encounters a JWT {@code zip} header value that matches a compression codec's
* {@link CompressionCodec#getId() CompressionCodec.getId()}, that codec will be used for decompression.
*
* <p>There may be only one registered {@code CompressionCodec} per {@code id}, and the {@code codecs}
* collection is added in iteration order; if a duplicate id is found when iterating the {@code codecs}
* collection, the later element will evict any previously-added algorithm with the same {@code id}.</p>
*
* <p>Finally, {@link CompressionCodecs#DEFLATE} and {@link CompressionCodecs#GZIP} are added last,
* <em>after</em> those in the {@code codecs} collection, to ensure that JWA standard algorithms cannot be
* accidentally replaced.</p>
*
* <p>This method is a simpler alternative than creating and registering a custom locator via the
* {@link #setCompressionCodecLocator(Locator)} method.</p>
*
* @param codecs collection of compression codecs to add to the parser's total set of supported compression codecs.
* @return the builder for method chaining.
* @since JJWT_RELEASE_VERSION
*/
JwtParserBuilder addCompressionCodecs(Collection<? extends CompressionCodec> codecs);
/**
* Adds the specified AEAD encryption algorithms to the parser's total set of supported encryption algorithms,
* overwriting any previously-added algorithms with the same {@link AeadAlgorithm#getId() id}s.
*
* <p>There may be only one registered {@code AeadAlgorithm} per algorithm {@code id}, and the {@code encAlgs}
* collection is added in iteration order; if a duplicate id is found when iterating the {@code encAlgs}
* collection, the later element will evict any previously-added algorithm with the same {@code id}.</p>
*
* <p>Finally, the {@link Jwts#ENC JWA standard encryption algorithms} are added last,
* <em>after</em> those in the {@code encAlgs} collection, to ensure that JWA standard algorithms cannot be
* accidentally replaced.</p>
*
* @param encAlgs collection of AEAD encryption algorithms to add to the parser's total set of supported
* encryption algorithms.
* @return the builder for method chaining.
* @since JJWT_RELEASE_VERSION
*/
JwtParserBuilder addEncryptionAlgorithms(Collection<? extends AeadAlgorithm> encAlgs);
/**
* Adds the specified signature algorithms to the parser's total set of supported signature algorithms,
* overwriting any previously-added algorithms with the same
* {@link Identifiable#getId() id}s.
*
* <p>There may be only one registered {@code SecureDigestAlgorithm} per algorithm {@code id}, and the
* {@code sigAlgs} collection is added in iteration order; if a duplicate id is found when iterating the
* {@code sigAlgs} collection, the later element will evict any previously-added algorithm with the same
* {@code id}.</p>
*
* <p>Finally, the {@link Jwts#SIG JWA standard signature and MAC algorithms} are
* added last, <em>after</em> those in the {@code sigAlgs} collection, to ensure that JWA standard algorithms
* cannot be accidentally replaced.</p>
*
* @param sigAlgs collection of signing algorithms to add to the parser's total set of supported signature
* algorithms.
* @return the builder for method chaining.
* @since JJWT_RELEASE_VERSION
*/
JwtParserBuilder addSignatureAlgorithms(Collection<? extends SecureDigestAlgorithm<?, ?>> sigAlgs);
/**
* Adds the specified key management algorithms to the parser's total set of supported key algorithms,
* overwriting any previously-added algorithms with the same {@link KeyAlgorithm#getId() id}s.
*
* <p>There may be only one registered {@code KeyAlgorithm} per algorithm {@code id}, and the {@code keyAlgs}
* collection is added in iteration order; if a duplicate id is found when iterating the {@code keyAlgs}
* collection, the later element will evict any previously-added algorithm with the same {@code id}.</p>
*
* <p>Finally, the {@link StandardKeyAlgorithms#values() JWA standard key management algorithms}
* are added last, <em>after</em> those in the {@code keyAlgs} collection, to ensure that JWA standard algorithms
* cannot be accidentally replaced.</p>
*
* @param keyAlgs collection of key management algorithms to add to the parser's total set of supported key
* management algorithms.
* @return the builder for method chaining.
* @since JJWT_RELEASE_VERSION
*/
JwtParserBuilder addKeyAlgorithms(Collection<? extends KeyAlgorithm<?, ?>> keyAlgs);
/** /**
* Sets the {@link CompressionCodecResolver} used to acquire the {@link CompressionCodec} that should be used to * Sets the {@link CompressionCodecResolver} used to acquire the {@link CompressionCodec} that should be used to
* decompress the JWT body. If the parsed JWT is not compressed, this resolver is not used. * decompress the JWT body. If the parsed JWT is not compressed, this resolver is not used.
* *
* <p><b>NOTE:</b> Compression is not defined by the JWT Specification, and it is not expected that other libraries * <p><b>NOTE:</b> Compression is not defined by the JWS Specification - only the JWE Specification - and it is
* (including JJWT versions &lt; 0.6.0) are able to consume a compressed JWT body correctly. This method is only * not expected that other libraries (including JJWT versions &lt; 0.6.0) are able to consume a compressed JWS
* useful if the compact JWT was compressed with JJWT &gt;= 0.6.0 or another library that you know implements * body correctly. This method is only useful if the compact JWS was compressed with JJWT &gt;= 0.6.0 or
* the same behavior.</p> * another library that you know implements the same behavior.</p>
* *
* <p><b>Default Support</b></p> * <p><b>Default Support</b></p>
* *
@ -274,15 +524,55 @@ public interface JwtParserBuilder {
* and {@link CompressionCodecs#GZIP GZIP} algorithms by default - you do not need to * and {@link CompressionCodecs#GZIP GZIP} algorithms by default - you do not need to
* specify a {@code CompressionCodecResolver} in these cases.</p> * specify a {@code CompressionCodecResolver} in these cases.</p>
* *
* <p>However, if you want to use a compression algorithm other than {@code DEF} or {@code GZIP}, you must implement * <p>However, if you want to use a compression algorithm other than {@code DEF} or {@code GZIP}, you must
* your own {@link CompressionCodecResolver} and specify that via this method and also when * implement your own {@link CompressionCodecResolver} and specify that via this method and also when
* {@link io.jsonwebtoken.JwtBuilder#compressWith(CompressionCodec) building} JWTs.</p> * {@link io.jsonwebtoken.JwtBuilder#compressWith(CompressionCodec) building} JWTs.</p>
* *
* @param compressionCodecResolver the compression codec resolver used to decompress the JWT body. * @param compressionCodecResolver the compression codec resolver used to decompress the JWT body.
* @return the parser builder for method chaining. * @return the parser builder for method chaining.
* @deprecated since JJWT_RELEASE_VERSION in favor of {@link #setCompressionCodecLocator(Locator)} to use the
* congruent {@code Locator} concept used elsewhere (such as {@link #setKeyLocator(Locator)}).
*/ */
@Deprecated
JwtParserBuilder setCompressionCodecResolver(CompressionCodecResolver compressionCodecResolver); JwtParserBuilder setCompressionCodecResolver(CompressionCodecResolver compressionCodecResolver);
/**
* Sets the {@link CompressionCodec} {@code Locator} used to acquire the {@code CompressionCodec} that should be
* used to decompress the JWT body.
*
* <p><b>NOTE:</b> Compression is not defined by the JWS Specification - only the JWE Specification - and it is
* not expected that other libraries (including JJWT versions &lt; 0.6.0) are able to consume a compressed JWS
* body correctly. This method is only useful if the compact JWS was compressed with JJWT &gt;= 0.6.0 or
* another library that you know implements the same behavior.</p>
*
* <p><b>Simple Registration</b></p>
*
* <p>If a CompressionCodec can be resolved in the JWT Header via a simple {@code zip} header value lookup, it is
* recommended to call the {@link #addCompressionCodecs(Collection)} method instead of this one. That method
* will add the codec to the total set of supported codecs and lookup will achieved by matching the
* {@link CompressionCodec#getId() CompressionCodec.getId()} against the {@code zip} header value automatically.</p>
*
* <p>You only need to call this method with a custom locator if compression codec lookup cannot be based on the
* {@code zip} header value.</p>
*
* <p><b>Default Support</b></p>
*
* <p>JJWT's default {@link JwtParser} implementation supports both the
* {@link CompressionCodecs#DEFLATE DEFLATE}
* and {@link CompressionCodecs#GZIP GZIP} algorithms by default - you do not need to
* specify a {@code CompressionCodec} {@link Locator} in these cases.</p>
*
* <p>However, if you want to use a compression algorithm other than {@code DEF} or {@code GZIP}, and
* {@link #addCompressionCodecs(Collection)} is not sufficient, you must
* implement your own {@code CompressionCodec} {@link Locator} and specify that via this method and also when
* {@link io.jsonwebtoken.JwtBuilder#compressWith(CompressionCodec) building} JWTs.</p>
*
* @param locator the compression codec locator used to decompress the JWT body.
* @return the parser builder for method chaining.
* @since JJWT_RELEASE_VERSION
*/
JwtParserBuilder setCompressionCodecLocator(Locator<CompressionCodec> locator);
/** /**
* Perform Base64Url decoding with the specified Decoder * Perform Base64Url decoding with the specified Decoder
* *

View File

@ -15,7 +15,12 @@
*/ */
package io.jsonwebtoken; package io.jsonwebtoken;
import io.jsonwebtoken.lang.Builder;
import io.jsonwebtoken.lang.Classes; import io.jsonwebtoken.lang.Classes;
import io.jsonwebtoken.lang.Registry;
import io.jsonwebtoken.security.StandardEncryptionAlgorithms;
import io.jsonwebtoken.security.StandardKeyAlgorithms;
import io.jsonwebtoken.security.StandardSecureDigestAlgorithms;
import java.util.Map; import java.util.Map;
@ -23,58 +28,188 @@ import java.util.Map;
* Factory class useful for creating instances of JWT interfaces. Using this factory class can be a good * Factory class useful for creating instances of JWT interfaces. Using this factory class can be a good
* alternative to tightly coupling your code to implementation classes. * alternative to tightly coupling your code to implementation classes.
* *
* <p><b>Standard Algorithm References</b></p>
* <p>Standard JSON Web Token algorithms used during JWS or JWE building or parsing are available organized by
* algorithm type. Each organized collection of algorithms is available via a constant to allow
* for easy code-completion in IDEs, showing available algorithm instances. For example, when typing:</p>
* <blockquote><pre>
* Jwts.// press code-completion hotkeys to suggest available algorithm registry fields
* Jwts.{@link #SIG}.// press hotkeys to suggest individual Digital Signature or MAC algorithms or utility methods
* Jwts.{@link #ENC}.// press hotkeys to suggest individual encryption algorithms or utility methods
* Jwts.{@link #KEY}.// press hotkeys to suggest individual key algorithms or utility methods</pre></blockquote>
*
* @since 0.1 * @since 0.1
*/ */
public final class Jwts { public final class Jwts {
@SuppressWarnings("rawtypes")
private static final Class[] MAP_ARG = new Class[]{Map.class}; private static final Class[] MAP_ARG = new Class[]{Map.class};
/**
* All JWA (RFC 7518) standard <a href="https://www.rfc-editor.org/rfc/rfc7518.html#section-5">Cryptographic
* Algorithms for Content Encryption</a> defined in the
* <a href="https://www.rfc-editor.org/rfc/rfc7518.html#section-7.1">
* JSON Web Signature and Encryption Algorithms Registry</a>. In addition to its
* {@link Registry#get(Object) get} and {@link Registry#find(Object) find} lookup methods, each standard algorithm
* is also available as a ({@code public final}) constant for direct type-safe reference in application code.
* For example:
* <blockquote><pre>
* Jwts.builder()
* // ... etc ...
* .encryptWith(aKey, <b>Jwts.ENC.A256GCM</b>) // or A128GCM, A192GCM, etc...
* .build();</pre></blockquote>
*
* @since JJWT_RELEASE_VERSION
*/
public static final StandardEncryptionAlgorithms ENC = StandardEncryptionAlgorithms.get();
/**
* All JWA (RFC 7518) standard <a href="https://www.rfc-editor.org/rfc/rfc7518.html#section-3">Cryptographic
* Algorithms for Digital Signatures and MACs</a> defined in the
* <a href="https://www.rfc-editor.org/rfc/rfc7518.html#section-7.1">JSON Web Signature and Encryption Algorithms
* Registry</a>. In addition to its
* {@link Registry#get(Object) get} and {@link Registry#find(Object) find} lookup methods, each standard algorithm
* is also available as a ({@code public final}) constant for direct type-safe reference in application code.
* For example:
* <blockquote><pre>
* Jwts.builder()
* // ... etc ...
* .signWith(aKey, <b>Jwts.SIG.HS512</b>) // or RS512, PS256, EdDSA, etc...
* .build();</pre></blockquote>
*
* @since JJWT_RELEASE_VERSION
*/
public static final StandardSecureDigestAlgorithms SIG = StandardSecureDigestAlgorithms.get();
/**
* All JWA (RFC 7518) standard <a href="https://www.rfc-editor.org/rfc/rfc7518.html#section-4">Cryptographic
* Algorithms for Key Management</a>. In addition to its
* convenience {@link Registry#get(Object) get} and {@link Registry#find(Object) find} lookup methods, each
* standard algorithm is also available as a ({@code public final}) constant for direct type-safe reference in
* application code. For example:
* <blockquote><pre>
* Jwts.builder()
* // ... etc ...
* .encryptWith(aKey, <b>Jwts.KEY.ECDH_ES_A256KW</b>, Jwts.ENC.A256GCM)
* .build();</pre></blockquote>
*
* @since JJWT_RELEASE_VERSION
*/
public static final StandardKeyAlgorithms KEY = StandardKeyAlgorithms.get();
/**
* Private constructor, prevent instantiation.
*/
private Jwts() { private Jwts() {
} }
/** /**
* Creates a new {@link Header} instance suitable for <em>plaintext</em> (not digitally signed) JWTs. As this * <p><b>Deprecation Notice</b>: Renamed from {@code header} to {@code unprotectedHeader} since
* is a less common use of JWTs, consider using the {@link #jwsHeader()} factory method instead if you will later * JJWT_RELEASE_VERSION and deprecated in favor of {@link #header()} as
* digitally sign the JWT. * the updated builder-based method supports method chaining and is capable of automatically constructing
* {@link UnprotectedHeader}, {@link JwsHeader}, and {@link JweHeader} automatically based on builder state.</p>
* *
* @return a new {@link Header} instance suitable for <em>plaintext</em> (not digitally signed) JWTs. * <p><b>Previous Documentation</b></p>
* <p>Creates a new {@link UnprotectedHeader} instance suitable for unprotected (not digitally signed or encrypted)
* JWTs. Because {@code Header} extends {@link Map} and map mutation methods cannot support method chaining,
* consider using the more flexible {@link #header()} method instead, which does support method
* chaining and other builder conveniences not available on the {@link UnprotectedHeader} interface.</p>
*
* @return a new {@link UnprotectedHeader} instance suitable for <em>unprotected</em> (not digitally signed or
* encrypted) JWTs.
* @see #header()
* @since JJWT_RELEASE_VERSION
* @deprecated since JJWT_RELEASE_VERSION. This method was created to rename the previous {@code header}
* method, but header construction should now use {@link #header()}. This method will be removed in a future
* release before 1.0.
*/ */
public static Header header() { @Deprecated
return Classes.newInstance("io.jsonwebtoken.impl.DefaultHeader"); public static UnprotectedHeader unprotectedHeader() {
return Classes.newInstance("io.jsonwebtoken.impl.DefaultUnprotectedHeader");
} }
/** /**
* Creates a new {@link Header} instance suitable for <em>plaintext</em> (not digitally signed) JWTs, populated * <p><b>Deprecation Notice</b>: deprecated since JJWT_RELEASE_VERSION in favor of {@link #header()} as
* with the specified name/value pairs. As this is a less common use of JWTs, consider using the * the newer method supports method chaining and is capable of automatically constructing
* {@link #jwsHeader(java.util.Map)} factory method instead if you will later digitally sign the JWT. * {@link UnprotectedHeader}, {@link JwsHeader}, and {@link JweHeader} automatically based on builder state.</p>
* *
* @param header map of name/value pairs used to create a <em>plaintext</em> (not digitally signed) JWT * <p><b>Previous Documentation</b></p>
* <p>Creates a new {@link UnprotectedHeader} instance suitable for unprotected (not digitally signed or encrypted)
* JWTs, populated with the specified name/value pairs. Because {@code Header} extends {@link Map} and map
* mutation methods cannot support method chaining, consider using the more flexible {@link #header()}
* method instead, which does support method chaining and other builder conveniences not available on the
* {@link UnprotectedHeader} interface.</p>
*
* @param header map of name/value pairs used to create an unprotected (not digitally signed or encrypted) JWT
* {@code Header} instance. * {@code Header} instance.
* @return a new {@link Header} instance suitable for <em>plaintext</em> (not digitally signed) JWTs. * @return a new {@link UnprotectedHeader} instance suitable for unprotected (not digitally signed or encrypted)
* JWTs.
* @see #header()
* @deprecated since JJWT_RELEASE_VERSION in favor of {@link #header()} as the builder supports
* method chaining and is more flexible and powerful. This method will be removed in a future release before 1.0.
*/ */
public static Header header(Map<String, Object> header) { @Deprecated
return Classes.newInstance("io.jsonwebtoken.impl.DefaultHeader", MAP_ARG, header); public static UnprotectedHeader header(Map<String, Object> header) {
return Classes.newInstance("io.jsonwebtoken.impl.DefaultUnprotectedHeader", MAP_ARG, header);
} }
/** /**
* Returns a new {@link JwsHeader} instance suitable for digitally signed JWTs (aka 'JWS's). * Returns a new {@link DynamicHeaderBuilder} that can build any type of {@link Header} instance depending on
* which builder properties are set.
*
* @return a new {@link DynamicHeaderBuilder} that can build any type of {@link Header} instance depending on
* which builder properties are set.
* @since JJWT_RELEASE_VERSION
*/
public static DynamicHeaderBuilder header() {
return Classes.newInstance("io.jsonwebtoken.impl.DefaultDynamicHeaderBuilder");
}
/**
* <p><b>Deprecation Notice</b>: deprecated since JJWT_RELEASE_VERSION in favor of {@link #header()} as
* the newer method supports method chaining and is capable of automatically constructing
* {@link UnprotectedHeader}, {@link JwsHeader}, and {@link JweHeader} automatically based on builder state.</p>
*
* <p><b>Previous Documentation</b></p>
* <p>Returns a new {@link JwsHeader} instance suitable for digitally signed JWTs (aka 'JWS's). Because {@code Header}
* extends {@link Map} and map mutation methods cannot support method chaining, consider using the
* more flexible {@link #header()} method instead, which does support method chaining, as well as other
* convenience builder methods not available via the {@link JwsHeader} interface.</p>
* *
* @return a new {@link JwsHeader} instance suitable for digitally signed JWTs (aka 'JWS's). * @return a new {@link JwsHeader} instance suitable for digitally signed JWTs (aka 'JWS's).
* @see #header()
* @see JwtBuilder#setHeader(Header) * @see JwtBuilder#setHeader(Header)
* @see JwtBuilder#setHeader(Builder)
* @deprecated since JJWT_RELEASE_VERSION in favor of {@link #header()} as the builder supports
* method chaining and is more flexible and powerful. This method will be removed in a future release before 1.0.
*/ */
@Deprecated
public static JwsHeader jwsHeader() { public static JwsHeader jwsHeader() {
return Classes.newInstance("io.jsonwebtoken.impl.DefaultJwsHeader"); return Classes.newInstance("io.jsonwebtoken.impl.DefaultJwsHeader");
} }
/** /**
* Returns a new {@link JwsHeader} instance suitable for digitally signed JWTs (aka 'JWS's), populated with the * <p><b>Deprecation Notice</b>: deprecated since JJWT_RELEASE_VERSION in favor of {@link #header()} as
* specified name/value pairs. * the newer method supports method chaining and is capable of automatically constructing
* {@link UnprotectedHeader}, {@link JwsHeader}, and {@link JweHeader} automatically based on builder state.</p>
*
* <p><b>Previous Documentation</b></p>
* <p>Returns a new {@link JwsHeader} instance suitable for digitally signed JWTs (aka 'JWS's), populated with the
* specified name/value pairs. Because {@code Header} extends {@link Map} and map mutation methods cannot
* support method chaining, consider using the more flexible {@link #header()} method instead,
* which does support method chaining and other builder conveniences not available on the
* {@link JwsHeader} interface directly.</p>
* *
* @param header map of name/value pairs used to create a new {@link JwsHeader} instance. * @param header map of name/value pairs used to create a new {@link JwsHeader} instance.
* @return a new {@link JwsHeader} instance suitable for digitally signed JWTs (aka 'JWS's), populated with the * @return a new {@link JwsHeader} instance suitable for digitally signed JWTs (aka 'JWS's), populated with the
* specified name/value pairs. * specified name/value pairs.
* @see #header()
* @see JwtBuilder#setHeader(Header) * @see JwtBuilder#setHeader(Header)
* @see JwtBuilder#setHeader(Builder)
* @deprecated since JJWT_RELEASE_VERSION in favor of {@link #header()} as the builder supports
* method chaining and is more flexible and powerful. This method will be removed in a future release before 1.0.
*/ */
@Deprecated
public static JwsHeader jwsHeader(Map<String, Object> header) { public static JwsHeader jwsHeader(Map<String, Object> header) {
return Classes.newInstance("io.jsonwebtoken.impl.DefaultJwsHeader", MAP_ARG, header); return Classes.newInstance("io.jsonwebtoken.impl.DefaultJwsHeader", MAP_ARG, header);
} }

View File

@ -0,0 +1,41 @@
/*
* Copyright (C) 2021 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;
import java.security.Key;
/**
* A {@link Locator} can return an object referenced in a JWT {@link Header} that is necessary to process
* the associated JWT.
*
* <p>For example, a {@code Locator} implementation can inspect a header's {@code kid} (Key ID) parameter, and use the
* discovered {@code kid} value to lookup and return the associated {@link Key} instance. JJWT could then use this
* {@code key} to decrypt a JWE or verify a JWS signature.</p>
*
* @param <T> the type of object that may be returned from the {@link #locate(Header)} method
* @since JJWT_RELEASE_VERSION
*/
public interface Locator<T> {
/**
* Returns an object referenced in the specified {@code header}, or {@code null} if the object couldn't be found.
*
* @param header the JWT header to inspect; may be an instance of {@link Header}, {@link JwsHeader} or
* {@link JweHeader} depending on if the respective JWT is an unprotected JWT, JWS or JWE.
* @return an object referenced in the specified {@code header}, or {@code null} if the object couldn't be found.
*/
T locate(Header<?> header);
}

View File

@ -0,0 +1,112 @@
/*
* Copyright (C) 2021 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;
import io.jsonwebtoken.lang.Assert;
/**
* Adapter pattern implementation for the {@link Locator} interface. Subclasses can override any of the
* {@link #locate(UnprotectedHeader)}, {@link #locate(ProtectedHeader)}, {@link #locate(JwsHeader)}, or
* {@link #locate(JweHeader)} methods for type-specific logic if desired when the encountered header is an
* unprotected JWT, or an integrity-protected JWT (either a JWS or JWE).
*
* @since JJWT_RELEASE_VERSION
*/
public abstract class LocatorAdapter<T> implements Locator<T> {
/**
* Constructs a new instance, where all default method implementations return {@code null}.
*/
public LocatorAdapter() {
}
/**
* Inspects the specified header, and delegates to the {@link #locate(UnprotectedHeader)} method if the header
* is an {@link UnprotectedHeader} or the {@link #locate(ProtectedHeader)} method if the header is either a
* {@link JwsHeader} or {@link JweHeader}.
*
* @param header the JWT header to inspect; may be an instance of {@link UnprotectedHeader}, {@link JwsHeader} or
* {@link JweHeader} depending on if the respective JWT is an unprotected JWT, JWS or JWE.
* @return an object referenced in the specified header, or {@code null} if the referenced object cannot be found
* or does not exist.
*/
@Override
public final T locate(Header<?> header) {
Assert.notNull(header, "Header cannot be null.");
if (header instanceof ProtectedHeader<?>) {
ProtectedHeader<?> protectedHeader = (ProtectedHeader<?>) header;
return locate(protectedHeader);
} else {
Assert.isInstanceOf(UnprotectedHeader.class, header, "Unrecognized Header type.");
return locate((UnprotectedHeader) header);
}
}
/**
* Returns an object referenced in the specified {@link ProtectedHeader}, or {@code null} if the referenced
* object cannot be found or does not exist. This is a convenience method that delegates to
* {@link #locate(JwsHeader)} if the {@code header} is a {@link JwsHeader} or {@link #locate(JweHeader)} if the
* {@code header} is a {@link JweHeader}.
*
* @param header the protected header of an encountered JWS or JWE.
* @return an object referenced in the specified {@link ProtectedHeader}, or {@code null} if the referenced
* object cannot be found or does not exist.
*/
protected T locate(ProtectedHeader<?> header) {
if (header instanceof JwsHeader) {
return locate((JwsHeader) header);
} else {
Assert.isInstanceOf(JweHeader.class, header, "Unrecognized ProtectedHeader type.");
return locate((JweHeader) header);
}
}
/**
* Returns an object referenced in the specified JWE header, or {@code null} if the referenced
* object cannot be found or does not exist. Default implementation simply returns {@code null}.
*
* @param header the header of an encountered JWE.
* @return an object referenced in the specified JWE header, or {@code null} if the referenced
* object cannot be found or does not exist.
*/
protected T locate(JweHeader header) {
return null;
}
/**
* Returns an object referenced in the specified JWS header, or {@code null} if the referenced
* object cannot be found or does not exist. Default implementation simply returns {@code null}.
*
* @param header the header of an encountered JWS.
* @return an object referenced in the specified JWS header, or {@code null} if the referenced
* object cannot be found or does not exist.
*/
protected T locate(JwsHeader header) {
return null;
}
/**
* Returns an object referenced in the specified Unprotected JWT header, or {@code null} if the referenced
* object cannot be found or does not exist. Default implementation simply returns {@code null}.
*
* @param header the header of an encountered JWE.
* @return an object referenced in the specified Unprotected JWT header, or {@code null} if the referenced
* object cannot be found or does not exist.
*/
protected T locate(UnprotectedHeader header) {
return null;
}
}

View File

@ -22,10 +22,21 @@ package io.jsonwebtoken;
*/ */
public class MalformedJwtException extends JwtException { public class MalformedJwtException extends JwtException {
/**
* Creates a new instance with the specified explanation message.
*
* @param message the message explaining why the exception is thrown.
*/
public MalformedJwtException(String message) { public MalformedJwtException(String message) {
super(message); super(message);
} }
/**
* Creates a new instance with the specified explanation message and underlying cause.
*
* @param message the message explaining why the exception is thrown.
* @param cause the underlying cause that resulted in this exception being thrown.
*/
public MalformedJwtException(String message, Throwable cause) { public MalformedJwtException(String message, Throwable cause) {
super(message, cause); super(message, cause);
} }

View File

@ -22,11 +22,32 @@ package io.jsonwebtoken;
* @since 0.6 * @since 0.6
*/ */
public class MissingClaimException extends InvalidClaimException { public class MissingClaimException extends InvalidClaimException {
public MissingClaimException(Header header, Claims claims, String message) {
super(header, claims, message); /**
* Creates a new instance with the specified explanation message.
*
* @param header the header associated with the claims that did not contain the required claim
* @param claims the claims that did not contain the required claim
* @param claimName the name of the claim that could not be validated
* @param claimValue the value of the claim that could not be validated
* @param message the message explaining why the exception is thrown.
*/
public MissingClaimException(Header<?> header, Claims claims, String claimName, Object claimValue, String message) {
super(header, claims, claimName, claimValue, message);
} }
public MissingClaimException(Header header, Claims claims, String message, Throwable cause) {
super(header, claims, message, cause); /**
* Creates a new instance with the specified explanation message and underlying cause.
*
* @param header the header associated with the claims that did not contain the required claim
* @param claims the claims that did not contain the required claim
* @param claimName the name of the claim that could not be validated
* @param claimValue the value of the claim that could not be validated
* @param message the message explaining why the exception is thrown.
* @param cause the underlying cause that resulted in this exception being thrown.
*/
public MissingClaimException(Header<?> header, Claims claims, String claimName, Object claimValue, String message, Throwable cause) {
super(header, claims, claimName, claimValue, message, cause);
} }
} }

View File

@ -22,18 +22,27 @@ package io.jsonwebtoken;
*/ */
public class PrematureJwtException extends ClaimJwtException { public class PrematureJwtException extends ClaimJwtException {
public PrematureJwtException(Header header, Claims claims, String message) { /**
* Creates a new instance with the specified explanation message.
*
* @param header jwt header
* @param claims jwt claims (body)
* @param message the message explaining why the exception is thrown.
*/
public PrematureJwtException(Header<?> header, Claims claims, String message) {
super(header, claims, message); super(header, claims, message);
} }
/** /**
* @param header jwt header * Creates a new instance with the specified explanation message and underlying cause.
* @param claims jwt claims (body) *
* @param header jwt header
* @param claims jwt claims (body)
* @param message exception message * @param message exception message
* @param cause cause * @param cause cause
* @since 0.5 * @since 0.5
*/ */
public PrematureJwtException(Header header, Claims claims, String message, Throwable cause) { public PrematureJwtException(Header<?> header, Claims claims, String message, Throwable cause) {
super(header, claims, message, cause); super(header, claims, message, cause);
} }
} }

View File

@ -0,0 +1,86 @@
/*
* Copyright (C) 2021 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;
import io.jsonwebtoken.security.PublicJwk;
import io.jsonwebtoken.security.X509Accessor;
import java.net.URI;
import java.util.Set;
/**
* A JWT header that is integrity protected, either by JWS digital signature or JWE AEAD encryption.
*
* @param <T> The exact header subtype returned during mutation (setter) operations.
* @see JwsHeader
* @see JweHeader
* @since JJWT_RELEASE_VERSION
*/
public interface ProtectedHeader<T extends ProtectedHeader<T>> extends Header<T>, ProtectedHeaderMutator<T>, X509Accessor {
/**
* Returns the {@code jku} (JWK Set URL) value that refers to a
* <a href="https://www.rfc-editor.org/rfc/rfc7517.html#section-5">JWK Set</a>
* resource containing JSON-encoded Public Keys, or {@code null} if not present. When present in a
* {@link JwsHeader}, the first public key in the JWK Set <em>must</em> be the public key complement of the private
* key used to sign the JWS. When present in a {@link JweHeader}, the first public key in the JWK Set <em>must</em>
* be the public key used during encryption.
*
* @return a URI that refers to a <a href="https://www.rfc-editor.org/rfc/rfc7517.html#section-5">JWK Set</a>
* resource for a set of JSON-encoded Public Keys, or {@code null} if not present.
* @see <a href="https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.2">JWS JWK Set URL</a>
* @see <a href="https://www.rfc-editor.org/rfc/rfc7516.html#section-4.1.4">JWE JWK Set URL</a>
*/
URI getJwkSetUrl();
/**
* Returns the {@code jwk} (JSON Web Key) associated with the JWT. When present in a {@link JwsHeader}, the
* {@code jwk} is the public key complement of the private key used to digitally sign the JWS. When present in a
* {@link JweHeader}, the {@code jwk} is the public key to which the JWE was encrypted, and may be used to
* determine the private key needed to decrypt the JWE.
*
* @return the {@code jwk} (JSON Web Key) associated with the header.
* @see <a href="https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.3">JWS {@code jwk} (JSON Web Key) Header Parameter</a>
* @see <a href="https://www.rfc-editor.org/rfc/rfc7516.html#section-4.1.5">JWE {@code jwk} (JSON Web Key) Header Parameter</a>
*/
PublicJwk<?> getJwk();
/**
* Returns the JWT case-sensitive {@code kid} (Key ID) header value or {@code null} if not present.
*
* <p>The keyId header parameter is a hint indicating which key was used to secure a JWS or JWE. This
* parameter allows originators to explicitly signal a change of key to recipients. The structure of the keyId
* value is unspecified. Its value is a CaSe-SeNsItIvE string.</p>
*
* <p>When used with a JWK, the keyId value is used to match a JWK {@code keyId} parameter value.</p>
*
* @return the case-sensitive {@code kid} header value or {@code null} if not present.
* @see <a href="https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.4">JWS Key ID</a>
* @see <a href="https://www.rfc-editor.org/rfc/rfc7516.html#section-4.1.6">JWE Key ID</a>
*/
String getKeyId();
/**
* Returns the header parameter names that use extensions to the JWT or JWA specification that <em>MUST</em>
* be understood and supported by the JWT recipient, or {@code null} if not present.
*
* @return the header parameter names that use extensions to the JWT or JWA specification that <em>MUST</em>
* be understood and supported by the JWT recipient, or {@code null} if not present.
* @see <a href="https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.11">JWS {@code crit} (Critical) Header Parameter</a>
* @see <a href="https://www.rfc-editor.org/rfc/rfc7516.html#section-4.1.13">JWS {@code crit} (Critical) Header Parameter</a>
*/
Set<String> getCritical();
}

View File

@ -0,0 +1,90 @@
/*
* Copyright (C) 2021 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;
import io.jsonwebtoken.security.PublicJwk;
import io.jsonwebtoken.security.X509Mutator;
import java.net.URI;
import java.util.Set;
/**
* Mutation (modifications) to a {@link ProtectedHeader Header} instance.
*
* @param <T> the mutator subtype, for method chaining
* @since JJWT_RELEASE_VERSION
*/
public interface ProtectedHeaderMutator<T extends ProtectedHeaderMutator<T>> extends HeaderMutator<T>, X509Mutator<T> {
/**
* Sets the {@code jku} (JWK Set URL) value that refers to a
* <a href="https://www.rfc-editor.org/rfc/rfc7517.html#section-5">JWK Set</a>
* resource containing JSON-encoded Public Keys, or {@code null} if not present. When set for a
* {@link JwsHeader}, the first public key in the JWK Set <em>must</em> be the public key complement of the
* private key used to sign the JWS. When set for a {@link JweHeader}, the first public key in the JWK Set
* <em>must</em> be the public key used during encryption.
*
* @param uri a URI that refers to a <a href="https://www.rfc-editor.org/rfc/rfc7517.html#section-5">JWK Set</a>
* resource containing JSON-encoded Public Keys
* @return the header for method chaining
* @see <a href="https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.2">JWS JWK Set URL</a>
* @see <a href="https://www.rfc-editor.org/rfc/rfc7516.html#section-4.1.4">JWE JWK Set URL</a>
*/
T setJwkSetUrl(URI uri);
/**
* Sets the {@code jwk} (JSON Web Key) associated with the JWT. When set for a {@link JwsHeader}, the
* {@code jwk} is the public key complement of the private key used to digitally sign the JWS. When set for a
* {@link JweHeader}, the {@code jwk} is the public key to which the JWE was encrypted, and may be used to
* determine the private key needed to decrypt the JWE.
*
* @param jwk the {@code jwk} (JSON Web Key) associated with the header.
* @return the header for method chaining
* @see <a href="https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.3">JWS <code>jwk</code> (JSON Web Key) Header Parameter</a>
* @see <a href="https://www.rfc-editor.org/rfc/rfc7516.html#section-4.1.5">JWE <code>jwk</code> (JSON Web Key) Header Parameter</a>
*/
T setJwk(PublicJwk<?> jwk);
/**
* Sets the JWT case-sensitive {@code kid} (Key ID) header value. A {@code null} value will remove the property
* from the JSON map.
*
* <p>The keyId header parameter is a hint indicating which key was used to secure a JWS or JWE. This parameter
* allows originators to explicitly signal a change of key to recipients. The structure of the keyId value is
* unspecified. Its value MUST be a case-sensitive string.</p>
*
* <p>When used with a JWK, the keyId value is used to match a JWK {@code keyId} parameter value.</p>
*
* @param kid the case-sensitive JWS {@code kid} header value or {@code null} to remove the property from the JSON map.
* @return the header instance for method chaining.
* @see <a href="https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.4">JWS Key ID</a>
* @see <a href="https://www.rfc-editor.org/rfc/rfc7516.html#section-4.1.6">JWE Key ID</a>
*/
T setKeyId(String kid);
/**
* Sets the header parameter names that use extensions to the JWT or JWA specification that <em>MUST</em>
* be understood and supported by the JWT recipient. A {@code null} value will remove the
* property from the JSON map.
*
* @param crit the header parameter names that use extensions to the JWT or JWA specification that <em>MUST</em>
* be understood and supported by the JWT recipient.
* @return the header for method chaining.
* @see <a href="https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.11">JWS <code>crit</code> (Critical) Header Parameter</a>
* @see <a href="https://www.rfc-editor.org/rfc/rfc7516.html#section-4.1.13">JWS <code>crit</code> (Critical) Header Parameter</a>
*/
T setCritical(Set<String> crit);
}

View File

@ -16,16 +16,28 @@
package io.jsonwebtoken; package io.jsonwebtoken;
/** /**
* Exception thrown when {@link Claims#get(String, Class)} is called and the value does not match the type of the * Exception thrown when attempting to obtain a value from a JWT or JWK and the existing value does not match the
* {@code Class} argument. * expected type.
* *
* @since 0.6 * @since 0.6
*/ */
public class RequiredTypeException extends JwtException { public class RequiredTypeException extends JwtException {
/**
* Creates a new instance with the specified explanation message.
*
* @param message the message explaining why the exception is thrown.
*/
public RequiredTypeException(String message) { public RequiredTypeException(String message) {
super(message); super(message);
} }
/**
* Creates a new instance with the specified explanation message and underlying cause.
*
* @param message the message explaining why the exception is thrown.
* @param cause the underlying cause that resulted in this exception being thrown.
*/
public RequiredTypeException(String message, Throwable cause) { public RequiredTypeException(String message, Throwable cause) {
super(message, cause); super(message, cause);
} }

View File

@ -18,6 +18,7 @@ package io.jsonwebtoken;
import io.jsonwebtoken.security.InvalidKeyException; import io.jsonwebtoken.security.InvalidKeyException;
import io.jsonwebtoken.security.Keys; import io.jsonwebtoken.security.Keys;
import io.jsonwebtoken.security.SignatureException; import io.jsonwebtoken.security.SignatureException;
import io.jsonwebtoken.security.StandardSecureDigestAlgorithms;
import io.jsonwebtoken.security.WeakKeyException; import io.jsonwebtoken.security.WeakKeyException;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
@ -34,7 +35,9 @@ import java.util.List;
* <a href="https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-31">JSON Web Algorithms</a> specification. * <a href="https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-31">JSON Web Algorithms</a> specification.
* *
* @since 0.1 * @since 0.1
* @deprecated since JJWT_RELEASE_VERSION; use {@link StandardSecureDigestAlgorithms} instead.
*/ */
@Deprecated
public enum SignatureAlgorithm { public enum SignatureAlgorithm {
/** /**
@ -110,10 +113,10 @@ public enum SignatureAlgorithm {
//purposefully ordered higher to lower: //purposefully ordered higher to lower:
private static final List<SignatureAlgorithm> PREFERRED_HMAC_ALGS = Collections.unmodifiableList(Arrays.asList( private static final List<SignatureAlgorithm> PREFERRED_HMAC_ALGS = Collections.unmodifiableList(Arrays.asList(
SignatureAlgorithm.HS512, SignatureAlgorithm.HS384, SignatureAlgorithm.HS256)); SignatureAlgorithm.HS512, SignatureAlgorithm.HS384, SignatureAlgorithm.HS256));
//purposefully ordered higher to lower: //purposefully ordered higher to lower:
private static final List<SignatureAlgorithm> PREFERRED_EC_ALGS = Collections.unmodifiableList(Arrays.asList( private static final List<SignatureAlgorithm> PREFERRED_EC_ALGS = Collections.unmodifiableList(Arrays.asList(
SignatureAlgorithm.ES512, SignatureAlgorithm.ES384, SignatureAlgorithm.ES256)); SignatureAlgorithm.ES512, SignatureAlgorithm.ES384, SignatureAlgorithm.ES256));
private final String value; private final String value;
private final String description; private final String description;
@ -132,7 +135,7 @@ public enum SignatureAlgorithm {
SignatureAlgorithm(String value, String description, String familyName, String jcaName, boolean jdkStandard, SignatureAlgorithm(String value, String description, String familyName, String jcaName, boolean jdkStandard,
int digestLength, int minKeyLength) { int digestLength, int minKeyLength) {
this(value, description,familyName, jcaName, jdkStandard, digestLength, minKeyLength, jcaName); this(value, description, familyName, jcaName, jdkStandard, digestLength, minKeyLength, jcaName);
} }
SignatureAlgorithm(String value, String description, String familyName, String jcaName, boolean jdkStandard, SignatureAlgorithm(String value, String description, String familyName, String jcaName, boolean jdkStandard,
@ -365,25 +368,25 @@ public enum SignatureAlgorithm {
// These next checks use equalsIgnoreCase per https://github.com/jwtk/jjwt/issues/381#issuecomment-412912272 // These next checks use equalsIgnoreCase per https://github.com/jwtk/jjwt/issues/381#issuecomment-412912272
if (!HS256.jcaName.equalsIgnoreCase(alg) && if (!HS256.jcaName.equalsIgnoreCase(alg) &&
!HS384.jcaName.equalsIgnoreCase(alg) && !HS384.jcaName.equalsIgnoreCase(alg) &&
!HS512.jcaName.equalsIgnoreCase(alg) && !HS512.jcaName.equalsIgnoreCase(alg) &&
!HS256.pkcs12Name.equals(alg) && !HS256.pkcs12Name.equals(alg) &&
!HS384.pkcs12Name.equals(alg) && !HS384.pkcs12Name.equals(alg) &&
!HS512.pkcs12Name.equals(alg)) { !HS512.pkcs12Name.equals(alg)) {
throw new InvalidKeyException("The " + keyType(signing) + " key's algorithm '" + alg + throw new InvalidKeyException("The " + keyType(signing) + " key's algorithm '" + alg +
"' does not equal a valid HmacSHA* algorithm name and cannot be used with " + name() + "."); "' does not equal a valid HmacSHA* algorithm name and cannot be used with " + name() + ".");
} }
int size = encoded.length * 8; //size in bits int size = encoded.length * 8; //size in bits
if (size < this.minKeyLength) { if (size < this.minKeyLength) {
String msg = "The " + keyType(signing) + " key's size is " + size + " bits which " + String msg = "The " + keyType(signing) + " key's size is " + size + " bits which " +
"is not secure enough for the " + name() + " algorithm. The JWT " + "is not secure enough for the " + name() + " algorithm. The JWT " +
"JWA Specification (RFC 7518, Section 3.2) states that keys used with " + name() + " MUST have a " + "JWA Specification (RFC 7518, Section 3.2) states that keys used with " + name() + " MUST have a " +
"size >= " + minKeyLength + " bits (the key size must be greater than or equal to the hash " + "size >= " + minKeyLength + " bits (the key size must be greater than or equal to the hash " +
"output size). Consider using the " + Keys.class.getName() + " class's " + "output size). Consider using the " + Keys.class.getName() + " class's " +
"'secretKeyFor(SignatureAlgorithm." + name() + ")' method to create a key guaranteed to be " + "'secretKeyFor(SignatureAlgorithm." + name() + ")' method to create a key guaranteed to be " +
"secure enough for " + name() + ". See " + "secure enough for " + name() + ". See " +
"https://tools.ietf.org/html/rfc7518#section-3.2 for more information."; "https://tools.ietf.org/html/rfc7518#section-3.2 for more information.";
throw new WeakKeyException(msg); throw new WeakKeyException(msg);
} }
@ -407,13 +410,13 @@ public enum SignatureAlgorithm {
int size = ecKey.getParams().getOrder().bitLength(); int size = ecKey.getParams().getOrder().bitLength();
if (size < this.minKeyLength) { if (size < this.minKeyLength) {
String msg = "The " + keyType(signing) + " key's size (ECParameterSpec order) is " + size + String msg = "The " + keyType(signing) + " key's size (ECParameterSpec order) is " + size +
" bits which is not secure enough for the " + name() + " algorithm. The JWT " + " bits which is not secure enough for the " + name() + " algorithm. The JWT " +
"JWA Specification (RFC 7518, Section 3.4) states that keys used with " + "JWA Specification (RFC 7518, Section 3.4) states that keys used with " +
name() + " MUST have a size >= " + this.minKeyLength + name() + " MUST have a size >= " + this.minKeyLength +
" bits. Consider using the " + Keys.class.getName() + " class's " + " bits. Consider using the " + Keys.class.getName() + " class's " +
"'keyPairFor(SignatureAlgorithm." + name() + ")' method to create a key pair guaranteed " + "'keyPairFor(SignatureAlgorithm." + name() + ")' method to create a key pair guaranteed " +
"to be secure enough for " + name() + ". See " + "to be secure enough for " + name() + ". See " +
"https://tools.ietf.org/html/rfc7518#section-3.4 for more information."; "https://tools.ietf.org/html/rfc7518#section-3.4 for more information.";
throw new WeakKeyException(msg); throw new WeakKeyException(msg);
} }
@ -431,12 +434,12 @@ public enum SignatureAlgorithm {
String section = name().startsWith("P") ? "3.5" : "3.3"; String section = name().startsWith("P") ? "3.5" : "3.3";
String msg = "The " + keyType(signing) + " key's size is " + size + " bits which is not secure " + String msg = "The " + keyType(signing) + " key's size is " + size + " bits which is not secure " +
"enough for the " + name() + " algorithm. The JWT JWA Specification (RFC 7518, Section " + "enough for the " + name() + " algorithm. The JWT JWA Specification (RFC 7518, Section " +
section + ") states that keys used with " + name() + " MUST have a size >= " + section + ") states that keys used with " + name() + " MUST have a size >= " +
this.minKeyLength + " bits. Consider using the " + Keys.class.getName() + " class's " + this.minKeyLength + " bits. Consider using the " + Keys.class.getName() + " class's " +
"'keyPairFor(SignatureAlgorithm." + name() + ")' method to create a key pair guaranteed " + "'keyPairFor(SignatureAlgorithm." + name() + ")' method to create a key pair guaranteed " +
"to be secure enough for " + name() + ". See " + "to be secure enough for " + name() + ". See " +
"https://tools.ietf.org/html/rfc7518#section-" + section + " for more information."; "https://tools.ietf.org/html/rfc7518#section-" + section + " for more information.";
throw new WeakKeyException(msg); throw new WeakKeyException(msg);
} }
} }
@ -563,19 +566,19 @@ public enum SignatureAlgorithm {
} }
if (!(key instanceof SecretKey || if (!(key instanceof SecretKey ||
(key instanceof PrivateKey && (key instanceof ECKey || key instanceof RSAKey)))) { (key instanceof PrivateKey && (key instanceof ECKey || key instanceof RSAKey)))) {
String msg = "JWT standard signing algorithms require either 1) a SecretKey for HMAC-SHA algorithms or " + String msg = "JWT standard signing algorithms require either 1) a SecretKey for HMAC-SHA algorithms or " +
"2) a private RSAKey for RSA algorithms or 3) a private ECKey for Elliptic Curve algorithms. " + "2) a private RSAKey for RSA algorithms or 3) a private ECKey for Elliptic Curve algorithms. " +
"The specified key is of type " + key.getClass().getName(); "The specified key is of type " + key.getClass().getName();
throw new InvalidKeyException(msg); throw new InvalidKeyException(msg);
} }
if (key instanceof SecretKey) { if (key instanceof SecretKey) {
SecretKey secretKey = (SecretKey)key; SecretKey secretKey = (SecretKey) key;
int bitLength = io.jsonwebtoken.lang.Arrays.length(secretKey.getEncoded()) * Byte.SIZE; int bitLength = io.jsonwebtoken.lang.Arrays.length(secretKey.getEncoded()) * Byte.SIZE;
for(SignatureAlgorithm alg : PREFERRED_HMAC_ALGS) { for (SignatureAlgorithm alg : PREFERRED_HMAC_ALGS) {
// ensure compatibility check is based on key length. See https://github.com/jwtk/jjwt/issues/381 // ensure compatibility check is based on key length. See https://github.com/jwtk/jjwt/issues/381
if (bitLength >= alg.minKeyLength) { if (bitLength >= alg.minKeyLength) {
return alg; return alg;
@ -583,9 +586,9 @@ public enum SignatureAlgorithm {
} }
String msg = "The specified SecretKey is not strong enough to be used with JWT HMAC signature " + String msg = "The specified SecretKey is not strong enough to be used with JWT HMAC signature " +
"algorithms. The JWT specification requires HMAC keys to be >= 256 bits long. The specified " + "algorithms. The JWT specification requires HMAC keys to be >= 256 bits long. The specified " +
"key is " + bitLength + " bits. See https://tools.ietf.org/html/rfc7518#section-3.2 for more " + "key is " + bitLength + " bits. See https://tools.ietf.org/html/rfc7518#section-3.2 for more " +
"information."; "information.";
throw new WeakKeyException(msg); throw new WeakKeyException(msg);
} }
@ -606,9 +609,9 @@ public enum SignatureAlgorithm {
} }
String msg = "The specified RSA signing key is not strong enough to be used with JWT RSA signature " + String msg = "The specified RSA signing key is not strong enough to be used with JWT RSA signature " +
"algorithms. The JWT specification requires RSA keys to be >= 2048 bits long. The specified RSA " + "algorithms. The JWT specification requires RSA keys to be >= 2048 bits long. The specified RSA " +
"key is " + bitLength + " bits. See https://tools.ietf.org/html/rfc7518#section-3.3 for more " + "key is " + bitLength + " bits. See https://tools.ietf.org/html/rfc7518#section-3.3 for more " +
"information."; "information.";
throw new WeakKeyException(msg); throw new WeakKeyException(msg);
} }
@ -626,9 +629,9 @@ public enum SignatureAlgorithm {
} }
String msg = "The specified Elliptic Curve signing key is not strong enough to be used with JWT ECDSA " + String msg = "The specified Elliptic Curve signing key is not strong enough to be used with JWT ECDSA " +
"signature algorithms. The JWT specification requires ECDSA keys to be >= 256 bits long. " + "signature algorithms. The JWT specification requires ECDSA keys to be >= 256 bits long. " +
"The specified ECDSA key is " + bitLength + " bits. See " + "The specified ECDSA key is " + bitLength + " bits. See " +
"https://tools.ietf.org/html/rfc7518#section-3.4 for more information."; "https://tools.ietf.org/html/rfc7518#section-3.4 for more information.";
throw new WeakKeyException(msg); throw new WeakKeyException(msg);
} }

View File

@ -21,15 +21,26 @@ import io.jsonwebtoken.security.SecurityException;
* Exception indicating that either calculating a signature or verifying an existing signature of a JWT failed. * Exception indicating that either calculating a signature or verifying an existing signature of a JWT failed.
* *
* @since 0.1 * @since 0.1
* @deprecated in favor of {@link io.jsonwebtoken.security.SecurityException}; this class will be removed before 1.0 * @deprecated in favor of {@link io.jsonwebtoken.security.SignatureException}; this class will be removed before 1.0
*/ */
@Deprecated @Deprecated
public class SignatureException extends SecurityException { public class SignatureException extends SecurityException {
/**
* Creates a new instance with the specified explanation message.
*
* @param message the message explaining why the exception is thrown.
*/
public SignatureException(String message) { public SignatureException(String message) {
super(message); super(message);
} }
/**
* Creates a new instance with the specified explanation message and underlying cause.
*
* @param message the message explaining why the exception is thrown.
* @param cause the underlying cause that resulted in this exception being thrown.
*/
public SignatureException(String message, Throwable cause) { public SignatureException(String message, Throwable cause) {
super(message, cause); super(message, cause);
} }

View File

@ -22,7 +22,7 @@ import java.security.Key;
* should be used to verify a JWS signature. * should be used to verify a JWS signature.
* *
* <p>A {@code SigningKeyResolver} is necessary when the signing key is not already known before parsing the JWT and the * <p>A {@code SigningKeyResolver} is necessary when the signing key is not already known before parsing the JWT and the
* JWT header or payload (plaintext body or Claims) must be inspected first to determine how to look up the signing key. * JWT header or payload (byte array or Claims) must be inspected first to determine how to look up the signing key.
* Once returned by the resolver, the JwtParser will then verify the JWS signature with the returned key. For * Once returned by the resolver, the JwtParser will then verify the JWS signature with the returned key. For
* example:</p> * example:</p>
* *
@ -40,13 +40,15 @@ import java.security.Key;
* *
* <h2>Using an Adapter</h2> * <h2>Using an Adapter</h2>
* *
* <p>If you only need to resolve a signing key for a particular JWS (either a plaintext or Claims JWS), consider using * <p>If you only need to resolve a signing key for a particular JWS (either a content or Claims JWS), consider using
* the {@link io.jsonwebtoken.SigningKeyResolverAdapter} and overriding only the method you need to support instead of * the {@link io.jsonwebtoken.SigningKeyResolverAdapter} and overriding only the method you need to support instead of
* implementing this interface directly.</p> * implementing this interface directly.</p>
* *
* @see io.jsonwebtoken.SigningKeyResolverAdapter * @see io.jsonwebtoken.JwtParserBuilder#setKeyLocator(Locator)
* @since 0.4 * @since 0.4
* @deprecated since JJWT_RELEASE_VERSION. Implement {@link Locator} instead.
*/ */
@Deprecated
public interface SigningKeyResolver { public interface SigningKeyResolver {
/** /**
@ -54,20 +56,20 @@ public interface SigningKeyResolver {
* header and claims. * header and claims.
* *
* @param header the header of the JWS to validate * @param header the header of the JWS to validate
* @param claims the claims (body) of the JWS to validate * @param claims the Claims payload of the JWS to validate
* @return the signing key that should be used to validate a digital signature for the Claims JWS with the specified * @return the signing key that should be used to validate a digital signature for the Claims JWS with the specified
* header and claims. * header and claims.
*/ */
Key resolveSigningKey(JwsHeader header, Claims claims); Key resolveSigningKey(JwsHeader header, Claims claims);
/** /**
* Returns the signing key that should be used to validate a digital signature for the Plaintext JWS with the * Returns the signing key that should be used to validate a digital signature for the content JWS with the
* specified header and plaintext payload. * specified header and byte array payload.
* *
* @param header the header of the JWS to validate * @param header the header of the JWS to validate
* @param plaintext the plaintext body of the JWS to validate * @param content the byte array payload of the JWS to validate
* @return the signing key that should be used to validate a digital signature for the Plaintext JWS with the * @return the signing key that should be used to validate a digital signature for the content JWS with the
* specified header and plaintext payload. * specified header and byte array payload.
*/ */
Key resolveSigningKey(JwsHeader header, String plaintext); Key resolveSigningKey(JwsHeader header, byte[] content);
} }

View File

@ -21,44 +21,67 @@ import javax.crypto.spec.SecretKeySpec;
import java.security.Key; import java.security.Key;
/** /**
* An <a href="http://en.wikipedia.org/wiki/Adapter_pattern">Adapter</a> implementation of the * <h2>Deprecation Notice</h2>
* {@link SigningKeyResolver} interface that allows subclasses to process only the type of JWS body that
* is known/expected for a particular case.
* *
* <p>The {@link #resolveSigningKey(JwsHeader, Claims)} and {@link #resolveSigningKey(JwsHeader, String)} method * <p>As of JJWT JJWT_RELEASE_VERSION, various Resolver concepts (including the {@code SigningKeyResolver}) have been
* unified into a single {@link Locator} interface. For key location, (for both signing and encryption keys),
* use the {@link JwtParserBuilder#setKeyLocator(Locator)} to configure a parser with your desired Key locator instead
* of using a {@code SigningKeyResolver}. Also see {@link LocatorAdapter} for the Adapter pattern parallel of this
* class. <b>This {@code SigningKeyResolverAdapter} class will be removed before the 1.0 release.</b></p>
*
* <p><b>Previous Documentation</b></p>
*
* <p>An <a href="http://en.wikipedia.org/wiki/Adapter_pattern">Adapter</a> implementation of the
* {@link SigningKeyResolver} interface that allows subclasses to process only the type of JWS body that
* is known/expected for a particular case.</p>
*
* <p>The {@link #resolveSigningKey(JwsHeader, Claims)} and {@link #resolveSigningKey(JwsHeader, byte[])} method
* implementations delegate to the * implementations delegate to the
* {@link #resolveSigningKeyBytes(JwsHeader, Claims)} and {@link #resolveSigningKeyBytes(JwsHeader, String)} methods * {@link #resolveSigningKeyBytes(JwsHeader, Claims)} and {@link #resolveSigningKeyBytes(JwsHeader, byte[])} methods
* respectively. The latter two methods simply throw exceptions: they represent scenarios expected by * respectively. The latter two methods simply throw exceptions: they represent scenarios expected by
* calling code in known situations, and it is expected that you override the implementation in those known situations; * calling code in known situations, and it is expected that you override the implementation in those known situations;
* non-overridden *KeyBytes methods indicates that the JWS input was unexpected.</p> * non-overridden *KeyBytes methods indicates that the JWS input was unexpected.</p>
* *
* <p>If either {@link #resolveSigningKey(JwsHeader, String)} or {@link #resolveSigningKey(JwsHeader, Claims)} * <p>If either {@link #resolveSigningKey(JwsHeader, byte[])} or {@link #resolveSigningKey(JwsHeader, Claims)}
* are not overridden, one (or both) of the *KeyBytes variants must be overridden depending on your expected * are not overridden, one (or both) of the *KeyBytes variants must be overridden depending on your expected
* use case. You do not have to override any method that does not represent an expected condition.</p> * use case. You do not have to override any method that does not represent an expected condition.</p>
* *
* @see io.jsonwebtoken.JwtParserBuilder#setKeyLocator(Locator)
* @see LocatorAdapter
* @since 0.4 * @since 0.4
* @deprecated since JJWT_RELEASE_VERSION. Use {@link LocatorAdapter LocatorAdapter} with
* {@link JwtParserBuilder#setKeyLocator(Locator)}
*/ */
@SuppressWarnings("DeprecatedIsStillUsed")
@Deprecated
public class SigningKeyResolverAdapter implements SigningKeyResolver { public class SigningKeyResolverAdapter implements SigningKeyResolver {
/**
* Default constructor.
*/
public SigningKeyResolverAdapter() {
}
@Override @Override
public Key resolveSigningKey(JwsHeader header, Claims claims) { public Key resolveSigningKey(JwsHeader header, Claims claims) {
SignatureAlgorithm alg = SignatureAlgorithm.forName(header.getAlgorithm()); SignatureAlgorithm alg = SignatureAlgorithm.forName(header.getAlgorithm());
Assert.isTrue(alg.isHmac(), "The default resolveSigningKey(JwsHeader, Claims) implementation cannot be " + Assert.isTrue(alg.isHmac(), "The default resolveSigningKey(JwsHeader, Claims) implementation cannot " +
"used for asymmetric key algorithms (RSA, Elliptic Curve). " + "be used for asymmetric key algorithms (RSA, Elliptic Curve). " +
"Override the resolveSigningKey(JwsHeader, Claims) method instead and return a " + "Override the resolveSigningKey(JwsHeader, Claims) method instead and return a " +
"Key instance appropriate for the " + alg.name() + " algorithm."); "Key instance appropriate for the " + alg.name() + " algorithm.");
byte[] keyBytes = resolveSigningKeyBytes(header, claims); byte[] keyBytes = resolveSigningKeyBytes(header, claims);
return new SecretKeySpec(keyBytes, alg.getJcaName()); return new SecretKeySpec(keyBytes, alg.getJcaName());
} }
@Override @Override
public Key resolveSigningKey(JwsHeader header, String plaintext) { public Key resolveSigningKey(JwsHeader header, byte[] content) {
SignatureAlgorithm alg = SignatureAlgorithm.forName(header.getAlgorithm()); SignatureAlgorithm alg = SignatureAlgorithm.forName(header.getAlgorithm());
Assert.isTrue(alg.isHmac(), "The default resolveSigningKey(JwsHeader, String) implementation cannot be " + Assert.isTrue(alg.isHmac(), "The default resolveSigningKey(JwsHeader, byte[]) implementation cannot " +
"used for asymmetric key algorithms (RSA, Elliptic Curve). " + "be used for asymmetric key algorithms (RSA, Elliptic Curve). " +
"Override the resolveSigningKey(JwsHeader, String) method instead and return a " + "Override the resolveSigningKey(JwsHeader, byte[]) method instead and return a " +
"Key instance appropriate for the " + alg.name() + " algorithm."); "Key instance appropriate for the " + alg.name() + " algorithm.");
byte[] keyBytes = resolveSigningKeyBytes(header, plaintext); byte[] keyBytes = resolveSigningKeyBytes(header, content);
return new SecretKeySpec(keyBytes, alg.getJcaName()); return new SecretKeySpec(keyBytes, alg.getJcaName());
} }
@ -76,24 +99,25 @@ public class SigningKeyResolverAdapter implements SigningKeyResolver {
*/ */
public byte[] resolveSigningKeyBytes(JwsHeader header, Claims claims) { public byte[] resolveSigningKeyBytes(JwsHeader header, Claims claims) {
throw new UnsupportedJwtException("The specified SigningKeyResolver implementation does not support " + throw new UnsupportedJwtException("The specified SigningKeyResolver implementation does not support " +
"Claims JWS signing key resolution. Consider overriding either the " + "Claims JWS signing key resolution. Consider overriding either the " +
"resolveSigningKey(JwsHeader, Claims) method or, for HMAC algorithms, the " + "resolveSigningKey(JwsHeader, Claims) method or, for HMAC algorithms, the " +
"resolveSigningKeyBytes(JwsHeader, Claims) method."); "resolveSigningKeyBytes(JwsHeader, Claims) method.");
} }
/** /**
* Convenience method invoked by {@link #resolveSigningKey(JwsHeader, String)} that obtains the necessary signing * Convenience method invoked by {@link #resolveSigningKey(JwsHeader, byte[])} that obtains the necessary signing
* key bytes. This implementation simply throws an exception: if the JWS parsed is a plaintext JWS, you must * key bytes. This implementation simply throws an exception: if the JWS parsed is a content JWS, you must
* override this method or the {@link #resolveSigningKey(JwsHeader, String)} method instead. * override this method or the {@link #resolveSigningKey(JwsHeader, byte[])} method instead.
* *
* @param header the parsed {@link JwsHeader} * @param header the parsed {@link JwsHeader}
* @param payload the parsed String plaintext payload * @param content the byte array payload
* @return the signing key bytes to use to verify the JWS signature. * @return the signing key bytes to use to verify the JWS signature.
*/ */
public byte[] resolveSigningKeyBytes(JwsHeader header, String payload) { @SuppressWarnings("unused")
public byte[] resolveSigningKeyBytes(JwsHeader header, byte[] content) {
throw new UnsupportedJwtException("The specified SigningKeyResolver implementation does not support " + throw new UnsupportedJwtException("The specified SigningKeyResolver implementation does not support " +
"plaintext JWS signing key resolution. Consider overriding either the " + "content JWS signing key resolution. Consider overriding either the " +
"resolveSigningKey(JwsHeader, String) method or, for HMAC algorithms, the " + "resolveSigningKey(JwsHeader, byte[]) method or, for HMAC algorithms, the " +
"resolveSigningKeyBytes(JwsHeader, String) method."); "resolveSigningKeyBytes(JwsHeader, byte[]) method.");
} }
} }

View File

@ -0,0 +1,25 @@
/*
* Copyright (C) 2021 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;
/**
* A JWT {@link Header} that is not integrity protected via either digital signature or encryption. It will
* always have an {@link #getAlgorithm() algorithm} of {@code none}.
*
* @since JJWT_RELEASE_VERSION
*/
public interface UnprotectedHeader extends Header<UnprotectedHeader> {
}

View File

@ -19,17 +19,28 @@ package io.jsonwebtoken;
* Exception thrown when receiving a JWT in a particular format/configuration that does not match the format expected * Exception thrown when receiving a JWT in a particular format/configuration that does not match the format expected
* by the application. * by the application.
* *
* <p>For example, this exception would be thrown if parsing an unsigned plaintext JWT when the application * <p>For example, this exception would be thrown if parsing an unprotected content JWT when the application
* requires a cryptographically signed Claims JWS instead.</p> * requires a cryptographically signed Claims JWS instead.</p>
* *
* @since 0.2 * @since 0.2
*/ */
public class UnsupportedJwtException extends JwtException { public class UnsupportedJwtException extends JwtException {
/**
* Creates a new instance with the specified explanation message.
*
* @param message the message explaining why the exception is thrown.
*/
public UnsupportedJwtException(String message) { public UnsupportedJwtException(String message) {
super(message); super(message);
} }
/**
* Creates a new instance with the specified explanation message and underlying cause.
*
* @param message the message explaining why the exception is thrown.
* @param cause the underlying cause that resulted in this exception being thrown.
*/
public UnsupportedJwtException(String message, Throwable cause) { public UnsupportedJwtException(String message, Throwable cause) {
super(message, cause); super(message, cause);
} }

View File

@ -18,6 +18,9 @@ package io.jsonwebtoken.io;
import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Assert;
/** /**
* Very fast <a href="https://datatracker.ietf.org/doc/html/rfc4648#section-4">Base64</a> decoder guaranteed to
* work in all &gt;= Java 7 JDK and Android environments.
*
* @since 0.10.0 * @since 0.10.0
*/ */
class Base64Decoder extends Base64Support implements Decoder<String, byte[]> { class Base64Decoder extends Base64Support implements Decoder<String, byte[]> {

View File

@ -18,6 +18,9 @@ package io.jsonwebtoken.io;
import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Assert;
/** /**
* Very fast <a href="https://datatracker.ietf.org/doc/html/rfc4648#section-4">Base64</a> encoder guaranteed to
* work in all &gt;= Java 7 JDK and Android environments.
*
* @since 0.10.0 * @since 0.10.0
*/ */
class Base64Encoder extends Base64Support implements Encoder<byte[], String> { class Base64Encoder extends Base64Support implements Encoder<byte[], String> {

View File

@ -18,6 +18,8 @@ package io.jsonwebtoken.io;
import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Assert;
/** /**
* Parent class for Base64 encoders and decoders.
*
* @since 0.10.0 * @since 0.10.0
*/ */
class Base64Support { class Base64Support {

View File

@ -16,6 +16,9 @@
package io.jsonwebtoken.io; package io.jsonwebtoken.io;
/** /**
* Very fast <a href="https://datatracker.ietf.org/doc/html/rfc4648#section-5">Base64Url</a> decoder guaranteed to
* work in all &gt;= Java 7 JDK and Android environments.
*
* @since 0.10.0 * @since 0.10.0
*/ */
class Base64UrlDecoder extends Base64Decoder { class Base64UrlDecoder extends Base64Decoder {

View File

@ -16,6 +16,9 @@
package io.jsonwebtoken.io; package io.jsonwebtoken.io;
/** /**
* Very fast <a href="https://datatracker.ietf.org/doc/html/rfc4648#section-5">Base64Url</a> encoder guaranteed to
* work in all &gt;= Java 7 JDK and Android environments.
*
* @since 0.10.0 * @since 0.10.0
*/ */
class Base64UrlEncoder extends Base64Encoder { class Base64UrlEncoder extends Base64Encoder {

View File

@ -16,14 +16,27 @@
package io.jsonwebtoken.io; package io.jsonwebtoken.io;
/** /**
* An exception thrown when encountering a problem during encoding or decoding.
*
* @since 0.10.0 * @since 0.10.0
*/ */
public class CodecException extends IOException { public class CodecException extends IOException {
/**
* Creates a new instance with the specified explanation message.
*
* @param message the message explaining why the exception is thrown.
*/
public CodecException(String message) { public CodecException(String message) {
super(message); super(message);
} }
/**
* Creates a new instance with the specified explanation message and underlying cause.
*
* @param message the message explaining why the exception is thrown.
* @param cause the underlying cause that resulted in this exception being thrown.
*/
public CodecException(String message, Throwable cause) { public CodecException(String message, Throwable cause) {
super(message, cause); super(message, cause);
} }

View File

@ -16,9 +16,18 @@
package io.jsonwebtoken.io; package io.jsonwebtoken.io;
/** /**
* A decoder converts an already-encoded data value to a desired data type.
*
* @since 0.10.0 * @since 0.10.0
*/ */
public interface Decoder<T, R> { public interface Decoder<T, R> {
/**
* Convert the specified encoded data value into the desired data type.
*
* @param t the encoded data
* @return the resulting expected data
* @throws DecodingException if there is a problem during decoding.
*/
R decode(T t) throws DecodingException; R decode(T t) throws DecodingException;
} }

View File

@ -16,11 +16,24 @@
package io.jsonwebtoken.io; package io.jsonwebtoken.io;
/** /**
* Constant definitions for various decoding algorithms.
*
* @see #BASE64
* @see #BASE64URL
* @since 0.10.0 * @since 0.10.0
*/ */
public final class Decoders { public final class Decoders {
/**
* Very fast <a href="https://datatracker.ietf.org/doc/html/rfc4648#section-4">Base64</a> decoder guaranteed to
* work in all &gt;= Java 7 JDK and Android environments.
*/
public static final Decoder<String, byte[]> BASE64 = new ExceptionPropagatingDecoder<>(new Base64Decoder()); public static final Decoder<String, byte[]> BASE64 = new ExceptionPropagatingDecoder<>(new Base64Decoder());
/**
* Very fast <a href="https://datatracker.ietf.org/doc/html/rfc4648#section-5">Base64Url</a> decoder guaranteed to
* work in all &gt;= Java 7 JDK and Android environments.
*/
public static final Decoder<String, byte[]> BASE64URL = new ExceptionPropagatingDecoder<>(new Base64UrlDecoder()); public static final Decoder<String, byte[]> BASE64URL = new ExceptionPropagatingDecoder<>(new Base64UrlDecoder());
private Decoders() { //prevent instantiation private Decoders() { //prevent instantiation

View File

@ -16,14 +16,27 @@
package io.jsonwebtoken.io; package io.jsonwebtoken.io;
/** /**
* An exception thrown when encountering a problem during decoding.
*
* @since 0.10.0 * @since 0.10.0
*/ */
public class DecodingException extends CodecException { public class DecodingException extends CodecException {
/**
* Creates a new instance with the specified explanation message.
*
* @param message the message explaining why the exception is thrown.
*/
public DecodingException(String message) { public DecodingException(String message) {
super(message); super(message);
} }
/**
* Creates a new instance with the specified explanation message and underlying cause.
*
* @param message the message explaining why the exception is thrown.
* @param cause the underlying cause that resulted in this exception being thrown.
*/
public DecodingException(String message, Throwable cause) { public DecodingException(String message, Throwable cause) {
super(message, cause); super(message, cause);
} }

View File

@ -16,14 +16,27 @@
package io.jsonwebtoken.io; package io.jsonwebtoken.io;
/** /**
* Exception thrown when reconstituting a serialized byte array into a Java object.
*
* @since 0.10.0 * @since 0.10.0
*/ */
public class DeserializationException extends SerialException { public class DeserializationException extends SerialException {
/**
* Creates a new instance with the specified explanation message.
*
* @param msg the message explaining why the exception is thrown.
*/
public DeserializationException(String msg) { public DeserializationException(String msg) {
super(msg); super(msg);
} }
/**
* Creates a new instance with the specified explanation message and underlying cause.
*
* @param message the message explaining why the exception is thrown.
* @param cause the underlying cause that resulted in this exception being thrown.
*/
public DeserializationException(String message, Throwable cause) { public DeserializationException(String message, Throwable cause) {
super(message, cause); super(message, cause);
} }

View File

@ -16,9 +16,19 @@
package io.jsonwebtoken.io; package io.jsonwebtoken.io;
/** /**
* A {@code Deserializer} is able to convert serialized data byte arrays into Java objects.
*
* @param <T> the type of object to be returned as a result of deserialization.
* @since 0.10.0 * @since 0.10.0
*/ */
public interface Deserializer<T> { public interface Deserializer<T> {
/**
* Convert the specified formatted data byte array into a Java object.
*
* @param bytes the formatted data byte array to convert
* @return the reconstituted Java object
* @throws DeserializationException if there is a problem converting the byte array to to an object.
*/
T deserialize(byte[] bytes) throws DeserializationException; T deserialize(byte[] bytes) throws DeserializationException;
} }

View File

@ -16,9 +16,20 @@
package io.jsonwebtoken.io; package io.jsonwebtoken.io;
/** /**
* An encoder converts data of one type into another formatted data value.
*
* @param <T> the type of data to convert
* @param <R> the type of the resulting formatted data
* @since 0.10.0 * @since 0.10.0
*/ */
public interface Encoder<T, R> { public interface Encoder<T, R> {
/**
* Convert the specified data into another formatted data value.
*
* @param t the data to convert
* @return the resulting formatted data value
* @throws EncodingException if there is a problem during encoding
*/
R encode(T t) throws EncodingException; R encode(T t) throws EncodingException;
} }

View File

@ -16,11 +16,24 @@
package io.jsonwebtoken.io; package io.jsonwebtoken.io;
/** /**
* Constant definitions for various encoding algorithms.
*
* @see #BASE64
* @see #BASE64URL
* @since 0.10.0 * @since 0.10.0
*/ */
public final class Encoders { public final class Encoders {
/**
* Very fast <a href="https://datatracker.ietf.org/doc/html/rfc4648#section-4">Base64</a> encoder guaranteed to
* work in all &gt;= Java 7 JDK and Android environments.
*/
public static final Encoder<byte[], String> BASE64 = new ExceptionPropagatingEncoder<>(new Base64Encoder()); public static final Encoder<byte[], String> BASE64 = new ExceptionPropagatingEncoder<>(new Base64Encoder());
/**
* Very fast <a href="https://datatracker.ietf.org/doc/html/rfc4648#section-5">Base64Url</a> encoder guaranteed to
* work in all &gt;= Java 7 JDK and Android environments.
*/
public static final Encoder<byte[], String> BASE64URL = new ExceptionPropagatingEncoder<>(new Base64UrlEncoder()); public static final Encoder<byte[], String> BASE64URL = new ExceptionPropagatingEncoder<>(new Base64UrlEncoder());
private Encoders() { //prevent instantiation private Encoders() { //prevent instantiation

View File

@ -16,10 +16,18 @@
package io.jsonwebtoken.io; package io.jsonwebtoken.io;
/** /**
* An exception thrown when encountering a problem during encoding.
*
* @since 0.10.0 * @since 0.10.0
*/ */
public class EncodingException extends CodecException { public class EncodingException extends CodecException {
/**
* Creates a new instance with the specified explanation message and underlying cause.
*
* @param message the message explaining why the exception is thrown.
* @param cause the underlying cause that resulted in this exception being thrown.
*/
public EncodingException(String message, Throwable cause) { public EncodingException(String message, Throwable cause) {
super(message, cause); super(message, cause);
} }

View File

@ -18,17 +18,33 @@ package io.jsonwebtoken.io;
import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Assert;
/** /**
* Decoder that ensures any exceptions thrown that are <em>not</em> {@link DecodingException}s are wrapped
* and re-thrown as a {@code DecodingException}.
*
* @since 0.10.0 * @since 0.10.0
*/ */
class ExceptionPropagatingDecoder<T, R> implements Decoder<T, R> { class ExceptionPropagatingDecoder<T, R> implements Decoder<T, R> {
private final Decoder<T, R> decoder; private final Decoder<T, R> decoder;
/**
* Creates a new instance, wrapping the specified {@code decoder} to invoke during {@link #decode(Object)}.
*
* @param decoder the decoder to wrap and call during {@link #decode(Object)}
*/
ExceptionPropagatingDecoder(Decoder<T, R> decoder) { ExceptionPropagatingDecoder(Decoder<T, R> decoder) {
Assert.notNull(decoder, "Decoder cannot be null."); Assert.notNull(decoder, "Decoder cannot be null.");
this.decoder = decoder; this.decoder = decoder;
} }
/**
* Decode the specified encoded data, delegating to the wrapped Decoder, wrapping any
* non-{@link DecodingException} as a {@code DecodingException}.
*
* @param t the encoded data
* @return the decoded data
* @throws DecodingException if there is an unexpected problem during decoding.
*/
@Override @Override
public R decode(T t) throws DecodingException { public R decode(T t) throws DecodingException {
Assert.notNull(t, "Decode argument cannot be null."); Assert.notNull(t, "Decode argument cannot be null.");

View File

@ -18,17 +18,33 @@ package io.jsonwebtoken.io;
import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Assert;
/** /**
* Encoder that ensures any exceptions thrown that are <em>not</em> {@link EncodingException}s are wrapped
* and re-thrown as a {@code EncodingException}.
*
* @since 0.10.0 * @since 0.10.0
*/ */
class ExceptionPropagatingEncoder<T, R> implements Encoder<T, R> { class ExceptionPropagatingEncoder<T, R> implements Encoder<T, R> {
private final Encoder<T, R> encoder; private final Encoder<T, R> encoder;
/**
* Creates a new instance, wrapping the specified {@code encoder} to invoke during {@link #encode(Object)}.
*
* @param encoder the encoder to wrap and call during {@link #encode(Object)}
*/
ExceptionPropagatingEncoder(Encoder<T, R> encoder) { ExceptionPropagatingEncoder(Encoder<T, R> encoder) {
Assert.notNull(encoder, "Encoder cannot be null."); Assert.notNull(encoder, "Encoder cannot be null.");
this.encoder = encoder; this.encoder = encoder;
} }
/**
* Encoded the specified data, delegating to the wrapped Encoder, wrapping any
* non-{@link EncodingException} as an {@code EncodingException}.
*
* @param t the data to encode
* @return the encoded data
* @throws EncodingException if there is an unexpected problem during encoding.
*/
@Override @Override
public R encode(T t) throws EncodingException { public R encode(T t) throws EncodingException {
Assert.notNull(t, "Encode argument cannot be null."); Assert.notNull(t, "Encode argument cannot be null.");

View File

@ -18,14 +18,28 @@ package io.jsonwebtoken.io;
import io.jsonwebtoken.JwtException; import io.jsonwebtoken.JwtException;
/** /**
* JJWT's base exception for problems during data input or output operations, such as serialization,
* deserialization, marshalling, unmarshalling, etc.
*
* @since 0.10.0 * @since 0.10.0
*/ */
public class IOException extends JwtException { public class IOException extends JwtException {
/**
* Creates a new instance with the specified explanation message.
*
* @param msg the message explaining why the exception is thrown.
*/
public IOException(String msg) { public IOException(String msg) {
super(msg); super(msg);
} }
/**
* Creates a new instance with the specified explanation message and underlying cause.
*
* @param message the message explaining why the exception is thrown.
* @param cause the underlying cause that resulted in this exception being thrown.
*/
public IOException(String message, Throwable cause) { public IOException(String message, Throwable cause) {
super(message, cause); super(message, cause);
} }

View File

@ -16,14 +16,27 @@
package io.jsonwebtoken.io; package io.jsonwebtoken.io;
/** /**
* An exception thrown during serialization or deserialization.
*
* @since 0.10.0 * @since 0.10.0
*/ */
public class SerialException extends IOException { public class SerialException extends IOException {
/**
* Creates a new instance with the specified explanation message.
*
* @param msg the message explaining why the exception is thrown.
*/
public SerialException(String msg) { public SerialException(String msg) {
super(msg); super(msg);
} }
/**
* Creates a new instance with the specified explanation message and underlying cause.
*
* @param message the message explaining why the exception is thrown.
* @param cause the underlying cause that resulted in this exception being thrown.
*/
public SerialException(String message, Throwable cause) { public SerialException(String message, Throwable cause) {
super(message, cause); super(message, cause);
} }

View File

@ -16,14 +16,27 @@
package io.jsonwebtoken.io; package io.jsonwebtoken.io;
/** /**
* Exception thrown when converting a Java object to a formatted byte array.
*
* @since 0.10.0 * @since 0.10.0
*/ */
public class SerializationException extends SerialException { public class SerializationException extends SerialException {
/**
* Creates a new instance with the specified explanation message.
*
* @param msg the message explaining why the exception is thrown.
*/
public SerializationException(String msg) { public SerializationException(String msg) {
super(msg); super(msg);
} }
/**
* Creates a new instance with the specified explanation message and underlying cause.
*
* @param message the message explaining why the exception is thrown.
* @param cause the underlying cause that resulted in this exception being thrown.
*/
public SerializationException(String message, Throwable cause) { public SerializationException(String message, Throwable cause) {
super(message, cause); super(message, cause);
} }

View File

@ -16,10 +16,21 @@
package io.jsonwebtoken.io; package io.jsonwebtoken.io;
/** /**
* A {@code Serializer} is able to convert a Java object into a formatted data byte array. It is expected this data
* can be reconstituted back into a Java object with a matching {@link Deserializer}.
*
* @param <T> The type of object to serialize.
* @since 0.10.0 * @since 0.10.0
*/ */
public interface Serializer<T> { public interface Serializer<T> {
/**
* Convert the specified Java object into a formatted data byte array.
*
* @param t the object to serialize
* @return the serialized byte array representing the specified object.
* @throws SerializationException if there is a problem converting the object to a byte array.
*/
byte[] serialize(T t) throws SerializationException; byte[] serialize(T t) throws SerializationException;
} }

View File

@ -15,18 +15,105 @@
*/ */
package io.jsonwebtoken.lang; package io.jsonwebtoken.lang;
import java.lang.reflect.Array;
import java.util.List;
/** /**
* Utility methods to work with array instances.
*
* @since 0.6 * @since 0.6
*/ */
public final class Arrays { public final class Arrays {
private Arrays(){} //prevent instantiation private Arrays() {
} //prevent instantiation
/**
* Returns the length of the array, or {@code 0} if the array is {@code null}.
*
* @param a the possibly-null array
* @param <T> the type of elements in the array
* @return the length of the array, or zero if the array is null.
*/
public static <T> int length(T[] a) {
return a == null ? 0 : a.length;
}
/**
* Converts the specified array to a {@link List}. If the array is empty, an empty list will be returned.
*
* @param a the array to represent as a list
* @param <T> the type of elements in the array
* @return the array as a list, or an empty list if the array is empty.
*/
public static <T> List<T> asList(T[] a) {
return Objects.isEmpty(a) ? Collections.<T>emptyList() : java.util.Arrays.asList(a);
}
/**
* Returns the length of the specified byte array, or {@code 0} if the byte array is {@code null}.
*
* @param bytes the array to check
* @return the length of the specified byte array, or {@code 0} if the byte array is {@code null}.
*/
public static int length(byte[] bytes) { public static int length(byte[] bytes) {
return bytes != null ? bytes.length : 0; return bytes != null ? bytes.length : 0;
} }
/**
* Returns the byte array unaltered if it is non-null and has a positive length, otherwise {@code null}.
*
* @param bytes the byte array to check.
* @return the byte array unaltered if it is non-null and has a positive length, otherwise {@code null}.
*/
public static byte[] clean(byte[] bytes) { public static byte[] clean(byte[] bytes) {
return length(bytes) > 0 ? bytes : null; return length(bytes) > 0 ? bytes : null;
} }
/**
* Creates a shallow copy of the specified object or array.
*
* @param obj the object to copy
* @return a shallow copy of the specified object or array.
*/
public static Object copy(Object obj) {
if (obj == null) {
return null;
}
Assert.isTrue(Objects.isArray(obj), "Argument must be an array.");
if (obj instanceof Object[]) {
return ((Object[]) obj).clone();
}
if (obj instanceof boolean[]) {
return ((boolean[]) obj).clone();
}
if (obj instanceof byte[]) {
return ((byte[]) obj).clone();
}
if (obj instanceof char[]) {
return ((char[]) obj).clone();
}
if (obj instanceof double[]) {
return ((double[]) obj).clone();
}
if (obj instanceof float[]) {
return ((float[]) obj).clone();
}
if (obj instanceof int[]) {
return ((int[]) obj).clone();
}
if (obj instanceof long[]) {
return ((long[]) obj).clone();
}
if (obj instanceof short[]) {
return ((short[]) obj).clone();
}
Class<?> componentType = obj.getClass().getComponentType();
int length = Array.getLength(obj);
Object[] copy = (Object[]) Array.newInstance(componentType, length);
for (int i = 0; i < length; i++) {
copy[i] = Array.get(obj, i);
}
return copy;
}
} }

View File

@ -18,16 +18,22 @@ package io.jsonwebtoken.lang;
import java.util.Collection; import java.util.Collection;
import java.util.Map; import java.util.Map;
/**
* Utility methods for providing argument and state assertions to reduce repeating these patterns and otherwise
* increasing cyclomatic complexity.
*/
public final class Assert { public final class Assert {
private Assert(){} //prevent instantiation private Assert() {
} //prevent instantiation
/** /**
* Assert a boolean expression, throwing <code>IllegalArgumentException</code> * Assert a boolean expression, throwing <code>IllegalArgumentException</code>
* if the test result is <code>false</code>. * if the test result is <code>false</code>.
* <pre class="code">Assert.isTrue(i &gt; 0, "The value must be greater than zero");</pre> * <pre class="code">Assert.isTrue(i &gt; 0, "The value must be greater than zero");</pre>
*
* @param expression a boolean expression * @param expression a boolean expression
* @param message the exception message to use if the assertion fails * @param message the exception message to use if the assertion fails
* @throws IllegalArgumentException if expression is <code>false</code> * @throws IllegalArgumentException if expression is <code>false</code>
*/ */
public static void isTrue(boolean expression, String message) { public static void isTrue(boolean expression, String message) {
@ -40,6 +46,7 @@ public final class Assert {
* Assert a boolean expression, throwing <code>IllegalArgumentException</code> * Assert a boolean expression, throwing <code>IllegalArgumentException</code>
* if the test result is <code>false</code>. * if the test result is <code>false</code>.
* <pre class="code">Assert.isTrue(i &gt; 0);</pre> * <pre class="code">Assert.isTrue(i &gt; 0);</pre>
*
* @param expression a boolean expression * @param expression a boolean expression
* @throws IllegalArgumentException if expression is <code>false</code> * @throws IllegalArgumentException if expression is <code>false</code>
*/ */
@ -50,7 +57,8 @@ public final class Assert {
/** /**
* Assert that an object is <code>null</code> . * Assert that an object is <code>null</code> .
* <pre class="code">Assert.isNull(value, "The value must be null");</pre> * <pre class="code">Assert.isNull(value, "The value must be null");</pre>
* @param object the object to check *
* @param object the object to check
* @param message the exception message to use if the assertion fails * @param message the exception message to use if the assertion fails
* @throws IllegalArgumentException if the object is not <code>null</code> * @throws IllegalArgumentException if the object is not <code>null</code>
*/ */
@ -63,6 +71,7 @@ public final class Assert {
/** /**
* Assert that an object is <code>null</code> . * Assert that an object is <code>null</code> .
* <pre class="code">Assert.isNull(value);</pre> * <pre class="code">Assert.isNull(value);</pre>
*
* @param object the object to check * @param object the object to check
* @throws IllegalArgumentException if the object is not <code>null</code> * @throws IllegalArgumentException if the object is not <code>null</code>
*/ */
@ -73,19 +82,24 @@ public final class Assert {
/** /**
* Assert that an object is not <code>null</code> . * Assert that an object is not <code>null</code> .
* <pre class="code">Assert.notNull(clazz, "The class must not be null");</pre> * <pre class="code">Assert.notNull(clazz, "The class must not be null");</pre>
* @param object the object to check *
* @param object the object to check
* @param <T> the type of object
* @param message the exception message to use if the assertion fails * @param message the exception message to use if the assertion fails
* @return the non-null object
* @throws IllegalArgumentException if the object is <code>null</code> * @throws IllegalArgumentException if the object is <code>null</code>
*/ */
public static void notNull(Object object, String message) { public static <T> T notNull(T object, String message) {
if (object == null) { if (object == null) {
throw new IllegalArgumentException(message); throw new IllegalArgumentException(message);
} }
return object;
} }
/** /**
* Assert that an object is not <code>null</code> . * Assert that an object is not <code>null</code> .
* <pre class="code">Assert.notNull(clazz);</pre> * <pre class="code">Assert.notNull(clazz);</pre>
*
* @param object the object to check * @param object the object to check
* @throws IllegalArgumentException if the object is <code>null</code> * @throws IllegalArgumentException if the object is <code>null</code>
*/ */
@ -97,7 +111,8 @@ public final class Assert {
* Assert that the given String is not empty; that is, * Assert that the given String is not empty; that is,
* it must not be <code>null</code> and not the empty String. * it must not be <code>null</code> and not the empty String.
* <pre class="code">Assert.hasLength(name, "Name must not be empty");</pre> * <pre class="code">Assert.hasLength(name, "Name must not be empty");</pre>
* @param text the String to check *
* @param text the String to check
* @param message the exception message to use if the assertion fails * @param message the exception message to use if the assertion fails
* @see Strings#hasLength * @see Strings#hasLength
*/ */
@ -111,32 +126,37 @@ public final class Assert {
* Assert that the given String is not empty; that is, * Assert that the given String is not empty; that is,
* it must not be <code>null</code> and not the empty String. * it must not be <code>null</code> and not the empty String.
* <pre class="code">Assert.hasLength(name);</pre> * <pre class="code">Assert.hasLength(name);</pre>
*
* @param text the String to check * @param text the String to check
* @see Strings#hasLength * @see Strings#hasLength
*/ */
public static void hasLength(String text) { public static void hasLength(String text) {
hasLength(text, hasLength(text,
"[Assertion failed] - this String argument must have length; it must not be null or empty"); "[Assertion failed] - this String argument must have length; it must not be null or empty");
} }
/** /**
* Assert that the given String has valid text content; that is, it must not * Assert that the given String has valid text content; that is, it must not
* be <code>null</code> and must contain at least one non-whitespace character. * be <code>null</code> and must contain at least one non-whitespace character.
* <pre class="code">Assert.hasText(name, "'name' must not be empty");</pre> * <pre class="code">Assert.hasText(name, "'name' must not be empty");</pre>
* @param text the String to check *
* @param text the String to check
* @param message the exception message to use if the assertion fails * @param message the exception message to use if the assertion fails
* @return the string if it has text
* @see Strings#hasText * @see Strings#hasText
*/ */
public static void hasText(String text, String message) { public static String hasText(String text, String message) {
if (!Strings.hasText(text)) { if (!Strings.hasText(text)) {
throw new IllegalArgumentException(message); throw new IllegalArgumentException(message);
} }
return text;
} }
/** /**
* Assert that the given String has valid text content; that is, it must not * Assert that the given String has valid text content; that is, it must not
* be <code>null</code> and must contain at least one non-whitespace character. * be <code>null</code> and must contain at least one non-whitespace character.
* <pre class="code">Assert.hasText(name, "'name' must not be empty");</pre> * <pre class="code">Assert.hasText(name, "'name' must not be empty");</pre>
*
* @param text the String to check * @param text the String to check
* @see Strings#hasText * @see Strings#hasText
*/ */
@ -148,13 +168,14 @@ public final class Assert {
/** /**
* Assert that the given text does not contain the given substring. * Assert that the given text does not contain the given substring.
* <pre class="code">Assert.doesNotContain(name, "rod", "Name must not contain 'rod'");</pre> * <pre class="code">Assert.doesNotContain(name, "rod", "Name must not contain 'rod'");</pre>
*
* @param textToSearch the text to search * @param textToSearch the text to search
* @param substring the substring to find within the text * @param substring the substring to find within the text
* @param message the exception message to use if the assertion fails * @param message the exception message to use if the assertion fails
*/ */
public static void doesNotContain(String textToSearch, String substring, String message) { public static void doesNotContain(String textToSearch, String substring, String message) {
if (Strings.hasLength(textToSearch) && Strings.hasLength(substring) && if (Strings.hasLength(textToSearch) && Strings.hasLength(substring) &&
textToSearch.indexOf(substring) != -1) { textToSearch.indexOf(substring) != -1) {
throw new IllegalArgumentException(message); throw new IllegalArgumentException(message);
} }
} }
@ -162,12 +183,13 @@ public final class Assert {
/** /**
* Assert that the given text does not contain the given substring. * Assert that the given text does not contain the given substring.
* <pre class="code">Assert.doesNotContain(name, "rod");</pre> * <pre class="code">Assert.doesNotContain(name, "rod");</pre>
*
* @param textToSearch the text to search * @param textToSearch the text to search
* @param substring the substring to find within the text * @param substring the substring to find within the text
*/ */
public static void doesNotContain(String textToSearch, String substring) { public static void doesNotContain(String textToSearch, String substring) {
doesNotContain(textToSearch, substring, doesNotContain(textToSearch, substring,
"[Assertion failed] - this String argument must not contain the substring [" + substring + "]"); "[Assertion failed] - this String argument must not contain the substring [" + substring + "]");
} }
@ -175,20 +197,24 @@ public final class Assert {
* Assert that an array has elements; that is, it must not be * Assert that an array has elements; that is, it must not be
* <code>null</code> and must have at least one element. * <code>null</code> and must have at least one element.
* <pre class="code">Assert.notEmpty(array, "The array must have elements");</pre> * <pre class="code">Assert.notEmpty(array, "The array must have elements");</pre>
* @param array the array to check *
* @param array the array to check
* @param message the exception message to use if the assertion fails * @param message the exception message to use if the assertion fails
* @return the non-empty array for immediate use
* @throws IllegalArgumentException if the object array is <code>null</code> or has no elements * @throws IllegalArgumentException if the object array is <code>null</code> or has no elements
*/ */
public static void notEmpty(Object[] array, String message) { public static Object[] notEmpty(Object[] array, String message) {
if (Objects.isEmpty(array)) { if (Objects.isEmpty(array)) {
throw new IllegalArgumentException(message); throw new IllegalArgumentException(message);
} }
return array;
} }
/** /**
* Assert that an array has elements; that is, it must not be * Assert that an array has elements; that is, it must not be
* <code>null</code> and must have at least one element. * <code>null</code> and must have at least one element.
* <pre class="code">Assert.notEmpty(array);</pre> * <pre class="code">Assert.notEmpty(array);</pre>
*
* @param array the array to check * @param array the array to check
* @throws IllegalArgumentException if the object array is <code>null</code> or has no elements * @throws IllegalArgumentException if the object array is <code>null</code> or has no elements
*/ */
@ -196,17 +222,44 @@ public final class Assert {
notEmpty(array, "[Assertion failed] - this array must not be empty: it must contain at least 1 element"); notEmpty(array, "[Assertion failed] - this array must not be empty: it must contain at least 1 element");
} }
public static void notEmpty(byte[] array, String msg) { /**
* Assert that the specified byte array is not null and has at least one byte element.
*
* @param array the byte array to check
* @param msg the exception message to use if the assertion fails
* @return the byte array if the assertion passes
* @throws IllegalArgumentException if the byte array is null or empty
* @since JJWT_RELEASE_VERSION
*/
public static byte[] notEmpty(byte[] array, String msg) {
if (Objects.isEmpty(array)) { if (Objects.isEmpty(array)) {
throw new IllegalArgumentException(msg); throw new IllegalArgumentException(msg);
} }
return array;
}
/**
* Assert that the specified character array is not null and has at least one byte element.
*
* @param chars the character array to check
* @param msg the exception message to use if the assertion fails
* @return the character array if the assertion passes
* @throws IllegalArgumentException if the character array is null or empty
* @since JJWT_RELEASE_VERSION
*/
public static char[] notEmpty(char[] chars, String msg) {
if (Objects.isEmpty(chars)) {
throw new IllegalArgumentException(msg);
}
return chars;
} }
/** /**
* Assert that an array has no null elements. * Assert that an array has no null elements.
* Note: Does not complain if the array is empty! * Note: Does not complain if the array is empty!
* <pre class="code">Assert.noNullElements(array, "The array must have non-null elements");</pre> * <pre class="code">Assert.noNullElements(array, "The array must have non-null elements");</pre>
* @param array the array to check *
* @param array the array to check
* @param message the exception message to use if the assertion fails * @param message the exception message to use if the assertion fails
* @throws IllegalArgumentException if the object array contains a <code>null</code> element * @throws IllegalArgumentException if the object array contains a <code>null</code> element
*/ */
@ -224,6 +277,7 @@ public final class Assert {
* Assert that an array has no null elements. * Assert that an array has no null elements.
* Note: Does not complain if the array is empty! * Note: Does not complain if the array is empty!
* <pre class="code">Assert.noNullElements(array);</pre> * <pre class="code">Assert.noNullElements(array);</pre>
*
* @param array the array to check * @param array the array to check
* @throws IllegalArgumentException if the object array contains a <code>null</code> element * @throws IllegalArgumentException if the object array contains a <code>null</code> element
*/ */
@ -235,46 +289,56 @@ public final class Assert {
* Assert that a collection has elements; that is, it must not be * Assert that a collection has elements; that is, it must not be
* <code>null</code> and must have at least one element. * <code>null</code> and must have at least one element.
* <pre class="code">Assert.notEmpty(collection, "Collection must have elements");</pre> * <pre class="code">Assert.notEmpty(collection, "Collection must have elements");</pre>
*
* @param collection the collection to check * @param collection the collection to check
* @param message the exception message to use if the assertion fails * @param <T> the type of collection
* @param message the exception message to use if the assertion fails
* @return the non-null, non-empty collection
* @throws IllegalArgumentException if the collection is <code>null</code> or has no elements * @throws IllegalArgumentException if the collection is <code>null</code> or has no elements
*/ */
public static void notEmpty(Collection collection, String message) { public static <T extends Collection<?>> T notEmpty(T collection, String message) {
if (Collections.isEmpty(collection)) { if (Collections.isEmpty(collection)) {
throw new IllegalArgumentException(message); throw new IllegalArgumentException(message);
} }
return collection;
} }
/** /**
* Assert that a collection has elements; that is, it must not be * Assert that a collection has elements; that is, it must not be
* <code>null</code> and must have at least one element. * <code>null</code> and must have at least one element.
* <pre class="code">Assert.notEmpty(collection, "Collection must have elements");</pre> * <pre class="code">Assert.notEmpty(collection, "Collection must have elements");</pre>
*
* @param collection the collection to check * @param collection the collection to check
* @throws IllegalArgumentException if the collection is <code>null</code> or has no elements * @throws IllegalArgumentException if the collection is <code>null</code> or has no elements
*/ */
public static void notEmpty(Collection collection) { public static void notEmpty(Collection<?> collection) {
notEmpty(collection, notEmpty(collection,
"[Assertion failed] - this collection must not be empty: it must contain at least 1 element"); "[Assertion failed] - this collection must not be empty: it must contain at least 1 element");
} }
/** /**
* Assert that a Map has entries; that is, it must not be <code>null</code> * Assert that a Map has entries; that is, it must not be <code>null</code>
* and must have at least one entry. * and must have at least one entry.
* <pre class="code">Assert.notEmpty(map, "Map must have entries");</pre> * <pre class="code">Assert.notEmpty(map, "Map must have entries");</pre>
* @param map the map to check *
* @param map the map to check
* @param <T> the type of Map to check
* @param message the exception message to use if the assertion fails * @param message the exception message to use if the assertion fails
* @return the non-null, non-empty map
* @throws IllegalArgumentException if the map is <code>null</code> or has no entries * @throws IllegalArgumentException if the map is <code>null</code> or has no entries
*/ */
public static void notEmpty(Map map, String message) { public static <T extends Map<?, ?>> T notEmpty(T map, String message) {
if (Collections.isEmpty(map)) { if (Collections.isEmpty(map)) {
throw new IllegalArgumentException(message); throw new IllegalArgumentException(message);
} }
return map;
} }
/** /**
* Assert that a Map has entries; that is, it must not be <code>null</code> * Assert that a Map has entries; that is, it must not be <code>null</code>
* and must have at least one entry. * and must have at least one entry.
* <pre class="code">Assert.notEmpty(map);</pre> * <pre class="code">Assert.notEmpty(map);</pre>
*
* @param map the map to check * @param map the map to check
* @throws IllegalArgumentException if the map is <code>null</code> or has no entries * @throws IllegalArgumentException if the map is <code>null</code> or has no entries
*/ */
@ -286,41 +350,49 @@ public final class Assert {
/** /**
* Assert that the provided object is an instance of the provided class. * Assert that the provided object is an instance of the provided class.
* <pre class="code">Assert.instanceOf(Foo.class, foo);</pre> * <pre class="code">Assert.instanceOf(Foo.class, foo);</pre>
*
* @param <T> the type of instance expected
* @param clazz the required class * @param clazz the required class
* @param obj the object to check * @param obj the object to check
* @return the expected instance of type {@code T}
* @throws IllegalArgumentException if the object is not an instance of clazz * @throws IllegalArgumentException if the object is not an instance of clazz
* @see Class#isInstance * @see Class#isInstance
*/ */
public static void isInstanceOf(Class clazz, Object obj) { public static <T> T isInstanceOf(Class<T> clazz, Object obj) {
isInstanceOf(clazz, obj, ""); return isInstanceOf(clazz, obj, "");
} }
/** /**
* Assert that the provided object is an instance of the provided class. * Assert that the provided object is an instance of the provided class.
* <pre class="code">Assert.instanceOf(Foo.class, foo);</pre> * <pre class="code">Assert.instanceOf(Foo.class, foo);</pre>
* @param type the type to check against *
* @param obj the object to check * @param type the type to check against
* @param <T> the object's expected type
* @param obj the object to check
* @param message a message which will be prepended to the message produced by * @param message a message which will be prepended to the message produced by
* the function itself, and which may be used to provide context. It should * the function itself, and which may be used to provide context. It should
* normally end in a ": " or ". " so that the function generate message looks * normally end in a ": " or ". " so that the function generate message looks
* ok when prepended to it. * ok when prepended to it.
* @return the non-null object IFF it is an instance of the specified {@code type}.
* @throws IllegalArgumentException if the object is not an instance of clazz * @throws IllegalArgumentException if the object is not an instance of clazz
* @see Class#isInstance * @see Class#isInstance
*/ */
public static void isInstanceOf(Class type, Object obj, String message) { public static <T> T isInstanceOf(Class<T> type, Object obj, String message) {
notNull(type, "Type to check against must not be null"); notNull(type, "Type to check against must not be null");
if (!type.isInstance(obj)) { if (!type.isInstance(obj)) {
throw new IllegalArgumentException(message + throw new IllegalArgumentException(message +
"Object of class [" + (obj != null ? obj.getClass().getName() : "null") + "Object of class [" + (obj != null ? obj.getClass().getName() : "null") +
"] must be an instance of " + type); "] must be an instance of " + type);
} }
return type.cast(obj);
} }
/** /**
* Assert that <code>superType.isAssignableFrom(subType)</code> is <code>true</code>. * Assert that <code>superType.isAssignableFrom(subType)</code> is <code>true</code>.
* <pre class="code">Assert.isAssignable(Number.class, myClass);</pre> * <pre class="code">Assert.isAssignable(Number.class, myClass);</pre>
*
* @param superType the super type to check * @param superType the super type to check
* @param subType the sub type to check * @param subType the sub type to check
* @throws IllegalArgumentException if the classes are not assignable * @throws IllegalArgumentException if the classes are not assignable
*/ */
public static void isAssignable(Class superType, Class subType) { public static void isAssignable(Class superType, Class subType) {
@ -330,12 +402,13 @@ public final class Assert {
/** /**
* Assert that <code>superType.isAssignableFrom(subType)</code> is <code>true</code>. * Assert that <code>superType.isAssignableFrom(subType)</code> is <code>true</code>.
* <pre class="code">Assert.isAssignable(Number.class, myClass);</pre> * <pre class="code">Assert.isAssignable(Number.class, myClass);</pre>
*
* @param superType the super type to check against * @param superType the super type to check against
* @param subType the sub type to check * @param subType the sub type to check
* @param message a message which will be prepended to the message produced by * @param message a message which will be prepended to the message produced by
* the function itself, and which may be used to provide context. It should * the function itself, and which may be used to provide context. It should
* normally end in a ": " or ". " so that the function generate message looks * normally end in a ": " or ". " so that the function generate message looks
* ok when prepended to it. * ok when prepended to it.
* @throws IllegalArgumentException if the classes are not assignable * @throws IllegalArgumentException if the classes are not assignable
*/ */
public static void isAssignable(Class superType, Class subType, String message) { public static void isAssignable(Class superType, Class subType, String message) {
@ -345,14 +418,54 @@ public final class Assert {
} }
} }
/**
* Asserts that a specified {@code value} is equal to the given {@code requirement}, throwing
* an {@link IllegalArgumentException} with the given message if not.
*
* @param <T> the type of argument
* @param requirement the integer that {@code value} must be greater than
* @param value the value to check
* @param msg the message to use for the {@code IllegalArgumentException} if thrown.
* @return {@code value} if greater than the specified {@code requirement}.
* @since JJWT_RELEASE_VERSION
*/
public static <T extends Number> T eq(T requirement, T value, String msg) {
notNull(requirement, "requirement cannot be null.");
notNull(value, "value cannot be null.");
if (!requirement.equals(value)) {
throw new IllegalArgumentException(msg);
}
return value;
}
/**
* Asserts that a specified {@code value} is greater than the given {@code requirement}, throwing
* an {@link IllegalArgumentException} with the given message if not.
*
* @param value the value to check
* @param requirement the integer that {@code value} must be greater than
* @param msg the message to use for the {@code IllegalArgumentException} if thrown.
* @return {@code value} if greater than the specified {@code requirement}.
* @since JJWT_RELEASE_VERSION
*/
public static Integer gt(Integer value, Integer requirement, String msg) {
notNull(value, "value cannot be null.");
notNull(requirement, "requirement cannot be null.");
if (!(value > requirement)) {
throw new IllegalArgumentException(msg);
}
return value;
}
/** /**
* Assert a boolean expression, throwing <code>IllegalStateException</code> * Assert a boolean expression, throwing <code>IllegalStateException</code>
* if the test result is <code>false</code>. Call isTrue if you wish to * if the test result is <code>false</code>. Call isTrue if you wish to
* throw IllegalArgumentException on an assertion failure. * throw IllegalArgumentException on an assertion failure.
* <pre class="code">Assert.state(id == null, "The id property must not already be initialized");</pre> * <pre class="code">Assert.state(id == null, "The id property must not already be initialized");</pre>
*
* @param expression a boolean expression * @param expression a boolean expression
* @param message the exception message to use if the assertion fails * @param message the exception message to use if the assertion fails
* @throws IllegalStateException if expression is <code>false</code> * @throws IllegalStateException if expression is <code>false</code>
*/ */
public static void state(boolean expression, String message) { public static void state(boolean expression, String message) {
@ -367,6 +480,7 @@ public final class Assert {
* <p>Call {@link #isTrue(boolean)} if you wish to * <p>Call {@link #isTrue(boolean)} if you wish to
* throw {@link IllegalArgumentException} on an assertion failure. * throw {@link IllegalArgumentException} on an assertion failure.
* <pre class="code">Assert.state(id == null);</pre> * <pre class="code">Assert.state(id == null);</pre>
*
* @param expression a boolean expression * @param expression a boolean expression
* @throws IllegalStateException if the supplied expression is <code>false</code> * @throws IllegalStateException if the supplied expression is <code>false</code>
*/ */
@ -374,4 +488,21 @@ public final class Assert {
state(expression, "[Assertion failed] - this state invariant must be true"); state(expression, "[Assertion failed] - this state invariant must be true");
} }
/**
* Asserts that the specified {@code value} is not null, otherwise throws an
* {@link IllegalStateException} with the specified {@code msg}. Intended to be used with
* code invariants (as opposed to method arguments, like {@link #notNull(Object)}).
*
* @param value value to assert is not null
* @param msg exception message to use if {@code value} is null
* @throws IllegalStateException with the specified {@code msg} if {@code value} is null.
* @since JJWT_RELEASE_VERSION
*/
public static <T> T stateNotNull(T value, String msg) throws IllegalStateException {
if (value == null) {
throw new IllegalStateException(msg);
}
return value;
}
} }

View File

@ -0,0 +1,32 @@
/*
* Copyright (C) 2022 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.lang;
/**
* Type-safe interface that reflects the <a href="https://en.wikipedia.org/wiki/Builder_pattern">Builder pattern</a>.
*
* @param <T> The type of object that will be created each time {@link #build()} is invoked.
* @since JJWT_RELEASE_VERSION
*/
public interface Builder<T> {
/**
* Creates and returns a new instance of type {@code T}.
*
* @return a new instance of type {@code T}.
*/
T build();
}

View File

@ -17,41 +17,36 @@ package io.jsonwebtoken.lang;
import java.io.InputStream; import java.io.InputStream;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
/** /**
* Utility methods for working with {@link Class}es.
*
* @since 0.1 * @since 0.1
*/ */
public final class Classes { public final class Classes {
private Classes() {} //prevent instantiation private Classes() {
} //prevent instantiation
/**
* @since 0.1
*/
private static final ClassLoaderAccessor THREAD_CL_ACCESSOR = new ExceptionIgnoringAccessor() { private static final ClassLoaderAccessor THREAD_CL_ACCESSOR = new ExceptionIgnoringAccessor() {
@Override @Override
protected ClassLoader doGetClassLoader() throws Throwable { protected ClassLoader doGetClassLoader() {
return Thread.currentThread().getContextClassLoader(); return Thread.currentThread().getContextClassLoader();
} }
}; };
/**
* @since 0.1
*/
private static final ClassLoaderAccessor CLASS_CL_ACCESSOR = new ExceptionIgnoringAccessor() { private static final ClassLoaderAccessor CLASS_CL_ACCESSOR = new ExceptionIgnoringAccessor() {
@Override @Override
protected ClassLoader doGetClassLoader() throws Throwable { protected ClassLoader doGetClassLoader() {
return Classes.class.getClassLoader(); return Classes.class.getClassLoader();
} }
}; };
/**
* @since 0.1
*/
private static final ClassLoaderAccessor SYSTEM_CL_ACCESSOR = new ExceptionIgnoringAccessor() { private static final ClassLoaderAccessor SYSTEM_CL_ACCESSOR = new ExceptionIgnoringAccessor() {
@Override @Override
protected ClassLoader doGetClassLoader() throws Throwable { protected ClassLoader doGetClassLoader() {
return ClassLoader.getSystemClassLoader(); return ClassLoader.getSystemClassLoader();
} }
}; };
@ -65,14 +60,14 @@ public final class Classes {
* the JRE's <code>ClassNotFoundException</code>. * the JRE's <code>ClassNotFoundException</code>.
* *
* @param fqcn the fully qualified class name to load * @param fqcn the fully qualified class name to load
* @param <T> The type of Class returned * @param <T> The type of Class returned
* @return the located class * @return the located class
* @throws UnknownClassException if the class cannot be found. * @throws UnknownClassException if the class cannot be found.
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static <T> Class<T> forName(String fqcn) throws UnknownClassException { public static <T> Class<T> forName(String fqcn) throws UnknownClassException {
Class clazz = THREAD_CL_ACCESSOR.loadClass(fqcn); Class<?> clazz = THREAD_CL_ACCESSOR.loadClass(fqcn);
if (clazz == null) { if (clazz == null) {
clazz = CLASS_CL_ACCESSOR.loadClass(fqcn); clazz = CLASS_CL_ACCESSOR.loadClass(fqcn);
@ -93,7 +88,7 @@ public final class Classes {
throw new UnknownClassException(msg); throw new UnknownClassException(msg);
} }
return clazz; return (Class<T>) clazz;
} }
/** /**
@ -123,6 +118,14 @@ public final class Classes {
return is; return is;
} }
/**
* Returns {@code true} if the specified {@code fullyQualifiedClassName} can be found in any of the thread
* context, class, or system classloaders, or {@code false} otherwise.
*
* @param fullyQualifiedClassName the fully qualified class name to check
* @return {@code true} if the specified {@code fullyQualifiedClassName} can be found in any of the thread
* context, class, or system classloaders, or {@code false} otherwise.
*/
public static boolean isAvailable(String fullyQualifiedClassName) { public static boolean isAvailable(String fullyQualifiedClassName) {
try { try {
forName(fullyQualifiedClassName); forName(fullyQualifiedClassName);
@ -132,22 +135,56 @@ public final class Classes {
} }
} }
/**
* Creates and returns a new instance of the class with the specified fully qualified class name using the
* classes default no-argument constructor.
*
* @param fqcn the fully qualified class name
* @param <T> the type of object created
* @return a new instance of the specified class name
*/
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static <T> T newInstance(String fqcn) { public static <T> T newInstance(String fqcn) {
return (T)newInstance(forName(fqcn)); return (T) newInstance(forName(fqcn));
} }
public static <T> T newInstance(String fqcn, Class[] ctorArgTypes, Object... args) { /**
* Creates and returns a new instance of the specified fully qualified class name using the
* specified {@code args} arguments provided to the constructor with {@code ctorArgTypes}
*
* @param fqcn the fully qualified class name
* @param ctorArgTypes the argument types of the constructor to invoke
* @param args the arguments to supply when invoking the constructor
* @param <T> the type of object created
* @return the newly created object
*/
public static <T> T newInstance(String fqcn, Class<?>[] ctorArgTypes, Object... args) {
Class<T> clazz = forName(fqcn); Class<T> clazz = forName(fqcn);
Constructor<T> ctor = getConstructor(clazz, ctorArgTypes); Constructor<T> ctor = getConstructor(clazz, ctorArgTypes);
return instantiate(ctor, args); return instantiate(ctor, args);
} }
/**
* Creates and returns a new instance of the specified fully qualified class name using a constructor that matches
* the specified {@code args} arguments.
*
* @param fqcn fully qualified class name
* @param args the arguments to supply to the constructor
* @param <T> the type of the object created
* @return the newly created object
*/
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static <T> T newInstance(String fqcn, Object... args) { public static <T> T newInstance(String fqcn, Object... args) {
return (T)newInstance(forName(fqcn), args); return (T) newInstance(forName(fqcn), args);
} }
/**
* Creates a new instance of the specified {@code clazz} via {@code clazz.newInstance()}.
*
* @param clazz the class to invoke
* @param <T> the type of the object created
* @return the newly created object
*/
public static <T> T newInstance(Class<T> clazz) { public static <T> T newInstance(Class<T> clazz) {
if (clazz == null) { if (clazz == null) {
String msg = "Class method parameter cannot be null."; String msg = "Class method parameter cannot be null.";
@ -160,8 +197,17 @@ public final class Classes {
} }
} }
/**
* Returns a new instance of the specified {@code clazz}, invoking the associated constructor with the specified
* {@code args} arguments.
*
* @param clazz the class to invoke
* @param args the arguments matching an associated class constructor
* @param <T> the type of the created object
* @return the newly created object
*/
public static <T> T newInstance(Class<T> clazz, Object... args) { public static <T> T newInstance(Class<T> clazz, Object... args) {
Class[] argTypes = new Class[args.length]; Class<?>[] argTypes = new Class[args.length];
for (int i = 0; i < args.length; i++) { for (int i = 0; i < args.length; i++) {
argTypes[i] = args[i].getClass(); argTypes[i] = args[i].getClass();
} }
@ -169,7 +215,17 @@ public final class Classes {
return instantiate(ctor, args); return instantiate(ctor, args);
} }
public static <T> Constructor<T> getConstructor(Class<T> clazz, Class... argTypes) { /**
* Returns the {@link Constructor} for the specified {@code Class} with arguments matching the specified
* {@code argTypes}.
*
* @param clazz the class to inspect
* @param argTypes the argument types for the desired constructor
* @param <T> the type of object to create
* @return the constructor matching the specified argument types
* @throws IllegalStateException if the constructor for the specified {@code argTypes} does not exist.
*/
public static <T> Constructor<T> getConstructor(Class<T> clazz, Class<?>... argTypes) throws IllegalStateException {
try { try {
return clazz.getConstructor(argTypes); return clazz.getConstructor(argTypes);
} catch (NoSuchMethodException e) { } catch (NoSuchMethodException e) {
@ -178,6 +234,16 @@ public final class Classes {
} }
/**
* Creates a new object using the specified {@link Constructor}, invoking it with the specified constructor
* {@code args} arguments.
*
* @param ctor the constructor to invoke
* @param args the arguments to supply to the constructor
* @param <T> the type of object to create
* @return the new object instance
* @throws InstantiationException if the constructor cannot be invoked successfully
*/
public static <T> T instantiate(Constructor<T> ctor, Object... args) { public static <T> T instantiate(Constructor<T> ctor, Object... args) {
try { try {
return ctor.newInstance(args); return ctor.newInstance(args);
@ -190,24 +256,51 @@ public final class Classes {
/** /**
* Invokes the fully qualified class name's method named {@code methodName} with parameters of type {@code argTypes} * Invokes the fully qualified class name's method named {@code methodName} with parameters of type {@code argTypes}
* using the {@code args} as the method arguments. * using the {@code args} as the method arguments.
* @param fqcn fully qualified class name to locate *
* @param fqcn fully qualified class name to locate
* @param methodName name of the method to invoke on the class * @param methodName name of the method to invoke on the class
* @param argTypes the method argument types supported by the {@code methodName} method * @param argTypes the method argument types supported by the {@code methodName} method
* @param args the runtime arguments to use when invoking the located class method * @param args the runtime arguments to use when invoking the located class method
* @param <T> the expected type of the object returned from the invoked method. * @param <T> the expected type of the object returned from the invoked method.
* @return the result returned by the invoked method * @return the result returned by the invoked method
* @since 0.10.0 * @since 0.10.0
*/ */
@SuppressWarnings("unchecked") public static <T> T invokeStatic(String fqcn, String methodName, Class<?>[] argTypes, Object... args) {
public static <T> T invokeStatic(String fqcn, String methodName, Class[] argTypes, Object... args) {
try { try {
Class clazz = Classes.forName(fqcn); Class<?> clazz = Classes.forName(fqcn);
Method method = clazz.getDeclaredMethod(methodName, argTypes); return invokeStatic(clazz, methodName, argTypes, args);
method.setAccessible(true);
return(T)method.invoke(null, args);
} catch (Exception e) { } catch (Exception e) {
String msg = "Unable to invoke class method " + fqcn + "#" + methodName + ". Ensure the necessary " + String msg = "Unable to invoke class method " + fqcn + "#" + methodName + ". Ensure the necessary " +
"implementation is in the runtime classpath."; "implementation is in the runtime classpath.";
throw new IllegalStateException(msg, e);
}
}
/**
* Invokes the {@code clazz}'s matching static method (named {@code methodName} with exact argument types
* of {@code argTypes}) with the given {@code args} arguments, and returns the method return value.
*
* @param clazz the class to invoke
* @param methodName the name of the static method on {@code clazz} to invoke
* @param argTypes the types of the arguments accepted by the method
* @param args the actual runtime arguments to use when invoking the method
* @param <T> the type of object expected to be returned from the method
* @return the result returned by the invoked method.
* @since JJWT_RELEASE_VERSION
*/
@SuppressWarnings("unchecked")
public static <T> T invokeStatic(Class<?> clazz, String methodName, Class<?>[] argTypes, Object... args) {
try {
Method method = clazz.getDeclaredMethod(methodName, argTypes);
method.setAccessible(true);
return (T) method.invoke(null, args);
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
Throwable cause = e.getCause();
if (cause instanceof RuntimeException) {
throw ((RuntimeException) cause); //propagate
}
String msg = "Unable to invoke class method " + clazz.getName() + "#" + methodName +
". Ensure the necessary implementation is in the runtime classpath.";
throw new IllegalStateException(msg, e); throw new IllegalStateException(msg, e);
} }
} }
@ -215,8 +308,8 @@ public final class Classes {
/** /**
* @since 1.0 * @since 1.0
*/ */
private static interface ClassLoaderAccessor { private interface ClassLoaderAccessor {
Class loadClass(String fqcn); Class<?> loadClass(String fqcn);
InputStream getResourceStream(String name); InputStream getResourceStream(String name);
} }
@ -226,8 +319,8 @@ public final class Classes {
*/ */
private static abstract class ExceptionIgnoringAccessor implements ClassLoaderAccessor { private static abstract class ExceptionIgnoringAccessor implements ClassLoaderAccessor {
public Class loadClass(String fqcn) { public Class<?> loadClass(String fqcn) {
Class clazz = null; Class<?> clazz = null;
ClassLoader cl = getClassLoader(); ClassLoader cl = getClassLoader();
if (cl != null) { if (cl != null) {
try { try {

View File

@ -20,22 +20,155 @@ import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Properties; import java.util.Properties;
import java.util.Set;
/**
* Utility methods for working with {@link Collection}s, {@link List}s, {@link Set}s, and {@link Maps}.
*/
@SuppressWarnings({"unused", "rawtypes"})
public final class Collections { public final class Collections {
private Collections(){} //prevent instantiation private Collections() {
} //prevent instantiation
/**
* Returns a type-safe immutable empty {@code List}.
*
* @param <T> list element type
* @return a type-safe immutable empty {@code List}.
*/
public static <T> List<T> emptyList() {
return java.util.Collections.emptyList();
}
/**
* Returns a type-safe immutable empty {@code Set}.
*
* @param <T> set element type
* @return a type-safe immutable empty {@code Set}.
*/
@SuppressWarnings("unused")
public static <T> Set<T> emptySet() {
return java.util.Collections.emptySet();
}
/**
* Returns a type-safe immutable empty {@code Map}.
*
* @param <K> map key type
* @param <V> map value type
* @return a type-safe immutable empty {@code Map}.
*/
@SuppressWarnings("unused")
public static <K, V> Map<K, V> emptyMap() {
return java.util.Collections.emptyMap();
}
/**
* Returns a type-safe immutable {@code List} containing the specified array elements.
*
* @param elements array elements to include in the list
* @param <T> list element type
* @return a type-safe immutable {@code List} containing the specified array elements.
*/
@SafeVarargs
public static <T> List<T> of(T... elements) {
if (elements == null || elements.length == 0) {
return java.util.Collections.emptyList();
}
return java.util.Collections.unmodifiableList(Arrays.asList(elements));
}
/**
* Returns a type-safe immutable {@code Set} containing the specified array elements.
*
* @param elements array elements to include in the set
* @param <T> set element type
* @return a type-safe immutable {@code Set} containing the specified array elements.
*/
@SafeVarargs
public static <T> Set<T> setOf(T... elements) {
if (elements == null || elements.length == 0) {
return java.util.Collections.emptySet();
}
Set<T> set = new LinkedHashSet<>(Arrays.asList(elements));
return immutable(set);
}
/**
* Shorter null-safe convenience alias for {@link java.util.Collections#unmodifiableList(List)} so both classes
* don't need to be imported.
*
* @param m map to wrap in an immutable/unmodifiable collection
* @param <K> map key type
* @param <V> map value type
* @return an immutable wrapper for {@code m}.
* @since JJWT_RELEASE_VERSION
*/
public static <K, V> Map<K, V> immutable(Map<K, V> m) {
return m != null ? java.util.Collections.unmodifiableMap(m) : null;
}
/**
* Shorter null-safe convenience alias for {@link java.util.Collections#unmodifiableSet(Set)} so both classes don't
* need to be imported.
*
* @param set set to wrap in an immutable Set
* @param <T> set element type
* @return an immutable wrapper for {@code set}
*/
public static <T> Set<T> immutable(Set<T> set) {
return set != null ? java.util.Collections.unmodifiableSet(set) : null;
}
/**
* Shorter null-safe convenience alias for {@link java.util.Collections#unmodifiableList(List)} so both classes
* don't need to be imported.
*
* @param list list to wrap in an immutable List
* @param <T> list element type
* @return an immutable wrapper for {@code list}
*/
public static <T> List<T> immutable(List<T> list) {
return list != null ? java.util.Collections.unmodifiableList(list) : null;
}
/**
* Null-safe factory method that returns an immutable/unmodifiable view of the specified collection instance.
* Works for {@link List}, {@link Set} and {@link Collection} arguments.
*
* @param c collection to wrap in an immutable/unmodifiable collection
* @param <C> type of collection
* @param <T> type of elements in the collection
* @return an immutable wrapper for {@code l}.
* @since JJWT_RELEASE_VERSION
*/
@SuppressWarnings("unchecked")
public static <T, C extends Collection<T>> C immutable(C c) {
if (c == null) {
return null;
} else if (c instanceof Set) {
return (C) java.util.Collections.unmodifiableSet((Set<T>) c);
} else if (c instanceof List) {
return (C) java.util.Collections.unmodifiableList((List<T>) c);
} else {
return (C) java.util.Collections.unmodifiableCollection(c);
}
}
/** /**
* Return <code>true</code> if the supplied Collection is <code>null</code> * Return <code>true</code> if the supplied Collection is <code>null</code>
* or empty. Otherwise, return <code>false</code>. * or empty. Otherwise, return <code>false</code>.
*
* @param collection the Collection to check * @param collection the Collection to check
* @return whether the given Collection is empty * @return whether the given Collection is empty
*/ */
public static boolean isEmpty(Collection collection) { public static boolean isEmpty(Collection<?> collection) {
return (collection == null || collection.isEmpty()); return size(collection) == 0;
} }
/** /**
@ -45,7 +178,7 @@ public final class Collections {
* @return the collection's size or {@code 0} if the collection is {@code null}. * @return the collection's size or {@code 0} if the collection is {@code null}.
* @since 0.9.2 * @since 0.9.2
*/ */
public static int size(Collection collection) { public static int size(Collection<?> collection) {
return collection == null ? 0 : collection.size(); return collection == null ? 0 : collection.size();
} }
@ -56,18 +189,19 @@ public final class Collections {
* @return the map's size or {@code 0} if the map is {@code null}. * @return the map's size or {@code 0} if the map is {@code null}.
* @since 0.9.2 * @since 0.9.2
*/ */
public static int size(Map map) { public static int size(Map<?, ?> map) {
return map == null ? 0 : map.size(); return map == null ? 0 : map.size();
} }
/** /**
* Return <code>true</code> if the supplied Map is <code>null</code> * Return <code>true</code> if the supplied Map is <code>null</code>
* or empty. Otherwise, return <code>false</code>. * or empty. Otherwise, return <code>false</code>.
*
* @param map the Map to check * @param map the Map to check
* @return whether the given Map is empty * @return whether the given Map is empty
*/ */
public static boolean isEmpty(Map map) { public static boolean isEmpty(Map<?, ?> map) {
return (map == null || map.isEmpty()); return size(map) == 0;
} }
/** /**
@ -75,6 +209,7 @@ public final class Collections {
* converted into a List of the appropriate wrapper type. * converted into a List of the appropriate wrapper type.
* <p>A <code>null</code> source value will be converted to an * <p>A <code>null</code> source value will be converted to an
* empty List. * empty List.
*
* @param source the (potentially primitive) array * @param source the (potentially primitive) array
* @return the converted List result * @return the converted List result
* @see Objects#toObjectArray(Object) * @see Objects#toObjectArray(Object)
@ -83,9 +218,28 @@ public final class Collections {
return Arrays.asList(Objects.toObjectArray(source)); return Arrays.asList(Objects.toObjectArray(source));
} }
/**
* Concatenate the specified set with the specified array elements, resulting in a new {@link LinkedHashSet} with
* the array elements appended to the end of the existing Set.
*
* @param c the set to append to
* @param elements the array elements to append to the end of the set
* @param <T> set element type
* @return a new {@link LinkedHashSet} with the array elements appended to the end of the original set.
*/
@SafeVarargs
public static <T> Set<T> concat(Set<T> c, T... elements) {
int size = Math.max(1, Collections.size(c) + io.jsonwebtoken.lang.Arrays.length(elements));
Set<T> set = new LinkedHashSet<>(size);
set.addAll(c);
java.util.Collections.addAll(set, elements);
return immutable(set);
}
/** /**
* Merge the given array into the given Collection. * Merge the given array into the given Collection.
* @param array the array to merge (may be <code>null</code>) *
* @param array the array to merge (may be <code>null</code>)
* @param collection the target Collection to merge the array into * @param collection the target Collection to merge the array into
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@ -94,9 +248,7 @@ public final class Collections {
throw new IllegalArgumentException("Collection must not be null"); throw new IllegalArgumentException("Collection must not be null");
} }
Object[] arr = Objects.toObjectArray(array); Object[] arr = Objects.toObjectArray(array);
for (Object elem : arr) { java.util.Collections.addAll(collection, arr);
collection.add(elem);
}
} }
/** /**
@ -104,8 +256,9 @@ public final class Collections {
* copying all properties (key-value pairs) over. * copying all properties (key-value pairs) over.
* <p>Uses <code>Properties.propertyNames()</code> to even catch * <p>Uses <code>Properties.propertyNames()</code> to even catch
* default properties linked into the original Properties instance. * default properties linked into the original Properties instance.
*
* @param props the Properties instance to merge (may be <code>null</code>) * @param props the Properties instance to merge (may be <code>null</code>)
* @param map the target Map to merge the properties into * @param map the target Map to merge the properties into
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static void mergePropertiesIntoMap(Properties props, Map map) { public static void mergePropertiesIntoMap(Properties props, Map map) {
@ -113,7 +266,7 @@ public final class Collections {
throw new IllegalArgumentException("Map must not be null"); throw new IllegalArgumentException("Map must not be null");
} }
if (props != null) { if (props != null) {
for (Enumeration en = props.propertyNames(); en.hasMoreElements();) { for (Enumeration en = props.propertyNames(); en.hasMoreElements(); ) {
String key = (String) en.nextElement(); String key = (String) en.nextElement();
Object value = props.getProperty(key); Object value = props.getProperty(key);
if (value == null) { if (value == null) {
@ -128,8 +281,9 @@ public final class Collections {
/** /**
* Check whether the given Iterator contains the given element. * Check whether the given Iterator contains the given element.
*
* @param iterator the Iterator to check * @param iterator the Iterator to check
* @param element the element to look for * @param element the element to look for
* @return <code>true</code> if found, <code>false</code> else * @return <code>true</code> if found, <code>false</code> else
*/ */
public static boolean contains(Iterator iterator, Object element) { public static boolean contains(Iterator iterator, Object element) {
@ -146,8 +300,9 @@ public final class Collections {
/** /**
* Check whether the given Enumeration contains the given element. * Check whether the given Enumeration contains the given element.
*
* @param enumeration the Enumeration to check * @param enumeration the Enumeration to check
* @param element the element to look for * @param element the element to look for
* @return <code>true</code> if found, <code>false</code> else * @return <code>true</code> if found, <code>false</code> else
*/ */
public static boolean contains(Enumeration enumeration, Object element) { public static boolean contains(Enumeration enumeration, Object element) {
@ -166,8 +321,9 @@ public final class Collections {
* Check whether the given Collection contains the given element instance. * Check whether the given Collection contains the given element instance.
* <p>Enforces the given instance to be present, rather than returning * <p>Enforces the given instance to be present, rather than returning
* <code>true</code> for an equal element as well. * <code>true</code> for an equal element as well.
*
* @param collection the Collection to check * @param collection the Collection to check
* @param element the element to look for * @param element the element to look for
* @return <code>true</code> if found, <code>false</code> else * @return <code>true</code> if found, <code>false</code> else
*/ */
public static boolean containsInstance(Collection collection, Object element) { public static boolean containsInstance(Collection collection, Object element) {
@ -184,7 +340,8 @@ public final class Collections {
/** /**
* Return <code>true</code> if any element in '<code>candidates</code>' is * Return <code>true</code> if any element in '<code>candidates</code>' is
* contained in '<code>source</code>'; otherwise returns <code>false</code>. * contained in '<code>source</code>'; otherwise returns <code>false</code>.
* @param source the source Collection *
* @param source the source Collection
* @param candidates the candidates to search for * @param candidates the candidates to search for
* @return whether any of the candidates has been found * @return whether any of the candidates has been found
*/ */
@ -205,7 +362,8 @@ public final class Collections {
* '<code>source</code>'. If no element in '<code>candidates</code>' is present in * '<code>source</code>'. If no element in '<code>candidates</code>' is present in
* '<code>source</code>' returns <code>null</code>. Iteration order is * '<code>source</code>' returns <code>null</code>. Iteration order is
* {@link Collection} implementation specific. * {@link Collection} implementation specific.
* @param source the source Collection *
* @param source the source Collection
* @param candidates the candidates to search for * @param candidates the candidates to search for
* @return the first present object, or <code>null</code> if not found * @return the first present object, or <code>null</code> if not found
*/ */
@ -223,9 +381,10 @@ public final class Collections {
/** /**
* Find a single value of the given type in the given Collection. * Find a single value of the given type in the given Collection.
*
* @param collection the Collection to search * @param collection the Collection to search
* @param type the type to look for * @param type the type to look for
* @param <T> the generic type parameter for {@code type} * @param <T> the generic type parameter for {@code type}
* @return a value of the given type found if there is a clear match, * @return a value of the given type found if there is a clear match,
* or <code>null</code> if none or more than one such value found * or <code>null</code> if none or more than one such value found
*/ */
@ -251,8 +410,9 @@ public final class Collections {
* Find a single value of one of the given types in the given Collection: * Find a single value of one of the given types in the given Collection:
* searching the Collection for a value of the first type, then * searching the Collection for a value of the first type, then
* searching for a value of the second type, etc. * searching for a value of the second type, etc.
*
* @param collection the collection to search * @param collection the collection to search
* @param types the types to look for, in prioritized order * @param types the types to look for, in prioritized order
* @return a value of one of the given types found if there is a clear match, * @return a value of one of the given types found if there is a clear match,
* or <code>null</code> if none or more than one such value found * or <code>null</code> if none or more than one such value found
*/ */
@ -271,6 +431,7 @@ public final class Collections {
/** /**
* Determine whether the given Collection only contains a single unique object. * Determine whether the given Collection only contains a single unique object.
*
* @param collection the Collection to check * @param collection the Collection to check
* @return <code>true</code> if the collection contains a single reference or * @return <code>true</code> if the collection contains a single reference or
* multiple references to the same instance, <code>false</code> else * multiple references to the same instance, <code>false</code> else
@ -285,8 +446,7 @@ public final class Collections {
if (!hasCandidate) { if (!hasCandidate) {
hasCandidate = true; hasCandidate = true;
candidate = elem; candidate = elem;
} } else if (candidate != elem) {
else if (candidate != elem) {
return false; return false;
} }
} }
@ -295,6 +455,7 @@ public final class Collections {
/** /**
* Find the common element type of the given Collection, if any. * Find the common element type of the given Collection, if any.
*
* @param collection the Collection to check * @param collection the Collection to check
* @return the common element type, or <code>null</code> if no clear * @return the common element type, or <code>null</code> if no clear
* common type has been found (or the collection was empty) * common type has been found (or the collection was empty)
@ -308,8 +469,7 @@ public final class Collections {
if (val != null) { if (val != null) {
if (candidate == null) { if (candidate == null) {
candidate = val.getClass(); candidate = val.getClass();
} } else if (candidate != val.getClass()) {
else if (candidate != val.getClass()) {
return null; return null;
} }
} }
@ -321,14 +481,15 @@ public final class Collections {
* Marshal the elements from the given enumeration into an array of the given type. * Marshal the elements from the given enumeration into an array of the given type.
* Enumeration elements must be assignable to the type of the given array. The array * Enumeration elements must be assignable to the type of the given array. The array
* returned will be a different instance than the array given. * returned will be a different instance than the array given.
*
* @param enumeration the collection to convert to an array * @param enumeration the collection to convert to an array
* @param array an array instance that matches the type of array to return * @param array an array instance that matches the type of array to return
* @param <A> the element type of the array that will be created * @param <A> the element type of the array that will be created
* @param <E> the element type contained within the enumeration. * @param <E> the element type contained within the enumeration.
* @return a new array of type {@code A} that contains the elements in the specified {@code enumeration}. * @return a new array of type {@code A} that contains the elements in the specified {@code enumeration}.
*/ */
public static <A,E extends A> A[] toArray(Enumeration<E> enumeration, A[] array) { public static <A, E extends A> A[] toArray(Enumeration<E> enumeration, A[] array) {
ArrayList<A> elements = new ArrayList<A>(); ArrayList<A> elements = new ArrayList<>();
while (enumeration.hasMoreElements()) { while (enumeration.hasMoreElements()) {
elements.add(enumeration.nextElement()); elements.add(enumeration.nextElement());
} }
@ -337,12 +498,13 @@ public final class Collections {
/** /**
* Adapt an enumeration to an iterator. * Adapt an enumeration to an iterator.
*
* @param enumeration the enumeration * @param enumeration the enumeration
* @param <E> the type of elements in the enumeration * @param <E> the type of elements in the enumeration
* @return the iterator * @return the iterator
*/ */
public static <E> Iterator<E> toIterator(Enumeration<E> enumeration) { public static <E> Iterator<E> toIterator(Enumeration<E> enumeration) {
return new EnumerationIterator<E>(enumeration); return new EnumerationIterator<>(enumeration);
} }
/** /**
@ -350,7 +512,7 @@ public final class Collections {
*/ */
private static class EnumerationIterator<E> implements Iterator<E> { private static class EnumerationIterator<E> implements Iterator<E> {
private Enumeration<E> enumeration; private final Enumeration<E> enumeration;
public EnumerationIterator(Enumeration<E> enumeration) { public EnumerationIterator(Enumeration<E> enumeration) {
this.enumeration = enumeration; this.enumeration = enumeration;

View File

@ -22,9 +22,14 @@ import java.util.Date;
import java.util.TimeZone; import java.util.TimeZone;
/** /**
* Utility methods to format and parse date strings.
*
* @since 0.10.0 * @since 0.10.0
*/ */
public class DateFormats { public final class DateFormats {
private DateFormats() {
} // prevent instantiation
private static final String ISO_8601_PATTERN = "yyyy-MM-dd'T'HH:mm:ss'Z'"; private static final String ISO_8601_PATTERN = "yyyy-MM-dd'T'HH:mm:ss'Z'";
@ -48,10 +53,25 @@ public class DateFormats {
} }
}; };
/**
* Return an ISO-8601-formatted string with millisecond precision representing the
* specified {@code date}.
*
* @param date the date for which to create an ISO-8601-formatted string
* @return the date represented as an ISO-8601-formatted string with millisecond precision.
*/
public static String formatIso8601(Date date) { public static String formatIso8601(Date date) {
return formatIso8601(date, true); return formatIso8601(date, true);
} }
/**
* Returns an ISO-8601-formatted string with optional millisecond precision for the specified
* {@code date}.
*
* @param date the date for which to create an ISO-8601-formatted string
* @param includeMillis whether to include millisecond notation within the string.
* @return the date represented as an ISO-8601-formatted string with optional millisecond precision.
*/
public static String formatIso8601(Date date, boolean includeMillis) { public static String formatIso8601(Date date, boolean includeMillis) {
if (includeMillis) { if (includeMillis) {
return ISO_8601_MILLIS.get().format(date); return ISO_8601_MILLIS.get().format(date);
@ -59,6 +79,14 @@ public class DateFormats {
return ISO_8601.get().format(date); return ISO_8601.get().format(date);
} }
/**
* Parse the specified ISO-8601-formatted date string and return the corresponding {@link Date} instance. The
* date string may optionally contain millisecond notation, and those milliseconds will be represented accordingly.
*
* @param s the ISO-8601-formatted string to parse
* @return the string's corresponding {@link Date} instance.
* @throws ParseException if the specified date string is not a validly-formatted ISO-8601 string.
*/
public static Date parseIso8601Date(String s) throws ParseException { public static Date parseIso8601Date(String s) throws ParseException {
Assert.notNull(s, "String argument cannot be null."); Assert.notNull(s, "String argument cannot be null.");
if (s.lastIndexOf('.') > -1) { //assume ISO-8601 with milliseconds if (s.lastIndexOf('.') > -1) { //assume ISO-8601 with milliseconds

View File

@ -16,11 +16,19 @@
package io.jsonwebtoken.lang; package io.jsonwebtoken.lang;
/** /**
* {@link RuntimeException} equivalent of {@link java.lang.InstantiationException}.
*
* @since 0.1 * @since 0.1
*/ */
public class InstantiationException extends RuntimeException { public class InstantiationException extends RuntimeException {
public InstantiationException(String s, Throwable t) { /**
super(s, t); * Creates a new instance with the specified explanation message and underlying cause.
*
* @param message the message explaining why the exception is thrown.
* @param cause the underlying cause that resulted in this exception being thrown.
*/
public InstantiationException(String message, Throwable cause) {
super(message, cause);
} }
} }

View File

@ -0,0 +1,65 @@
/*
* Copyright (C) 2021 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.lang;
import java.util.Map;
/**
* Mutation (modifications) to a {@link Map} instance while also supporting method chaining. The Map interface's
* {@link Map#put(Object, Object)}, {@link Map#remove(Object)}, {@link Map#putAll(Map)}, and {@link Map#clear()}
* mutation methods do not support method chaining, so this interface enables that behavior.
*
* @param <K> map key type
* @param <V> map value type
* @param <T> the mutator subtype, for method chaining
* @since JJWT_RELEASE_VERSION
*/
public interface MapMutator<K, V, T extends MapMutator<K, V, T>> {
/**
* Sets the specified name/value pair in the map. A {@code null} or empty value will remove the property
* from the map entirely.
*
* @param key the map key
* @param value the value to set for the specified header parameter name
* @return the mutator/builder for method chaining.
*/
T put(K key, V value);
/**
* Removes the map entry with the specified key
*
* @param key the key for the map entry to remove.
* @return the mutator/builder for method chaining.
*/
T remove(K key);
/**
* Sets the specified name/value pairs in the map. If any name has a {@code null} or empty value, that
* map entry will be removed from the map entirely.
*
* @param m the map to add
* @return the mutator/builder for method chaining.
*/
T putAll(Map<? extends K, ? extends V> m);
/**
* Removes all entries from the map. The map will be empty after this call returns.
*
* @return the mutator/builder for method chaining.
*/
T clear();
}

View File

@ -21,11 +21,13 @@ import java.util.Map;
/** /**
* Utility class to help with the manipulation of working with Maps. * Utility class to help with the manipulation of working with Maps.
*
* @since 0.11.0 * @since 0.11.0
*/ */
public final class Maps { public final class Maps {
private Maps() {} //prevent instantiation private Maps() {
} //prevent instantiation
/** /**
* Creates a new map builder with a single entry. * Creates a new map builder with a single entry.
@ -35,7 +37,8 @@ public final class Maps {
* // ... * // ...
* .build(); * .build();
* }</pre> * }</pre>
* @param key the key of an map entry to be added *
* @param key the key of an map entry to be added
* @param value the value of map entry to be added * @param value the value of map entry to be added
* @param <K> the maps key type * @param <K> the maps key type
* @param <V> the maps value type * @param <V> the maps value type
@ -53,21 +56,24 @@ public final class Maps {
* // ... * // ...
* .build(); * .build();
* }</pre> * }</pre>
*
* @param <K> the maps key type * @param <K> the maps key type
* @param <V> the maps value type * @param <V> the maps value type
*/ */
public interface MapBuilder<K, V> { public interface MapBuilder<K, V> extends Builder<Map<K, V>> {
/** /**
* Add a new entry to this map builder * Add a new entry to this map builder
* @param key the key of an map entry to be added *
* @param key the key of an map entry to be added
* @param value the value of map entry to be added * @param value the value of map entry to be added
* @return the current MapBuilder to allow for method chaining. * @return the current MapBuilder to allow for method chaining.
*/ */
MapBuilder<K, V> and(K key, V value); MapBuilder<K, V> and(K key, V value);
/** /**
* Returns a the resulting Map object from this MapBuilder. * Returns the resulting Map object from this MapBuilder.
* @return Returns a the resulting Map object from this MapBuilder. *
* @return the resulting Map object from this MapBuilder.
*/ */
Map<K, V> build(); Map<K, V> build();
} }
@ -80,6 +86,7 @@ public final class Maps {
data.put(key, value); data.put(key, value);
return this; return this;
} }
public Map<K, V> build() { public Map<K, V> build() {
return Collections.unmodifiableMap(data); return Collections.unmodifiableMap(data);
} }

View File

@ -20,18 +20,23 @@ import java.io.IOException;
import java.lang.reflect.Array; import java.lang.reflect.Array;
import java.util.Arrays; import java.util.Arrays;
/**
* Utility methods for working with object instances to reduce pattern repetition and otherwise
* increased cyclomatic complexity.
*/
public final class Objects { public final class Objects {
private Objects(){} //prevent instantiation private Objects() {
} //prevent instantiation
private static final int INITIAL_HASH = 7; private static final int INITIAL_HASH = 7;
private static final int MULTIPLIER = 31; private static final int MULTIPLIER = 31;
private static final String EMPTY_STRING = ""; private static final String EMPTY_STRING = "";
private static final String NULL_STRING = "null"; private static final String NULL_STRING = "null";
private static final String ARRAY_START = "{"; private static final String ARRAY_START = "{";
private static final String ARRAY_END = "}"; private static final String ARRAY_END = "}";
private static final String EMPTY_ARRAY = ARRAY_START + ARRAY_END; private static final String EMPTY_ARRAY = ARRAY_START + ARRAY_END;
private static final String ARRAY_ELEMENT_SEPARATOR = ", "; private static final String ARRAY_ELEMENT_SEPARATOR = ", ";
/** /**
@ -102,6 +107,16 @@ public final class Objects {
return array == null || array.length == 0; return array == null || array.length == 0;
} }
/**
* Returns {@code true} if the specified character array is null or of zero length, {@code false} otherwise.
*
* @param chars the character array to check
* @return {@code true} if the specified character array is null or of zero length, {@code false} otherwise.
*/
public static boolean isEmpty(char[] chars) {
return chars == null || chars.length == 0;
}
/** /**
* Check whether the given array contains the given element. * Check whether the given array contains the given element.
* *
@ -145,8 +160,8 @@ public final class Objects {
public static boolean containsConstant(Enum<?>[] enumValues, String constant, boolean caseSensitive) { public static boolean containsConstant(Enum<?>[] enumValues, String constant, boolean caseSensitive) {
for (Enum<?> candidate : enumValues) { for (Enum<?> candidate : enumValues) {
if (caseSensitive ? if (caseSensitive ?
candidate.toString().equals(constant) : candidate.toString().equals(constant) :
candidate.toString().equalsIgnoreCase(constant)) { candidate.toString().equalsIgnoreCase(constant)) {
return true; return true;
} }
} }
@ -171,8 +186,8 @@ public final class Objects {
} }
} }
throw new IllegalArgumentException( throw new IllegalArgumentException(
String.format("constant [%s] does not exist in enum type %s", String.format("constant [%s] does not exist in enum type %s",
constant, enumValues.getClass().getComponentType().getName())); constant, enumValues.getClass().getComponentType().getName()));
} }
/** /**
@ -180,9 +195,9 @@ public final class Objects {
* consisting of the input array contents plus the given object. * consisting of the input array contents plus the given object.
* *
* @param array the array to append to (can be <code>null</code>) * @param array the array to append to (can be <code>null</code>)
* @param <A> the type of each element in the specified {@code array} * @param <A> the type of each element in the specified {@code array}
* @param obj the object to append * @param obj the object to append
* @param <O> the type of the specified object, which must equal to or extend the {@code &lt;A&gt;} type. * @param <O> the type of the specified object, which must be equal to or extend the <code>&lt;A&gt;</code> type.
* @return the new array (of the same component type; never <code>null</code>) * @return the new array (of the same component type; never <code>null</code>)
*/ */
public static <A, O extends A> A[] addObjectToArray(A[] array, O obj) { public static <A, O extends A> A[] addObjectToArray(A[] array, O obj) {
@ -300,6 +315,8 @@ public final class Objects {
* methods for arrays in this class. If the object is <code>null</code>, * methods for arrays in this class. If the object is <code>null</code>,
* this method returns 0. * this method returns 0.
* *
* @param obj the object to use for obtaining a hashcode
* @return the object's hashcode, which could be 0 if the object is null.
* @see #nullSafeHashCode(Object[]) * @see #nullSafeHashCode(Object[])
* @see #nullSafeHashCode(boolean[]) * @see #nullSafeHashCode(boolean[])
* @see #nullSafeHashCode(byte[]) * @see #nullSafeHashCode(byte[])
@ -309,8 +326,6 @@ public final class Objects {
* @see #nullSafeHashCode(int[]) * @see #nullSafeHashCode(int[])
* @see #nullSafeHashCode(long[]) * @see #nullSafeHashCode(long[])
* @see #nullSafeHashCode(short[]) * @see #nullSafeHashCode(short[])
* @param obj the object to use for obtaining a hashcode
* @return the object's hashcode, which could be 0 if the object is null.
*/ */
public static int nullSafeHashCode(Object obj) { public static int nullSafeHashCode(Object obj) {
if (obj == null) { if (obj == null) {
@ -351,10 +366,11 @@ public final class Objects {
/** /**
* Return a hash code based on the contents of the specified array. * Return a hash code based on the contents of the specified array.
* If <code>array</code> is <code>null</code>, this method returns 0. * If <code>array</code> is <code>null</code>, this method returns 0.
*
* @param array the array to obtain a hashcode * @param array the array to obtain a hashcode
* @return the array's hashcode, which could be 0 if the array is null. * @return the array's hashcode, which could be 0 if the array is null.
*/ */
public static int nullSafeHashCode(Object[] array) { public static int nullSafeHashCode(Object... array) {
if (array == null) { if (array == null) {
return 0; return 0;
} }
@ -369,6 +385,7 @@ public final class Objects {
/** /**
* Return a hash code based on the contents of the specified array. * Return a hash code based on the contents of the specified array.
* If <code>array</code> is <code>null</code>, this method returns 0. * If <code>array</code> is <code>null</code>, this method returns 0.
*
* @param array the boolean array to obtain a hashcode * @param array the boolean array to obtain a hashcode
* @return the boolean array's hashcode, which could be 0 if the array is null. * @return the boolean array's hashcode, which could be 0 if the array is null.
*/ */
@ -387,6 +404,7 @@ public final class Objects {
/** /**
* Return a hash code based on the contents of the specified array. * Return a hash code based on the contents of the specified array.
* If <code>array</code> is <code>null</code>, this method returns 0. * If <code>array</code> is <code>null</code>, this method returns 0.
*
* @param array the byte array to obtain a hashcode * @param array the byte array to obtain a hashcode
* @return the byte array's hashcode, which could be 0 if the array is null. * @return the byte array's hashcode, which could be 0 if the array is null.
*/ */
@ -405,6 +423,7 @@ public final class Objects {
/** /**
* Return a hash code based on the contents of the specified array. * Return a hash code based on the contents of the specified array.
* If <code>array</code> is <code>null</code>, this method returns 0. * If <code>array</code> is <code>null</code>, this method returns 0.
*
* @param array the char array to obtain a hashcode * @param array the char array to obtain a hashcode
* @return the char array's hashcode, which could be 0 if the array is null. * @return the char array's hashcode, which could be 0 if the array is null.
*/ */
@ -423,6 +442,7 @@ public final class Objects {
/** /**
* Return a hash code based on the contents of the specified array. * Return a hash code based on the contents of the specified array.
* If <code>array</code> is <code>null</code>, this method returns 0. * If <code>array</code> is <code>null</code>, this method returns 0.
*
* @param array the double array to obtain a hashcode * @param array the double array to obtain a hashcode
* @return the double array's hashcode, which could be 0 if the array is null. * @return the double array's hashcode, which could be 0 if the array is null.
*/ */
@ -441,6 +461,7 @@ public final class Objects {
/** /**
* Return a hash code based on the contents of the specified array. * Return a hash code based on the contents of the specified array.
* If <code>array</code> is <code>null</code>, this method returns 0. * If <code>array</code> is <code>null</code>, this method returns 0.
*
* @param array the float array to obtain a hashcode * @param array the float array to obtain a hashcode
* @return the float array's hashcode, which could be 0 if the array is null. * @return the float array's hashcode, which could be 0 if the array is null.
*/ */
@ -459,6 +480,7 @@ public final class Objects {
/** /**
* Return a hash code based on the contents of the specified array. * Return a hash code based on the contents of the specified array.
* If <code>array</code> is <code>null</code>, this method returns 0. * If <code>array</code> is <code>null</code>, this method returns 0.
*
* @param array the int array to obtain a hashcode * @param array the int array to obtain a hashcode
* @return the int array's hashcode, which could be 0 if the array is null. * @return the int array's hashcode, which could be 0 if the array is null.
*/ */
@ -477,6 +499,7 @@ public final class Objects {
/** /**
* Return a hash code based on the contents of the specified array. * Return a hash code based on the contents of the specified array.
* If <code>array</code> is <code>null</code>, this method returns 0. * If <code>array</code> is <code>null</code>, this method returns 0.
*
* @param array the long array to obtain a hashcode * @param array the long array to obtain a hashcode
* @return the long array's hashcode, which could be 0 if the array is null. * @return the long array's hashcode, which could be 0 if the array is null.
*/ */
@ -495,6 +518,7 @@ public final class Objects {
/** /**
* Return a hash code based on the contents of the specified array. * Return a hash code based on the contents of the specified array.
* If <code>array</code> is <code>null</code>, this method returns 0. * If <code>array</code> is <code>null</code>, this method returns 0.
*
* @param array the short array to obtain a hashcode * @param array the short array to obtain a hashcode
* @return the short array's hashcode, which could be 0 if the array is null. * @return the short array's hashcode, which could be 0 if the array is null.
*/ */
@ -939,6 +963,12 @@ public final class Objects {
return sb.toString(); return sb.toString();
} }
/**
* Iterate over the specified {@link Closeable} instances, invoking
* {@link Closeable#close()} on each one, ignoring any potential {@link IOException}s.
*
* @param closeables the closeables to close.
*/
public static void nullSafeClose(Closeable... closeables) { public static void nullSafeClose(Closeable... closeables) {
if (closeables == null) { if (closeables == null) {
return; return;

View File

@ -0,0 +1,55 @@
/*
* 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.lang;
import java.util.Collection;
/**
* An immutable read-only repository of key-value pairs.
*
* @param <K> key type
* @param <V> value type
* @since JJWT_RELEASE_VERSION
*/
public interface Registry<K, V> {
/**
* Returns all registry values as a read-only collection.
*
* @return all registry values as a read-only collection.
*/
Collection<V> values();
/**
* Returns the value assigned the specified key or throws an {@code IllegalArgumentException} if there is no
* associated value. If a value is not required, consider using the {@link #find(Object)} method instead.
*
* @param key the registry key assigned to the required value
* @return the value assigned the specified key
* @throws IllegalArgumentException if there is no value assigned the specified key
* @see #find(Object)
*/
V get(K key) throws IllegalArgumentException;
/**
* Returns the value assigned the specified key or {@code null} if there is no associated value.
*
* @param key the registry key assigned to the required value
* @return the value assigned the specified key or {@code null} if there is no associated value.
* @see #get(Object)
*/
V find(K key);
}

View File

@ -19,16 +19,36 @@ import java.security.Provider;
import java.security.Security; import java.security.Security;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
/**
* No longer used by JJWT. Will be removed before the 1.0 final release.
*
* @deprecated since JJWT_RELEASE_VERSION. will be removed before the 1.0 final release.
*/
@Deprecated
public final class RuntimeEnvironment { public final class RuntimeEnvironment {
private RuntimeEnvironment(){} //prevent instantiation private RuntimeEnvironment() {
} //prevent instantiation
private static final String BC_PROVIDER_CLASS_NAME = "org.bouncycastle.jce.provider.BouncyCastleProvider"; private static final String BC_PROVIDER_CLASS_NAME = "org.bouncycastle.jce.provider.BouncyCastleProvider";
private static final AtomicBoolean bcLoaded = new AtomicBoolean(false); private static final AtomicBoolean bcLoaded = new AtomicBoolean(false);
/**
* {@code true} if BouncyCastle is in the runtime classpath, {@code false} otherwise.
*
* @deprecated since JJWT_RELEASE_VERSION. will be removed before the 1.0 final release.
*/
@Deprecated
public static final boolean BOUNCY_CASTLE_AVAILABLE = Classes.isAvailable(BC_PROVIDER_CLASS_NAME); public static final boolean BOUNCY_CASTLE_AVAILABLE = Classes.isAvailable(BC_PROVIDER_CLASS_NAME);
/**
* Register BouncyCastle as a JCA provider in the system's {@link Security#getProviders() Security Providers} list
* if BouncyCastle is in the runtime classpath.
*
* @deprecated since JJWT_RELEASE_VERSION. will be removed before the 1.0 final release.
*/
@Deprecated
public static void enableBouncyCastleIfPossible() { public static void enableBouncyCastleIfPossible() {
if (!BOUNCY_CASTLE_AVAILABLE || bcLoaded.get()) { if (!BOUNCY_CASTLE_AVAILABLE || bcLoaded.get()) {
@ -36,13 +56,13 @@ public final class RuntimeEnvironment {
} }
try { try {
Class clazz = Classes.forName(BC_PROVIDER_CLASS_NAME); Class<Provider> clazz = Classes.forName(BC_PROVIDER_CLASS_NAME);
//check to see if the user has already registered the BC provider: //check to see if the user has already registered the BC provider:
Provider[] providers = Security.getProviders(); Provider[] providers = Security.getProviders();
for(Provider provider : providers) { for (Provider provider : providers) {
if (clazz.isInstance(provider)) { if (clazz.isInstance(provider)) {
bcLoaded.set(true); bcLoaded.set(true);
return; return;
@ -50,7 +70,8 @@ public final class RuntimeEnvironment {
} }
//bc provider not enabled - add it: //bc provider not enabled - add it:
Security.addProvider((Provider)Classes.newInstance(clazz)); Provider provider = Classes.newInstance(clazz);
Security.addProvider(provider);
bcLoaded.set(true); bcLoaded.set(true);
} catch (UnknownClassException e) { } catch (UnknownClassException e) {

View File

@ -16,6 +16,7 @@
package io.jsonwebtoken.lang; package io.jsonwebtoken.lang;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
@ -29,8 +30,17 @@ import java.util.Set;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import java.util.TreeSet; import java.util.TreeSet;
/**
* Utility methods for working with Strings to reduce pattern repetition and otherwise
* increased cyclomatic complexity.
*/
public final class Strings { public final class Strings {
/**
* Empty String, equal to <code>&quot;&quot;</code>.
*/
public static final String EMPTY = "";
private static final String FOLDER_SEPARATOR = "/"; private static final String FOLDER_SEPARATOR = "/";
private static final String WINDOWS_FOLDER_SEPARATOR = "\\"; private static final String WINDOWS_FOLDER_SEPARATOR = "\\";
@ -41,9 +51,13 @@ public final class Strings {
private static final char EXTENSION_SEPARATOR = '.'; private static final char EXTENSION_SEPARATOR = '.';
public static final Charset UTF_8 = Charset.forName("UTF-8"); /**
* Convenience alias for {@link StandardCharsets#UTF_8}.
*/
public static final Charset UTF_8 = StandardCharsets.UTF_8;
private Strings(){} //prevent instantiation private Strings() {
} //prevent instantiation
//--------------------------------------------------------------------- //---------------------------------------------------------------------
// General convenience methods for working with Strings // General convenience methods for working with Strings
@ -58,6 +72,7 @@ public final class Strings {
* Strings.hasLength(" ") = true * Strings.hasLength(" ") = true
* Strings.hasLength("Hello") = true * Strings.hasLength("Hello") = true
* </pre> * </pre>
*
* @param str the CharSequence to check (may be <code>null</code>) * @param str the CharSequence to check (may be <code>null</code>)
* @return <code>true</code> if the CharSequence is not null and has length * @return <code>true</code> if the CharSequence is not null and has length
* @see #hasText(String) * @see #hasText(String)
@ -69,6 +84,7 @@ public final class Strings {
/** /**
* Check that the given String is neither <code>null</code> nor of length 0. * Check that the given String is neither <code>null</code> nor of length 0.
* Note: Will return <code>true</code> for a String that purely consists of whitespace. * Note: Will return <code>true</code> for a String that purely consists of whitespace.
*
* @param str the String to check (may be <code>null</code>) * @param str the String to check (may be <code>null</code>)
* @return <code>true</code> if the String is not null and has length * @return <code>true</code> if the String is not null and has length
* @see #hasLength(CharSequence) * @see #hasLength(CharSequence)
@ -88,6 +104,7 @@ public final class Strings {
* Strings.hasText("12345") = true * Strings.hasText("12345") = true
* Strings.hasText(" 12345 ") = true * Strings.hasText(" 12345 ") = true
* </pre> * </pre>
*
* @param str the CharSequence to check (may be <code>null</code>) * @param str the CharSequence to check (may be <code>null</code>)
* @return <code>true</code> if the CharSequence is not <code>null</code>, * @return <code>true</code> if the CharSequence is not <code>null</code>,
* its length is greater than 0, and it does not contain whitespace only * its length is greater than 0, and it does not contain whitespace only
@ -110,6 +127,7 @@ public final class Strings {
* Check whether the given String has actual text. * Check whether the given String has actual text.
* More specifically, returns <code>true</code> if the string not <code>null</code>, * More specifically, returns <code>true</code> if the string not <code>null</code>,
* its length is greater than 0, and it contains at least one non-whitespace character. * its length is greater than 0, and it contains at least one non-whitespace character.
*
* @param str the String to check (may be <code>null</code>) * @param str the String to check (may be <code>null</code>)
* @return <code>true</code> if the String is not <code>null</code>, its length is * @return <code>true</code> if the String is not <code>null</code>, its length is
* greater than 0, and it does not contain whitespace only * greater than 0, and it does not contain whitespace only
@ -121,6 +139,7 @@ public final class Strings {
/** /**
* Check whether the given CharSequence contains any whitespace characters. * Check whether the given CharSequence contains any whitespace characters.
*
* @param str the CharSequence to check (may be <code>null</code>) * @param str the CharSequence to check (may be <code>null</code>)
* @return <code>true</code> if the CharSequence is not empty and * @return <code>true</code> if the CharSequence is not empty and
* contains at least 1 whitespace character * contains at least 1 whitespace character
@ -141,6 +160,7 @@ public final class Strings {
/** /**
* Check whether the given String contains any whitespace characters. * Check whether the given String contains any whitespace characters.
*
* @param str the String to check (may be <code>null</code>) * @param str the String to check (may be <code>null</code>)
* @return <code>true</code> if the String is not empty and * @return <code>true</code> if the String is not empty and
* contains at least 1 whitespace character * contains at least 1 whitespace character
@ -152,15 +172,16 @@ public final class Strings {
/** /**
* Trim leading and trailing whitespace from the given String. * Trim leading and trailing whitespace from the given String.
*
* @param str the String to check * @param str the String to check
* @return the trimmed String * @return the trimmed String
* @see java.lang.Character#isWhitespace * @see java.lang.Character#isWhitespace
*/ */
public static String trimWhitespace(String str) { public static String trimWhitespace(String str) {
return (String) trimWhitespace((CharSequence)str); return (String) trimWhitespace((CharSequence) str);
} }
private static CharSequence trimWhitespace(CharSequence str) { private static CharSequence trimWhitespace(CharSequence str) {
if (!hasLength(str)) { if (!hasLength(str)) {
return str; return str;
@ -168,24 +189,40 @@ public final class Strings {
final int length = str.length(); final int length = str.length();
int start = 0; int start = 0;
while (start < length && Character.isWhitespace(str.charAt(start))) { while (start < length && Character.isWhitespace(str.charAt(start))) {
start++; start++;
} }
int end = length; int end = length;
while (start < length && Character.isWhitespace(str.charAt(end - 1))) { while (start < length && Character.isWhitespace(str.charAt(end - 1))) {
end--; end--;
} }
return ((start > 0) || (end < length)) ? str.subSequence(start, end) : str; return ((start > 0) || (end < length)) ? str.subSequence(start, end) : str;
} }
/**
* Returns the specified string without leading or trailing whitespace, or {@code null} if there are no remaining
* characters.
*
* @param str the string to clean
* @return the specified string without leading or trailing whitespace, or {@code null} if there are no remaining
* characters.
*/
public static String clean(String str) { public static String clean(String str) {
CharSequence result = clean((CharSequence) str); CharSequence result = clean((CharSequence) str);
return result!=null?result.toString():null; return result != null ? result.toString() : null;
} }
/**
* Returns the specified {@code CharSequence} without leading or trailing whitespace, or {@code null} if there are
* no remaining characters.
*
* @param str the {@code CharSequence} to clean
* @return the specified string without leading or trailing whitespace, or {@code null} if there are no remaining
* characters.
*/
public static CharSequence clean(CharSequence str) { public static CharSequence clean(CharSequence str) {
str = trimWhitespace(str); str = trimWhitespace(str);
if (!hasLength(str)) { if (!hasLength(str)) {
@ -194,9 +231,56 @@ public final class Strings {
return str; return str;
} }
/**
* Returns a String representation (1s and 0s) of the specified byte.
*
* @param b the byte to represent as 1s and 0s.
* @return a String representation (1s and 0s) of the specified byte.
*/
public static String toBinary(byte b) {
String bString = Integer.toBinaryString(b & 0xFF);
return String.format("%8s", bString).replace((char) Character.SPACE_SEPARATOR, '0');
}
/**
* Returns a String representation (1s and 0s) of the specified byte array.
*
* @param bytes the bytes to represent as 1s and 0s.
* @return a String representation (1s and 0s) of the specified byte array.
*/
public static String toBinary(byte[] bytes) {
StringBuilder sb = new StringBuilder(19); //16 characters + 3 space characters
for (byte b : bytes) {
if (sb.length() > 0) {
sb.append((char) Character.SPACE_SEPARATOR);
}
String val = toBinary(b);
sb.append(val);
}
return sb.toString();
}
/**
* Returns a hexadecimal String representation of the specified byte array.
*
* @param bytes the bytes to represent as a hexidecimal string.
* @return a hexadecimal String representation of the specified byte array.
*/
public static String toHex(byte[] bytes) {
StringBuilder result = new StringBuilder();
for (byte temp : bytes) {
if (result.length() > 0) {
result.append((char) Character.SPACE_SEPARATOR);
}
result.append(String.format("%02x", temp));
}
return result.toString();
}
/** /**
* Trim <i>all</i> whitespace from the given String: * Trim <i>all</i> whitespace from the given String:
* leading, trailing, and intermediate characters. * leading, trailing, and intermediate characters.
*
* @param str the String to check * @param str the String to check
* @return the trimmed String * @return the trimmed String
* @see java.lang.Character#isWhitespace * @see java.lang.Character#isWhitespace
@ -210,8 +294,7 @@ public final class Strings {
while (sb.length() > index) { while (sb.length() > index) {
if (Character.isWhitespace(sb.charAt(index))) { if (Character.isWhitespace(sb.charAt(index))) {
sb.deleteCharAt(index); sb.deleteCharAt(index);
} } else {
else {
index++; index++;
} }
} }
@ -220,6 +303,7 @@ public final class Strings {
/** /**
* Trim leading whitespace from the given String. * Trim leading whitespace from the given String.
*
* @param str the String to check * @param str the String to check
* @return the trimmed String * @return the trimmed String
* @see java.lang.Character#isWhitespace * @see java.lang.Character#isWhitespace
@ -237,6 +321,7 @@ public final class Strings {
/** /**
* Trim trailing whitespace from the given String. * Trim trailing whitespace from the given String.
*
* @param str the String to check * @param str the String to check
* @return the trimmed String * @return the trimmed String
* @see java.lang.Character#isWhitespace * @see java.lang.Character#isWhitespace
@ -254,7 +339,8 @@ public final class Strings {
/** /**
* Trim all occurrences of the supplied leading character from the given String. * Trim all occurrences of the supplied leading character from the given String.
* @param str the String to check *
* @param str the String to check
* @param leadingCharacter the leading character to be trimmed * @param leadingCharacter the leading character to be trimmed
* @return the trimmed String * @return the trimmed String
*/ */
@ -271,7 +357,8 @@ public final class Strings {
/** /**
* Trim all occurrences of the supplied trailing character from the given String. * Trim all occurrences of the supplied trailing character from the given String.
* @param str the String to check *
* @param str the String to check
* @param trailingCharacter the trailing character to be trimmed * @param trailingCharacter the trailing character to be trimmed
* @return the trimmed String * @return the trimmed String
*/ */
@ -289,7 +376,8 @@ public final class Strings {
/** /**
* Returns {@code true} if the given string starts with the specified case-insensitive prefix, {@code false} otherwise. * Returns {@code true} if the given string starts with the specified case-insensitive prefix, {@code false} otherwise.
* @param str the String to check *
* @param str the String to check
* @param prefix the prefix to look for * @param prefix the prefix to look for
* @return {@code true} if the given string starts with the specified case-insensitive prefix, {@code false} otherwise. * @return {@code true} if the given string starts with the specified case-insensitive prefix, {@code false} otherwise.
* @see java.lang.String#startsWith * @see java.lang.String#startsWith
@ -298,12 +386,12 @@ public final class Strings {
if (str == null || prefix == null) { if (str == null || prefix == null) {
return false; return false;
} }
if (str.startsWith(prefix)) {
return true;
}
if (str.length() < prefix.length()) { if (str.length() < prefix.length()) {
return false; return false;
} }
if (str.startsWith(prefix)) {
return true;
}
String lcStr = str.substring(0, prefix.length()).toLowerCase(); String lcStr = str.substring(0, prefix.length()).toLowerCase();
String lcPrefix = prefix.toLowerCase(); String lcPrefix = prefix.toLowerCase();
return lcStr.equals(lcPrefix); return lcStr.equals(lcPrefix);
@ -311,7 +399,8 @@ public final class Strings {
/** /**
* Returns {@code true} if the given string ends with the specified case-insensitive suffix, {@code false} otherwise. * Returns {@code true} if the given string ends with the specified case-insensitive suffix, {@code false} otherwise.
* @param str the String to check *
* @param str the String to check
* @param suffix the suffix to look for * @param suffix the suffix to look for
* @return {@code true} if the given string ends with the specified case-insensitive suffix, {@code false} otherwise. * @return {@code true} if the given string ends with the specified case-insensitive suffix, {@code false} otherwise.
* @see java.lang.String#endsWith * @see java.lang.String#endsWith
@ -334,8 +423,9 @@ public final class Strings {
/** /**
* Returns {@code true} if the given string matches the given substring at the given index, {@code false} otherwise. * Returns {@code true} if the given string matches the given substring at the given index, {@code false} otherwise.
* @param str the original string (or StringBuilder) *
* @param index the index in the original string to start matching against * @param str the original string (or StringBuilder)
* @param index the index in the original string to start matching against
* @param substring the substring to match at the given index * @param substring the substring to match at the given index
* @return {@code true} if the given string matches the given substring at the given index, {@code false} otherwise. * @return {@code true} if the given string matches the given substring at the given index, {@code false} otherwise.
*/ */
@ -351,6 +441,7 @@ public final class Strings {
/** /**
* Returns the number of occurrences the substring {@code sub} appears in string {@code str}. * Returns the number of occurrences the substring {@code sub} appears in string {@code str}.
*
* @param str string to search in. Return 0 if this is null. * @param str string to search in. Return 0 if this is null.
* @param sub string to search for. Return 0 if this is null. * @param sub string to search for. Return 0 if this is null.
* @return the number of occurrences the substring {@code sub} appears in string {@code str}. * @return the number of occurrences the substring {@code sub} appears in string {@code str}.
@ -372,7 +463,8 @@ public final class Strings {
/** /**
* Replace all occurrences of a substring within a string with * Replace all occurrences of a substring within a string with
* another string. * another string.
* @param inString String to examine *
* @param inString String to examine
* @param oldPattern String to replace * @param oldPattern String to replace
* @param newPattern String to insert * @param newPattern String to insert
* @return a String with the replacements * @return a String with the replacements
@ -399,8 +491,9 @@ public final class Strings {
/** /**
* Delete all occurrences of the given substring. * Delete all occurrences of the given substring.
*
* @param inString the original String * @param inString the original String
* @param pattern the pattern to delete all occurrences of * @param pattern the pattern to delete all occurrences of
* @return the resulting String * @return the resulting String
*/ */
public static String delete(String inString, String pattern) { public static String delete(String inString, String pattern) {
@ -409,9 +502,10 @@ public final class Strings {
/** /**
* Delete any character in a given String. * Delete any character in a given String.
* @param inString the original String *
* @param inString the original String
* @param charsToDelete a set of characters to delete. * @param charsToDelete a set of characters to delete.
* E.g. "az\n" will delete 'a's, 'z's and new lines. * E.g. "az\n" will delete 'a's, 'z's and new lines.
* @return the resulting String * @return the resulting String
*/ */
public static String deleteAny(String inString, String charsToDelete) { public static String deleteAny(String inString, String charsToDelete) {
@ -435,6 +529,7 @@ public final class Strings {
/** /**
* Quote the given String with single quotes. * Quote the given String with single quotes.
*
* @param str the input String (e.g. "myString") * @param str the input String (e.g. "myString")
* @return the quoted String (e.g. "'myString'"), * @return the quoted String (e.g. "'myString'"),
* or <code>null</code> if the input was <code>null</code> * or <code>null</code> if the input was <code>null</code>
@ -446,6 +541,7 @@ public final class Strings {
/** /**
* Turn the given Object into a String with single quotes * Turn the given Object into a String with single quotes
* if it is a String; keeping the Object as-is else. * if it is a String; keeping the Object as-is else.
*
* @param obj the input Object (e.g. "myString") * @param obj the input Object (e.g. "myString")
* @return the quoted String (e.g. "'myString'"), * @return the quoted String (e.g. "'myString'"),
* or the input object as-is if not a String * or the input object as-is if not a String
@ -457,6 +553,7 @@ public final class Strings {
/** /**
* Unqualify a string qualified by a '.' dot character. For example, * Unqualify a string qualified by a '.' dot character. For example,
* "this.name.is.qualified", returns "qualified". * "this.name.is.qualified", returns "qualified".
*
* @param qualifiedName the qualified name * @param qualifiedName the qualified name
* @return an unqualified string by stripping all previous text before (and including) the last period character. * @return an unqualified string by stripping all previous text before (and including) the last period character.
*/ */
@ -467,8 +564,9 @@ public final class Strings {
/** /**
* Unqualify a string qualified by a separator character. For example, * Unqualify a string qualified by a separator character. For example,
* "this:name:is:qualified" returns "qualified" if using a ':' separator. * "this:name:is:qualified" returns "qualified" if using a ':' separator.
*
* @param qualifiedName the qualified name * @param qualifiedName the qualified name
* @param separator the separator * @param separator the separator
* @return an unqualified string by stripping all previous text before and including the last {@code separator} character. * @return an unqualified string by stripping all previous text before and including the last {@code separator} character.
*/ */
public static String unqualify(String qualifiedName, char separator) { public static String unqualify(String qualifiedName, char separator) {
@ -479,6 +577,7 @@ public final class Strings {
* Capitalize a <code>String</code>, changing the first letter to * Capitalize a <code>String</code>, changing the first letter to
* upper case as per {@link Character#toUpperCase(char)}. * upper case as per {@link Character#toUpperCase(char)}.
* No other letters are changed. * No other letters are changed.
*
* @param str the String to capitalize, may be <code>null</code> * @param str the String to capitalize, may be <code>null</code>
* @return the capitalized String, <code>null</code> if null * @return the capitalized String, <code>null</code> if null
*/ */
@ -490,6 +589,7 @@ public final class Strings {
* Uncapitalize a <code>String</code>, changing the first letter to * Uncapitalize a <code>String</code>, changing the first letter to
* lower case as per {@link Character#toLowerCase(char)}. * lower case as per {@link Character#toLowerCase(char)}.
* No other letters are changed. * No other letters are changed.
*
* @param str the String to uncapitalize, may be <code>null</code> * @param str the String to uncapitalize, may be <code>null</code>
* @return the uncapitalized String, <code>null</code> if null * @return the uncapitalized String, <code>null</code> if null
*/ */
@ -504,8 +604,7 @@ public final class Strings {
StringBuilder sb = new StringBuilder(str.length()); StringBuilder sb = new StringBuilder(str.length());
if (capitalize) { if (capitalize) {
sb.append(Character.toUpperCase(str.charAt(0))); sb.append(Character.toUpperCase(str.charAt(0)));
} } else {
else {
sb.append(Character.toLowerCase(str.charAt(0))); sb.append(Character.toLowerCase(str.charAt(0)));
} }
sb.append(str.substring(1)); sb.append(str.substring(1));
@ -515,6 +614,7 @@ public final class Strings {
/** /**
* Extract the filename from the given path, * Extract the filename from the given path,
* e.g. "mypath/myfile.txt" -&gt; "myfile.txt". * e.g. "mypath/myfile.txt" -&gt; "myfile.txt".
*
* @param path the file path (may be <code>null</code>) * @param path the file path (may be <code>null</code>)
* @return the extracted filename, or <code>null</code> if none * @return the extracted filename, or <code>null</code> if none
*/ */
@ -529,6 +629,7 @@ public final class Strings {
/** /**
* Extract the filename extension from the given path, * Extract the filename extension from the given path,
* e.g. "mypath/myfile.txt" -&gt; "txt". * e.g. "mypath/myfile.txt" -&gt; "txt".
*
* @param path the file path (may be <code>null</code>) * @param path the file path (may be <code>null</code>)
* @return the extracted filename extension, or <code>null</code> if none * @return the extracted filename extension, or <code>null</code> if none
*/ */
@ -550,6 +651,7 @@ public final class Strings {
/** /**
* Strip the filename extension from the given path, * Strip the filename extension from the given path,
* e.g. "mypath/myfile.txt" -&gt; "mypath/myfile". * e.g. "mypath/myfile.txt" -&gt; "mypath/myfile".
*
* @param path the file path (may be <code>null</code>) * @param path the file path (may be <code>null</code>)
* @return the path with stripped filename extension, * @return the path with stripped filename extension,
* or <code>null</code> if none * or <code>null</code> if none
@ -572,9 +674,10 @@ public final class Strings {
/** /**
* Apply the given relative path to the given path, * Apply the given relative path to the given path,
* assuming standard Java folder separation (i.e. "/" separators). * assuming standard Java folder separation (i.e. "/" separators).
* @param path the path to start from (usually a full file path) *
* @param path the path to start from (usually a full file path)
* @param relativePath the relative path to apply * @param relativePath the relative path to apply
* (relative to the full file path above) * (relative to the full file path above)
* @return the full file path that results from applying the relative path * @return the full file path that results from applying the relative path
*/ */
public static String applyRelativePath(String path, String relativePath) { public static String applyRelativePath(String path, String relativePath) {
@ -585,8 +688,7 @@ public final class Strings {
newPath += FOLDER_SEPARATOR; newPath += FOLDER_SEPARATOR;
} }
return newPath + relativePath; return newPath + relativePath;
} } else {
else {
return relativePath; return relativePath;
} }
} }
@ -596,6 +698,7 @@ public final class Strings {
* inner simple dots. * inner simple dots.
* <p>The result is convenient for path comparison. For other uses, * <p>The result is convenient for path comparison. For other uses,
* notice that Windows separators ("\") are replaced by simple slashes. * notice that Windows separators ("\") are replaced by simple slashes.
*
* @param path the original path * @param path the original path
* @return the normalized path * @return the normalized path
*/ */
@ -628,17 +731,14 @@ public final class Strings {
String element = pathArray[i]; String element = pathArray[i];
if (CURRENT_PATH.equals(element)) { if (CURRENT_PATH.equals(element)) {
// Points to current directory - drop it. // Points to current directory - drop it.
} } else if (TOP_PATH.equals(element)) {
else if (TOP_PATH.equals(element)) {
// Registering top path found. // Registering top path found.
tops++; tops++;
} } else {
else {
if (tops > 0) { if (tops > 0) {
// Merging path element with element corresponding to top path. // Merging path element with element corresponding to top path.
tops--; tops--;
} } else {
else {
// Normal path element found. // Normal path element found.
pathElements.add(0, element); pathElements.add(0, element);
} }
@ -655,6 +755,7 @@ public final class Strings {
/** /**
* Compare two paths after normalization of them. * Compare two paths after normalization of them.
*
* @param path1 first path for comparison * @param path1 first path for comparison
* @param path2 second path for comparison * @param path2 second path for comparison
* @return whether the two paths are equivalent after normalization * @return whether the two paths are equivalent after normalization
@ -666,9 +767,10 @@ public final class Strings {
/** /**
* Parse the given <code>localeString</code> value into a {@link java.util.Locale}. * Parse the given <code>localeString</code> value into a {@link java.util.Locale}.
* <p>This is the inverse operation of {@link java.util.Locale#toString Locale's toString}. * <p>This is the inverse operation of {@link java.util.Locale#toString Locale's toString}.
*
* @param localeString the locale string, following <code>Locale's</code> * @param localeString the locale string, following <code>Locale's</code>
* <code>toString()</code> format ("en", "en_UK", etc); * <code>toString()</code> format ("en", "en_UK", etc);
* also accepts spaces as separators, as an alternative to underscores * also accepts spaces as separators, as an alternative to underscores
* @return a corresponding <code>Locale</code> instance * @return a corresponding <code>Locale</code> instance
*/ */
public static Locale parseLocaleString(String localeString) { public static Locale parseLocaleString(String localeString) {
@ -696,7 +798,7 @@ public final class Strings {
char ch = localePart.charAt(i); char ch = localePart.charAt(i);
if (ch != '_' && ch != ' ' && !Character.isLetterOrDigit(ch)) { if (ch != '_' && ch != ' ' && !Character.isLetterOrDigit(ch)) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"Locale part \"" + localePart + "\" contains invalid characters"); "Locale part \"" + localePart + "\" contains invalid characters");
} }
} }
} }
@ -704,6 +806,7 @@ public final class Strings {
/** /**
* Determine the RFC 3066 compliant language tag, * Determine the RFC 3066 compliant language tag,
* as used for the HTTP "Accept-Language" header. * as used for the HTTP "Accept-Language" header.
*
* @param locale the Locale to transform to a language tag * @param locale the Locale to transform to a language tag
* @return the RFC 3066 compliant language tag as String * @return the RFC 3066 compliant language tag as String
*/ */
@ -719,13 +822,14 @@ public final class Strings {
/** /**
* Append the given String to the given String array, returning a new array * Append the given String to the given String array, returning a new array
* consisting of the input array contents plus the given String. * consisting of the input array contents plus the given String.
*
* @param array the array to append to (can be <code>null</code>) * @param array the array to append to (can be <code>null</code>)
* @param str the String to append * @param str the String to append
* @return the new array (never <code>null</code>) * @return the new array (never <code>null</code>)
*/ */
public static String[] addStringToArray(String[] array, String str) { public static String[] addStringToArray(String[] array, String str) {
if (Objects.isEmpty(array)) { if (Objects.isEmpty(array)) {
return new String[] {str}; return new String[]{str};
} }
String[] newArr = new String[array.length + 1]; String[] newArr = new String[array.length + 1];
System.arraycopy(array, 0, newArr, 0, array.length); System.arraycopy(array, 0, newArr, 0, array.length);
@ -737,6 +841,7 @@ public final class Strings {
* Concatenate the given String arrays into one, * Concatenate the given String arrays into one,
* with overlapping array elements included twice. * with overlapping array elements included twice.
* <p>The order of elements in the original arrays is preserved. * <p>The order of elements in the original arrays is preserved.
*
* @param array1 the first array (can be <code>null</code>) * @param array1 the first array (can be <code>null</code>)
* @param array2 the second array (can be <code>null</code>) * @param array2 the second array (can be <code>null</code>)
* @return the new array (<code>null</code> if both given arrays were <code>null</code>) * @return the new array (<code>null</code> if both given arrays were <code>null</code>)
@ -760,6 +865,7 @@ public final class Strings {
* <p>The order of elements in the original arrays is preserved * <p>The order of elements in the original arrays is preserved
* (with the exception of overlapping elements, which are only * (with the exception of overlapping elements, which are only
* included on their first occurrence). * included on their first occurrence).
*
* @param array1 the first array (can be <code>null</code>) * @param array1 the first array (can be <code>null</code>)
* @param array2 the second array (can be <code>null</code>) * @param array2 the second array (can be <code>null</code>)
* @return the new array (<code>null</code> if both given arrays were <code>null</code>) * @return the new array (<code>null</code> if both given arrays were <code>null</code>)
@ -783,6 +889,7 @@ public final class Strings {
/** /**
* Turn given source String array into sorted array. * Turn given source String array into sorted array.
*
* @param array the source array * @param array the source array
* @return the sorted array (never <code>null</code>) * @return the sorted array (never <code>null</code>)
*/ */
@ -797,6 +904,7 @@ public final class Strings {
/** /**
* Copy the given Collection into a String array. * Copy the given Collection into a String array.
* The Collection must contain String elements only. * The Collection must contain String elements only.
*
* @param collection the Collection to copy * @param collection the Collection to copy
* @return the String array (<code>null</code> if the passed-in * @return the String array (<code>null</code> if the passed-in
* Collection was <code>null</code>) * Collection was <code>null</code>)
@ -811,6 +919,7 @@ public final class Strings {
/** /**
* Copy the given Enumeration into a String array. * Copy the given Enumeration into a String array.
* The Enumeration must contain String elements only. * The Enumeration must contain String elements only.
*
* @param enumeration the Enumeration to copy * @param enumeration the Enumeration to copy
* @return the String array (<code>null</code> if the passed-in * @return the String array (<code>null</code> if the passed-in
* Enumeration was <code>null</code>) * Enumeration was <code>null</code>)
@ -826,6 +935,7 @@ public final class Strings {
/** /**
* Trim the elements of the given String array, * Trim the elements of the given String array,
* calling <code>String.trim()</code> on each of them. * calling <code>String.trim()</code> on each of them.
*
* @param array the original String array * @param array the original String array
* @return the resulting array (of the same size) with trimmed elements * @return the resulting array (of the same size) with trimmed elements
*/ */
@ -844,6 +954,7 @@ public final class Strings {
/** /**
* Remove duplicate Strings from the given array. * Remove duplicate Strings from the given array.
* Also sorts the array, as it uses a TreeSet. * Also sorts the array, as it uses a TreeSet.
*
* @param array the String array * @param array the String array
* @return an array without duplicates, in natural sort order * @return an array without duplicates, in natural sort order
*/ */
@ -861,7 +972,8 @@ public final class Strings {
/** /**
* Split a String at the first occurrence of the delimiter. * Split a String at the first occurrence of the delimiter.
* Does not include the delimiter in the result. * Does not include the delimiter in the result.
* @param toSplit the string to split *
* @param toSplit the string to split
* @param delimiter to split the string up with * @param delimiter to split the string up with
* @return a two element array with index 0 being before the delimiter, and * @return a two element array with index 0 being before the delimiter, and
* index 1 being after the delimiter (neither element includes the delimiter); * index 1 being after the delimiter (neither element includes the delimiter);
@ -877,7 +989,7 @@ public final class Strings {
} }
String beforeDelimiter = toSplit.substring(0, offset); String beforeDelimiter = toSplit.substring(0, offset);
String afterDelimiter = toSplit.substring(offset + delimiter.length()); String afterDelimiter = toSplit.substring(offset + delimiter.length());
return new String[] {beforeDelimiter, afterDelimiter}; return new String[]{beforeDelimiter, afterDelimiter};
} }
/** /**
@ -886,7 +998,8 @@ public final class Strings {
* delimiter providing the key, and the right of the delimiter providing the value. * delimiter providing the key, and the right of the delimiter providing the value.
* <p>Will trim both the key and value before adding them to the * <p>Will trim both the key and value before adding them to the
* <code>Properties</code> instance. * <code>Properties</code> instance.
* @param array the array to process *
* @param array the array to process
* @param delimiter to split each element using (typically the equals symbol) * @param delimiter to split each element using (typically the equals symbol)
* @return a <code>Properties</code> instance representing the array contents, * @return a <code>Properties</code> instance representing the array contents,
* or <code>null</code> if the array to process was null or empty * or <code>null</code> if the array to process was null or empty
@ -901,16 +1014,17 @@ public final class Strings {
* delimiter providing the key, and the right of the delimiter providing the value. * delimiter providing the key, and the right of the delimiter providing the value.
* <p>Will trim both the key and value before adding them to the * <p>Will trim both the key and value before adding them to the
* <code>Properties</code> instance. * <code>Properties</code> instance.
* @param array the array to process *
* @param delimiter to split each element using (typically the equals symbol) * @param array the array to process
* @param delimiter to split each element using (typically the equals symbol)
* @param charsToDelete one or more characters to remove from each element * @param charsToDelete one or more characters to remove from each element
* prior to attempting the split operation (typically the quotation mark * prior to attempting the split operation (typically the quotation mark
* symbol), or <code>null</code> if no removal should occur * symbol), or <code>null</code> if no removal should occur
* @return a <code>Properties</code> instance representing the array contents, * @return a <code>Properties</code> instance representing the array contents,
* or <code>null</code> if the array to process was <code>null</code> or empty * or <code>null</code> if the array to process was <code>null</code> or empty
*/ */
public static Properties splitArrayElementsIntoProperties( public static Properties splitArrayElementsIntoProperties(
String[] array, String delimiter, String charsToDelete) { String[] array, String delimiter, String charsToDelete) {
if (Objects.isEmpty(array)) { if (Objects.isEmpty(array)) {
return null; return null;
@ -936,9 +1050,10 @@ public final class Strings {
* delimiter characters. Each of those characters can be used to separate * delimiter characters. Each of those characters can be used to separate
* tokens. A delimiter is always a single character; for multi-character * tokens. A delimiter is always a single character; for multi-character
* delimiters, consider using <code>delimitedListToStringArray</code> * delimiters, consider using <code>delimitedListToStringArray</code>
* @param str the String to tokenize *
* @param str the String to tokenize
* @param delimiters the delimiter characters, assembled as String * @param delimiters the delimiter characters, assembled as String
* (each of those characters is individually considered as delimiter). * (each of those characters is individually considered as delimiter).
* @return an array of the tokens * @return an array of the tokens
* @see java.util.StringTokenizer * @see java.util.StringTokenizer
* @see java.lang.String#trim() * @see java.lang.String#trim()
@ -954,13 +1069,14 @@ public final class Strings {
* delimiter characters. Each of those characters can be used to separate * delimiter characters. Each of those characters can be used to separate
* tokens. A delimiter is always a single character; for multi-character * tokens. A delimiter is always a single character; for multi-character
* delimiters, consider using <code>delimitedListToStringArray</code> * delimiters, consider using <code>delimitedListToStringArray</code>
* @param str the String to tokenize *
* @param delimiters the delimiter characters, assembled as String * @param str the String to tokenize
* (each of those characters is individually considered as delimiter) * @param delimiters the delimiter characters, assembled as String
* @param trimTokens trim the tokens via String's <code>trim</code> * (each of those characters is individually considered as delimiter)
* @param trimTokens trim the tokens via String's <code>trim</code>
* @param ignoreEmptyTokens omit empty tokens from the result array * @param ignoreEmptyTokens omit empty tokens from the result array
* (only applies to tokens that are empty after trimming; StringTokenizer * (only applies to tokens that are empty after trimming; StringTokenizer
* will not consider subsequent delimiters as token in the first place). * will not consider subsequent delimiters as token in the first place).
* @return an array of the tokens (<code>null</code> if the input String * @return an array of the tokens (<code>null</code> if the input String
* was <code>null</code>) * was <code>null</code>)
* @see java.util.StringTokenizer * @see java.util.StringTokenizer
@ -968,7 +1084,7 @@ public final class Strings {
* @see #delimitedListToStringArray * @see #delimitedListToStringArray
*/ */
public static String[] tokenizeToStringArray( public static String[] tokenizeToStringArray(
String str, String delimiters, boolean trimTokens, boolean ignoreEmptyTokens) { String str, String delimiters, boolean trimTokens, boolean ignoreEmptyTokens) {
if (str == null) { if (str == null) {
return null; return null;
@ -992,9 +1108,10 @@ public final class Strings {
* <p>A single delimiter can consists of more than one character: It will still * <p>A single delimiter can consists of more than one character: It will still
* be considered as single delimiter string, rather than as bunch of potential * be considered as single delimiter string, rather than as bunch of potential
* delimiter characters - in contrast to <code>tokenizeToStringArray</code>. * delimiter characters - in contrast to <code>tokenizeToStringArray</code>.
* @param str the input String *
* @param str the input String
* @param delimiter the delimiter between elements (this is a single delimiter, * @param delimiter the delimiter between elements (this is a single delimiter,
* rather than a bunch individual delimiter characters) * rather than a bunch individual delimiter characters)
* @return an array of the tokens in the list * @return an array of the tokens in the list
* @see #tokenizeToStringArray * @see #tokenizeToStringArray
*/ */
@ -1007,11 +1124,12 @@ public final class Strings {
* <p>A single delimiter can consists of more than one character: It will still * <p>A single delimiter can consists of more than one character: It will still
* be considered as single delimiter string, rather than as bunch of potential * be considered as single delimiter string, rather than as bunch of potential
* delimiter characters - in contrast to <code>tokenizeToStringArray</code>. * delimiter characters - in contrast to <code>tokenizeToStringArray</code>.
* @param str the input String *
* @param delimiter the delimiter between elements (this is a single delimiter, * @param str the input String
* rather than a bunch individual delimiter characters) * @param delimiter the delimiter between elements (this is a single delimiter,
* rather than a bunch individual delimiter characters)
* @param charsToDelete a set of characters to delete. Useful for deleting unwanted * @param charsToDelete a set of characters to delete. Useful for deleting unwanted
* line breaks: e.g. "\r\n\f" will delete all new lines and line feeds in a String. * line breaks: e.g. "\r\n\f" will delete all new lines and line feeds in a String.
* @return an array of the tokens in the list * @return an array of the tokens in the list
* @see #tokenizeToStringArray * @see #tokenizeToStringArray
*/ */
@ -1020,15 +1138,14 @@ public final class Strings {
return new String[0]; return new String[0];
} }
if (delimiter == null) { if (delimiter == null) {
return new String[] {str}; return new String[]{str};
} }
List<String> result = new ArrayList<String>(); List<String> result = new ArrayList<String>();
if ("".equals(delimiter)) { if ("".equals(delimiter)) {
for (int i = 0; i < str.length(); i++) { for (int i = 0; i < str.length(); i++) {
result.add(deleteAny(str.substring(i, i + 1), charsToDelete)); result.add(deleteAny(str.substring(i, i + 1), charsToDelete));
} }
} } else {
else {
int pos = 0; int pos = 0;
int delPos; int delPos;
while ((delPos = str.indexOf(delimiter, pos)) != -1) { while ((delPos = str.indexOf(delimiter, pos)) != -1) {
@ -1045,6 +1162,7 @@ public final class Strings {
/** /**
* Convert a CSV list into an array of Strings. * Convert a CSV list into an array of Strings.
*
* @param str the input String * @param str the input String
* @return an array of Strings, or the empty array in case of empty input * @return an array of Strings, or the empty array in case of empty input
*/ */
@ -1055,6 +1173,7 @@ public final class Strings {
/** /**
* Convenience method to convert a CSV string list to a set. * Convenience method to convert a CSV string list to a set.
* Note that this will suppress duplicates. * Note that this will suppress duplicates.
*
* @param str the input String * @param str the input String
* @return a Set of String entries in the list * @return a Set of String entries in the list
*/ */
@ -1070,8 +1189,9 @@ public final class Strings {
/** /**
* Convenience method to return a Collection as a delimited (e.g. CSV) * Convenience method to return a Collection as a delimited (e.g. CSV)
* String. E.g. useful for <code>toString()</code> implementations. * String. E.g. useful for <code>toString()</code> implementations.
* @param coll the Collection to display *
* @param delim the delimiter to use (probably a ",") * @param coll the Collection to display
* @param delim the delimiter to use (probably a ",")
* @param prefix the String to start each element with * @param prefix the String to start each element with
* @param suffix the String to end each element with * @param suffix the String to end each element with
* @return the delimited String * @return the delimited String
@ -1094,7 +1214,8 @@ public final class Strings {
/** /**
* Convenience method to return a Collection as a delimited (e.g. CSV) * Convenience method to return a Collection as a delimited (e.g. CSV)
* String. E.g. useful for <code>toString()</code> implementations. * String. E.g. useful for <code>toString()</code> implementations.
* @param coll the Collection to display *
* @param coll the Collection to display
* @param delim the delimiter to use (probably a ",") * @param delim the delimiter to use (probably a ",")
* @return the delimited String * @return the delimited String
*/ */
@ -1105,6 +1226,7 @@ public final class Strings {
/** /**
* Convenience method to return a Collection as a CSV String. * Convenience method to return a Collection as a CSV String.
* E.g. useful for <code>toString()</code> implementations. * E.g. useful for <code>toString()</code> implementations.
*
* @param coll the Collection to display * @param coll the Collection to display
* @return the delimited String * @return the delimited String
*/ */
@ -1115,7 +1237,8 @@ public final class Strings {
/** /**
* Convenience method to return a String array as a delimited (e.g. CSV) * Convenience method to return a String array as a delimited (e.g. CSV)
* String. E.g. useful for <code>toString()</code> implementations. * String. E.g. useful for <code>toString()</code> implementations.
* @param arr the array to display *
* @param arr the array to display
* @param delim the delimiter to use (probably a ",") * @param delim the delimiter to use (probably a ",")
* @return the delimited String * @return the delimited String
*/ */
@ -1139,6 +1262,7 @@ public final class Strings {
/** /**
* Convenience method to return a String array as a CSV String. * Convenience method to return a String array as a CSV String.
* E.g. useful for <code>toString()</code> implementations. * E.g. useful for <code>toString()</code> implementations.
*
* @param arr the array to display * @param arr the array to display
* @return the delimited String * @return the delimited String
*/ */
@ -1146,5 +1270,30 @@ public final class Strings {
return arrayToDelimitedString(arr, ","); return arrayToDelimitedString(arr, ",");
} }
/**
* Appends a space character (<code>' '</code>) if the argument is not empty, otherwise does nothing. This method
* can be thought of as &quot;non-empty space&quot;. Using this method allows reduction of this:
* <blockquote><pre>
* if (sb.length != 0) {
* sb.append(' ');
* }
* sb.append(nextWord);</pre></blockquote>
* <p>To this:</p>
* <blockquote><pre>
* nespace(sb).append(nextWord);</pre></blockquote>
* @param sb the string builder to append a space to if non-empty
* @return the string builder argument for method chaining.
* @since JJWT_RELEASE_VERSION
*/
public static StringBuilder nespace(StringBuilder sb) {
if (sb == null) {
return null;
}
if (sb.length() != 0) {
sb.append(' ');
}
return sb;
}
} }

View File

@ -0,0 +1,37 @@
/*
* Copyright (C) 2022 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.lang;
/**
* Represents a supplier of results.
*
* <p>There is no requirement that a new or distinct result be returned each time the supplier is invoked.</p>
*
* <p>This interface is the equivalent of a JDK 8 {@code java.util.function.Supplier}, backported for JJWT's use in
* JDK 7 environments.</p>
*
* @param <T> the type of object returned by this supplier
* @since JJWT_RELEASE_VERSION
*/
public interface Supplier<T> {
/**
* Returns a result.
*
* @return a result.
*/
T get();
}

View File

@ -0,0 +1,91 @@
/*
* Copyright (C) 2021 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.security;
import io.jsonwebtoken.Identifiable;
import io.jsonwebtoken.Jwts;
import javax.crypto.SecretKey;
/**
* A cryptographic algorithm that performs
* <a href="https://en.wikipedia.org/wiki/Authenticated_encryption">Authenticated encryption with additional data</a>.
* Per <a href="https://www.rfc-editor.org/rfc/rfc7516.html#section-4.1.2">JWE RFC 7516, Section 4.1.2</a>, all JWEs
* <em>MUST</em> use an AEAD algorithm to encrypt or decrypt the JWE payload/content. Consequently, all
* <a href="https://www.rfc-editor.org/rfc/rfc7518.html#section-5.1">JWA &quot;enc&quot; algorithms</a> are AEAD
* algorithms, and they are accessible as concrete instances via {@link Jwts#ENC}.
*
* <p><b>&quot;enc&quot; identifier</b></p>
*
* <p>{@code AeadAlgorithm} extends {@code Identifiable}: the value returned from {@link Identifiable#getId() getId()}
* will be used as the JWE &quot;enc&quot; protected header value.</p>
*
* <p><b>Key Strength</b></p>
*
* <p>Encryption strength is in part attributed to how difficult it is to discover the encryption key. As such,
* cryptographic algorithms often require keys of a minimum length to ensure the keys are difficult to discover
* and the algorithm's security properties are maintained.</p>
*
* <p>The {@code AeadAlgorithm} interface extends the {@link KeyLengthSupplier} interface to represent the length
* in bits a key must have to be used with its implementation. If you do not want to worry about lengths and
* parameters of keys required for an algorithm, it is often easier to automatically generate a key that adheres
* to the algorithms requirements, as discussed below.</p>
*
* <p><b>Key Generation</b></p>
*
* <p>{@code AeadAlgorithm} extends {@link KeyBuilderSupplier} to enable {@link SecretKey} generation. Each AEAD
* algorithm instance will return a {@link KeyBuilder} that ensures any created keys will have a sufficient length
* and algorithm parameters required by that algorithm. For example:</p>
*
* <pre><code>
* SecretKey key = aeadAlgorithm.keyBuilder().build();
* </code></pre>
*
* <p>The resulting {@code key} is guaranteed to have the correct algorithm parameters and strength/length necessary for
* that exact {@code aeadAlgorithm} instance.</p>
*
* @see Jwts#ENC
* @see Identifiable#getId()
* @see KeyLengthSupplier
* @see KeyBuilderSupplier
* @see KeyBuilder
* @since JJWT_RELEASE_VERSION
*/
public interface AeadAlgorithm extends Identifiable, KeyLengthSupplier, KeyBuilderSupplier<SecretKey, SecretKeyBuilder> {
/**
* Perform AEAD encryption with the plaintext represented by the specified {@code request}, returning the
* integrity-protected encrypted ciphertext result.
*
* @param request the encryption request representing the plaintext to be encrypted, any additional
* integrity-protected data and the encryption key.
* @return the encryption result containing the ciphertext, and associated initialization vector and resulting
* authentication tag.
* @throws SecurityException if there is an encryption problem or authenticity cannot be guaranteed.
*/
AeadResult encrypt(AeadRequest request) throws SecurityException;
/**
* Perform AEAD decryption with the ciphertext represented by the specific {@code request}, also verifying the
* integrity and authenticity of any associated data, returning the decrypted plaintext result.
*
* @param request the decryption request representing the ciphertext to be decrypted, any additional
* integrity-protected data, authentication tag, initialization vector, and the decryption key.
* @return the decryption result containing the plaintext
* @throws SecurityException if there is a decryption problem or authenticity assertions fail.
*/
Message<byte[]> decrypt(DecryptAeadRequest request) throws SecurityException;
}

View File

@ -0,0 +1,29 @@
/*
* Copyright (C) 2021 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.security;
import javax.crypto.SecretKey;
/**
* A request to an {@link AeadAlgorithm} to perform authenticated encryption with a supplied symmetric
* {@link SecretKey}, allowing for additional data to be authenticated and integrity-protected.
*
* @see SecureRequest
* @see AssociatedDataSupplier
* @since JJWT_RELEASE_VERSION
*/
public interface AeadRequest extends SecureRequest<byte[], SecretKey>, AssociatedDataSupplier {
}

View File

@ -0,0 +1,37 @@
/*
* Copyright (C) 2021 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.security;
/**
* The result of authenticated encryption, providing access to the resulting {@link #getPayload() ciphertext},
* {@link #getDigest() AAD tag}, and {@link #getInitializationVector() initialization vector}. The AAD tag and
* initialization vector must be supplied with the ciphertext to decrypt.
*
* <p><b>AAD Tag</b></p>
*
* {@code AeadResult} inherits {@link DigestSupplier} which is a generic concept for supplying any digest. The digest
* in the case of AEAD is called an AAD tag, and it must in turn be supplied for verification during decryption.
*
* <p><b>Initialization Vector</b></p>
*
* All JWE-standard AEAD algorithms use a secure-random Initialization Vector for safe ciphertext creation, so
* {@code AeadResult} inherits {@link InitializationVectorSupplier} to make the generated IV available after
* encryption. This IV must in turn be supplied during decryption.
*
* @since JJWT_RELEASE_VERSION
*/
public interface AeadResult extends Message<byte[]>, DigestSupplier, InitializationVectorSupplier {
}

View File

@ -0,0 +1,37 @@
/*
* Copyright (C) 2021 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.security;
/**
* Provides any &quot;associated data&quot; that must be integrity protected (but not encrypted) when performing
* <a href="https://en.wikipedia.org/wiki/Authenticated_encryption">AEAD encryption or decryption</a>.
*
* @see #getAssociatedData()
* @since JJWT_RELEASE_VERSION
*/
public interface AssociatedDataSupplier {
/**
* Returns any data that must be integrity protected (but not encrypted) when performing
* <a href="https://en.wikipedia.org/wiki/Authenticated_encryption">AEAD encryption or decryption</a>, or
* {@code null} if no additional data must be integrity protected.
*
* @return any data that must be integrity protected (but not encrypted) when performing
* <a href="https://en.wikipedia.org/wiki/Authenticated_encryption">AEAD encryption or decryption</a>, or
* {@code null} if no additional data must be integrity protected.
*/
byte[] getAssociatedData();
}

View File

@ -0,0 +1,75 @@
/*
* Copyright (C) 2021 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.security;
import java.security.Key;
/**
* JWK representation of an asymmetric (public or private) cryptographic key.
*
* @param <K> the type of {@link java.security.PublicKey} or {@link java.security.PrivateKey} represented by this JWK.
* @since JJWT_RELEASE_VERSION
*/
public interface AsymmetricJwk<K extends Key> extends Jwk<K>, X509Accessor {
/**
* Returns the JWK
* <a href="https://www.rfc-editor.org/rfc/rfc7517.html#section-4.2">{@code use} (Public Key Use)
* parameter</a> value or {@code null} if not present. {@code use} values are CaSe-SeNsItIvE.
*
* <p>The JWK specification <a href="https://www.rfc-editor.org/rfc/rfc7517.html#section-4.2">defines</a> the
* following {@code use} values:</p>
*
* <table>
* <caption>JWK Key Use Values</caption>
* <thead>
* <tr>
* <th>Value</th>
* <th>Key Use</th>
* </tr>
* </thead>
* <tbody>
* <tr>
* <td><b>{@code sig}</b></td>
* <td>signature</td>
* </tr>
* <tr>
* <td><b>{@code enc}</b></td>
* <td>encryption</td>
* </tr>
* </tbody>
* </table>
*
* <p>Other values <em>MAY</em> be used. For best interoperability with other applications however, it is
* recommended to use only the values above.</p>
*
* <p>When a key is used to wrap another key and a public key use designation for the first key is desired, the
* {@code enc} (encryption) key use value is used, since key wrapping is a kind of encryption. The
* {@code enc} value is also to be used for public keys used for key agreement operations.</p>
*
* <p><b>Public Key Use vs Key Operations</b></p>
*
* <p>Per
* <a href="https://www.rfc-editor.org/rfc/rfc7517.html#section-4.3">JWK RFC 7517, Section 4.3, last paragraph</a>,
* the {@code use} (Public Key Use) and {@link #getOperations() key_ops (Key Operations)} members
* <em>SHOULD NOT</em> be used together; however, if both are used, the information they convey <em>MUST</em> be
* consistent. Applications should specify which of these members they use, if either is to be used by the
* application.</p>
*
* @return the JWK {@code use} value or {@code null} if not present.
*/
String getPublicKeyUse();
}

View File

@ -0,0 +1,82 @@
/*
* Copyright (C) 2021 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.security;
import java.security.Key;
import java.util.Set;
/**
* A {@link JwkBuilder} that builds asymmetric (public or private) JWKs.
*
* @param <K> the type of Java key provided by the JWK.
* @param <J> the type of asymmetric JWK created
* @param <T> the type of the builder, for subtype method chaining
* @since JJWT_RELEASE_VERSION
*/
public interface AsymmetricJwkBuilder<K extends Key, J extends AsymmetricJwk<K>, T extends AsymmetricJwkBuilder<K, J, T>>
extends JwkBuilder<K, J, T>, X509Builder<T> {
/**
* Sets the JWK
* <a href="https://www.rfc-editor.org/rfc/rfc7517.html#section-4.2">{@code use} (Public Key Use)
* parameter</a> value. {@code use} values are CaSe-SeNsItIvE. A {@code null} value will remove the property
* from the JWK.
*
* <p>The JWK specification <a href="https://www.rfc-editor.org/rfc/rfc7517.html#section-4.2">defines</a> the
* following {@code use} values:</p>
*
* <table>
* <caption>JWK Key Use Values</caption>
* <thead>
* <tr>
* <th>Value</th>
* <th>Key Use</th>
* </tr>
* </thead>
* <tbody>
* <tr>
* <td><b>{@code sig}</b></td>
* <td>signature</td>
* </tr>
* <tr>
* <td><b>{@code enc}</b></td>
* <td>encryption</td>
* </tr>
* </tbody>
* </table>
*
* <p>Other values <em>MAY</em> be used. For best interoperability with other applications however, it is
* recommended to use only the values above.</p>
*
* <p>When a key is used to wrap another key and a public key use designation for the first key is desired, the
* {@code enc} (encryption) key use value is used, since key wrapping is a kind of encryption. The
* {@code enc} value is also to be used for public keys used for key agreement operations.</p>
*
* <p><b>Public Key Use vs Key Operations</b></p>
*
* <p>Per
* <a href="https://www.rfc-editor.org/rfc/rfc7517.html#section-4.3">JWK RFC 7517, Section 4.3, last paragraph</a>,
* the {@code use} (Public Key Use) and {@link #setOperations(Set) key_ops (Key Operations)} members
* <em>SHOULD NOT</em> be used together; however, if both are used, the information they convey <em>MUST</em> be
* consistent. Applications should specify which of these members they use, if either is to be used by the
* application.</p>
*
* @param use the JWK {@code use} value.
* @return the builder for method chaining.
* @throws IllegalArgumentException if the {@code use} value is {@code null} or empty.
*/
T setPublicKeyUse(String use) throws IllegalArgumentException;
}

View File

@ -0,0 +1,28 @@
/*
* Copyright (C) 2021 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.security;
import javax.crypto.SecretKey;
/**
* A request to an {@link AeadAlgorithm} to decrypt ciphertext and perform integrity-protection with a supplied
* decryption {@link SecretKey}. Extends both {@link InitializationVectorSupplier} and {@link DigestSupplier} to
* ensure the respective required IV and AAD tag returned from an {@link AeadResult} are available for decryption.
*
* @since JJWT_RELEASE_VERSION
*/
public interface DecryptAeadRequest extends AeadRequest, InitializationVectorSupplier, DigestSupplier {
}

View File

@ -0,0 +1,42 @@
/*
* Copyright (C) 2021 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.security;
import java.security.Key;
/**
* A {@link KeyRequest} to obtain a decryption key that will be used to decrypt a JWE using an {@link AeadAlgorithm}.
* The AEAD algorithm used for decryption is accessible via {@link #getEncryptionAlgorithm()}.
*
* <p>The key used to perform cryptographic operations, for example a direct shared key, or a
* JWE &quot;key decryption key&quot; will be accessible via {@link #getKey()}. This is always required and
* never {@code null}.</p>
*
* <p>Any encrypted key material (what the JWE specification calls the
* <a href="https://www.rfc-editor.org/rfc/rfc7516.html#section-2">JWE Encrypted Key</a>) will
* be accessible via {@link #getPayload()}. If present, the {@link KeyAlgorithm} will decrypt it to obtain the resulting
* <a href="https://www.rfc-editor.org/rfc/rfc7516.html#section-2">Content Encryption Key (CEK)</a>.
* This may be empty however depending on which {@link KeyAlgorithm} was used during JWE encryption.</p>
*
* <p>Finally, any public information necessary by the called {@link KeyAlgorithm} to decrypt any
* {@code JWE Encrypted Key} (such as an initialization vector, authentication tag, ephemeral key, etc) is expected
* to be available in the JWE protected header, accessible via {@link #getHeader()}.</p>
*
* @param <K> the type of {@link Key} used during the request to obtain the resulting decryption key.
* @since JJWT_RELEASE_VERSION
*/
public interface DecryptionKeyRequest<K extends Key> extends SecureRequest<byte[], K>, KeyRequest<byte[]> {
}

View File

@ -0,0 +1,100 @@
/*
* Copyright © 2022 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.security;
import io.jsonwebtoken.Identifiable;
import io.jsonwebtoken.lang.Registry;
import javax.crypto.SecretKey;
import java.security.PrivateKey;
import java.security.PublicKey;
/**
* A {@code DigestAlgorithm} is a
* <a href="https://en.wikipedia.org/wiki/Cryptographic_hash_function">Cryptographic Hash Function</a>
* that computes and verifies cryptographic digests. There are three types of {@code DigestAlgorithm}s represented
* by subtypes, and RFC-standard implementations are available as constants in {@link Registry} singletons:
*
* <table>
* <caption>Types of {@code DigestAlgorithm}s</caption>
* <thead>
* <tr>
* <th>Subtype</th>
* <th>Standard Implementation Registry</th>
* <th>Security Model</th>
* </tr>
* </thead>
* <tbody>
* <tr>
* <td>{@link HashAlgorithm}</td>
* <td>{@link StandardHashAlgorithms}</td>
* <td>Unsecured (unkeyed), does not require a key to compute or verify digests.</td>
* </tr>
* <tr>
* <td>{@link MacAlgorithm}</td>
* <td>{@link StandardSecureDigestAlgorithms}</td>
* <td>Requires a {@link SecretKey} to both compute and verify digests (aka
* &quot;Message Authentication Codes&quot;).</td>
* </tr>
* <tr>
* <td>{@link SignatureAlgorithm}</td>
* <td>{@link StandardSecureDigestAlgorithms}</td>
* <td>Requires a {@link PrivateKey} to compute and {@link PublicKey} to verify digests
* (aka &quot;Digital Signatures&quot;).</td>
* </tr>
* </tbody>
* </table>
*
* <p><b>Standard Identifier</b></p>
*
* <p>{@code DigestAlgorithm} extends {@link Identifiable}: the value returned from
* {@link Identifiable#getId() getId()} will be used as the JWT standard identifier where required.</p>
*
* <p>For example,
* when a {@link MacAlgorithm} or {@link SignatureAlgorithm} is used to secure a JWS, the value returned from
* {@code algorithm.getId()} will be used as the JWS <code>&quot;alg&quot;</code> protected header value. Or when a
* {@link HashAlgorithm} is used to compute a {@link JwkThumbprint}, it's {@code algorithm.getId()} value will be
* used within the thumbprint's {@link JwkThumbprint#toURI() URI} per JWT RFC requirements.</p>
*
* @param <R> the type of {@link Request} used when computing a digest.
* @param <V> the type of {@link VerifyDigestRequest} used when verifying a digest.
* @see StandardHashAlgorithms
* @see StandardSecureDigestAlgorithms
* @since JJWT_RELEASE_VERSION
*/
public interface DigestAlgorithm<R extends Request<byte[]>, V extends VerifyDigestRequest> extends Identifiable {
/**
* Returns a cryptographic digest of the request {@link Request#getPayload() payload}.
*
* @param request the request containing the data to be hashed, mac'd or signed.
* @return a cryptographic digest of the request {@link Request#getPayload() payload}.
* @throws SecurityException if there is invalid key input or a problem during digest creation.
*/
byte[] digest(R request) throws SecurityException;
/**
* Returns {@code true} if the provided {@link VerifyDigestRequest#getDigest() digest} matches the expected value
* for the given {@link VerifyDigestRequest#getPayload() payload}, {@code false} otherwise.
*
* @param request the request containing the {@link VerifyDigestRequest#getDigest() digest} to verify for the
* associated {@link VerifyDigestRequest#getPayload() payload}.
* @return {@code true} if the provided {@link VerifyDigestRequest#getDigest() digest} matches the expected value
* for the given {@link VerifyDigestRequest#getPayload() payload}, {@code false} otherwise.
* @throws SecurityException if there is an invalid key input or a problem that won't allow digest verification.
*/
boolean verify(V request) throws SecurityException;
}

View File

@ -0,0 +1,35 @@
/*
* Copyright (C) 2021 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.security;
/**
* A {@code DigestSupplier} provides access to the result of a cryptographic digest algorithm, such as a
* Message Digest, MAC, Signature, or Authentication Tag.
*
* @since JJWT_RELEASE_VERSION
*/
public interface DigestSupplier {
/**
* Returns a cryptographic digest result, such as a Message Digest, MAC, Signature, or Authentication Tag
* depending on the cryptographic algorithm that produced it.
*
* @return a cryptographic digest result, such as a Message Digest, MAC, Signature, or Authentication Tag
* * depending on the cryptographic algorithm that produced it.
*/
byte[] getDigest();
}

View File

@ -0,0 +1,43 @@
/*
* Copyright (C) 2021 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.security;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
/**
* JWK representation of an {@link ECPrivateKey} as defined by the JWA (RFC 7518) specification sections on
* <a href="https://www.rfc-editor.org/rfc/rfc7518.html#section-6.2">Parameters for Elliptic Curve Keys</a> and
* <a href="https://www.rfc-editor.org/rfc/rfc7518.html#section-6.2.2">Parameters for Elliptic Curve Private Keys</a>.
*
* <p>Note that the various EC-specific properties are not available as separate dedicated getter methods, as most Java
* applications should rarely, if ever, need to access these individual key properties since they typically represent
* internal key material and/or serialization details. If you need to access these key properties, it is usually
* recommended to obtain the corresponding {@link ECPrivateKey} instance returned by {@link #toKey()} and
* query that instead.</p>
*
* <p>Even so, because these properties exist and are readable by nature of every JWK being a
* {@link java.util.Map Map}, they are still accessible via the standard {@code Map} {@link #get(Object) get} method
* using an appropriate JWK parameter id, for example:</p>
* <blockquote><pre>
* jwk.get(&quot;x&quot;);
* jwk.get(&quot;y&quot;);
* // ... etc ...</pre></blockquote>
*
* @since JJWT_RELEASE_VERSION
*/
public interface EcPrivateJwk extends PrivateJwk<ECPrivateKey, ECPublicKey, EcPublicJwk> {
}

View File

@ -0,0 +1,27 @@
/*
* Copyright (C) 2021 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.security;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
/**
* A {@link PrivateJwkBuilder} that creates {@link EcPrivateJwk}s.
*
* @since JJWT_RELEASE_VERSION
*/
public interface EcPrivateJwkBuilder extends PrivateJwkBuilder<ECPrivateKey, ECPublicKey, EcPublicJwk, EcPrivateJwk, EcPrivateJwkBuilder> {
}

View File

@ -0,0 +1,42 @@
/*
* Copyright (C) 2021 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.security;
import java.security.interfaces.ECPublicKey;
/**
* JWK representation of an {@link ECPublicKey} as defined by the JWA (RFC 7518) specification sections on
* <a href="https://www.rfc-editor.org/rfc/rfc7518.html#section-6.2">Parameters for Elliptic Curve Keys</a> and
* <a href="https://www.rfc-editor.org/rfc/rfc7518.html#section-6.2.1">Parameters for Elliptic Curve Public Keys</a>.
*
* <p>Note that the various EC-specific properties are not available as separate dedicated getter methods, as most Java
* applications should rarely, if ever, need to access these individual key properties since they typically represent
* internal key material and/or serialization details. If you need to access these key properties, it is usually
* recommended to obtain the corresponding {@link ECPublicKey} instance returned by {@link #toKey()} and
* query that instead.</p>
*
* <p>Even so, because these properties exist and are readable by nature of every JWK being a
* {@link java.util.Map Map}, they are still accessible via the standard {@code Map} {@link #get(Object) get} method
* using an appropriate JWK parameter id, for example:</p>
* <blockquote><pre>
* jwk.get(&quot;x&quot;);
* jwk.get(&quot;y&quot;);
* // ... etc ...</pre></blockquote>
*
* @since JJWT_RELEASE_VERSION
*/
public interface EcPublicJwk extends PublicJwk<ECPublicKey> {
}

View File

@ -0,0 +1,27 @@
/*
* Copyright (C) 2021 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.security;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
/**
* A {@link PublicJwkBuilder} that creates {@link EcPublicJwk}s.
*
* @since JJWT_RELEASE_VERSION
*/
public interface EcPublicJwkBuilder extends PublicJwkBuilder<ECPublicKey, ECPrivateKey, EcPublicJwk, EcPrivateJwk, EcPrivateJwkBuilder, EcPublicJwkBuilder> {
}

View File

@ -0,0 +1,43 @@
/*
* Copyright © 2022 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.security;
import io.jsonwebtoken.Identifiable;
/**
* A {@link DigestAlgorithm} that computes and verifies digests without the use of a cryptographic key, such as for
* thumbprints and <a href="https://en.wikipedia.org/wiki/Fingerprint_(computing)">digital fingerprint</a>s.
*
* <p><b>Standard Identifier</b></p>
*
* <p>{@code HashAlgorithm} extends {@link Identifiable}: the value returned from
* {@link Identifiable#getId() getId()} in all JWT standard hash algorithms will return one of the
* &quot;{@code Hash Name String}&quot; values defined in the IANA
* <a href="https://www.iana.org/assignments/named-information/named-information.xhtml">Named Information Hash
* Algorithm Registry</a>. This is to ensure the correct algorithm ID is used within other JWT-standard identifiers,
* such as within <a href="https://www.rfc-editor.org/rfc/rfc9278.html">JWK Thumbprint URI</a>s.</p>
*
* <p><b>IANA Standard Implementations</b></p>
*
* <p>Constant definitions and utility methods for common (<em>but not all</em>)
* <a href="https://www.iana.org/assignments/named-information/named-information.xhtml#hash-alg">IANA Hash
* Algorithms</a> are available via the {@link StandardHashAlgorithms} singleton.</p>
*
* @see StandardHashAlgorithms
* @since JJWT_RELEASE_VERSION
*/
public interface HashAlgorithm extends DigestAlgorithm<Request<byte[]>, VerifyDigestRequest> {
}

View File

@ -0,0 +1,36 @@
/*
* Copyright (C) 2021 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.security;
/**
* An {@code InitializationVectorSupplier} provides access to the secure-random Initialization Vector used during
* encryption, which must in turn be presented for use during decryption. To maintain the security integrity of cryptographic
* algorithms, a <em>new</em> secure-random Initialization Vector <em>MUST</em> be generated for every individual
* encryption attempt.
*
* @since JJWT_RELEASE_VERSION
*/
public interface InitializationVectorSupplier {
/**
* Returns the secure-random Initialization Vector used during encryption, which must in turn be presented for
* use during decryption.
*
* @return the secure-random Initialization Vector used during encryption, which must in turn be presented for
* use during decryption.
*/
byte[] getInitializationVector();
}

View File

@ -16,11 +16,30 @@
package io.jsonwebtoken.security; package io.jsonwebtoken.security;
/** /**
* A {@code KeyException} thrown when encountering a key that is not suitable for the required functionality, or
* when attempting to use a Key in an incorrect or prohibited manner.
*
* @since 0.10.0 * @since 0.10.0
*/ */
public class InvalidKeyException extends KeyException { public class InvalidKeyException extends KeyException {
/**
* Creates a new instance with the specified explanation message.
*
* @param message the message explaining why the exception is thrown.
*/
public InvalidKeyException(String message) { public InvalidKeyException(String message) {
super(message); super(message);
} }
/**
* Creates a new instance with the specified explanation message and underlying cause.
*
* @param message the message explaining why the exception is thrown.
* @param cause the underlying cause that resulted in this exception being thrown.
* @since JJWT_RELEASE_VERSION
*/
public InvalidKeyException(String message, Throwable cause) {
super(message, cause);
}
} }

View File

@ -0,0 +1,226 @@
/*
* Copyright (C) 2021 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.security;
import io.jsonwebtoken.Identifiable;
import io.jsonwebtoken.lang.Supplier;
import java.security.Key;
import java.util.Map;
import java.util.Set;
/**
* A JWK is an immutable set of name/value pairs that represent a cryptographic key as defined by
* <a href="https://www.rfc-editor.org/rfc/rfc7517.html">RFC 7517: JSON Web Key (JWK)</a>. The {@code Jwk}
* interface represents properties common to all JWKs. Subtypes will have additional properties specific to
* different types of cryptographic keys (e.g. Secret, Asymmetric, RSA, Elliptic Curve, etc).
*
* <p><b>Immutability</b></p>
*
* <p>JWKs are immutable and cannot be changed after they are created. {@code Jwk} extends the
* {@link Map} interface purely out of convenience: to allow easy marshalling to JSON as well as name/value
* pair access and key/value iteration, and other conveniences provided by the Map interface. Attempting to call any of
* the {@link Map} interface's mutation methods however (such as {@link Map#put(Object, Object) put},
* {@link Map#remove(Object) remove}, {@link Map#clear() clear}, etc) will throw an
* {@link UnsupportedOperationException}.</p>
*
* <p><b>Identification</b></p>
*
* <p>{@code Jwk} extends {@link Identifiable} to support the
* <a href="https://www.rfc-editor.org/rfc/rfc7517.html#section-4.5">JWK {@code kid} parameter</a>. Calling
* {@link #getId() aJwk.getId()} is the type-safe idiomatic approach to the alternative equivalent of
* {@code aJwk.get("kid")}. Either approach will return an id if one was originally set on the JWK, or {@code null} if
* an id does not exist.</p>
*
* <p><b>Private and Secret Value Safety</b></p>
*
* <p>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 field 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
* actual private or secret value.</p>
*
* <p>For example, a {@link SecretJwk} will have an internal &quot;{@code k}&quot; member whose value reflects raw
* key material that should always be kept secret. If the following is called:</p>
* <blockquote><pre>
* System.out.println(aSecretJwk.get(&quot;k&quot;));</pre></blockquote>
* <p>You would see the following:</p>
* <blockquote><pre>
* &lt;redacted&gt;</pre></blockquote>
* <p>instead of the actual/raw {@code k} value.</p>
*
* <p>Similarly, if attempting to print the entire JWK:</p>
* <blockquote><pre>
* System.out.println(aSecretJwk);</pre></blockquote>
* <p>You would see the following substring in the output:</p>
* <blockquote><pre>
* k=&lt;redacted&gt;</pre></blockquote>
* <p>instead of the actual/raw {@code k} value.</p>
*
* <p>Finally, because all private or secret field values are wrapped as {@link io.jsonwebtoken.lang.Supplier}
* instances, if you really wanted the <em>real</em> internal value, you could just call the supplier's
* {@link Supplier#get() get()} method:</p>
* <blockquote><pre>
* String k = ((Supplier&lt;String&gt;)aSecretJwk.get(&quot;k&quot;)).get();</pre></blockquote>
* <p>but <b><em>BE CAREFUL</em></b>: 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
* JWK internal serialization fields.</p>
*
* @param <K> The type of Java {@link Key} represented by this JWK
* @since JJWT_RELEASE_VERSION
*/
public interface Jwk<K extends Key> extends Identifiable, Map<String, Object> {
/**
* Returns the JWK
* <a href="https://www.rfc-editor.org/rfc/rfc7517.html#section-4.4">{@code alg} (Algorithm)</a> value
* or {@code null} if not present.
*
* @return the JWK {@code alg} value or {@code null} if not present.
*/
String getAlgorithm();
/**
* Returns the JWK
* <a href="https://www.rfc-editor.org/rfc/rfc7517.html#section-4.3">{@code key_ops} (Key Operations)
* parameter</a> values or {@code null} if not present. Any values within the returned {@code Set} are
* CaSe-SeNsItIvE.
*
* <p>The JWK specification <a href="https://www.rfc-editor.org/rfc/rfc7517.html#section-4.3">defines</a> the
* following values:</p>
*
* <table>
* <caption>JWK Key Operations</caption>
* <thead>
* <tr>
* <th>Value</th>
* <th>Operation</th>
* </tr>
* </thead>
* <tbody>
* <tr>
* <td><b>{@code sign}</b></td>
* <td>compute digital signatures or MAC</td>
* </tr>
* <tr>
* <td><b>{@code verify}</b></td>
* <td>verify digital signatures or MAC</td>
* </tr>
* <tr>
* <td><b>{@code encrypt}</b></td>
* <td>encrypt content</td>
* </tr>
* <tr>
* <td><b>{@code decrypt}</b></td>
* <td>decrypt content and validate decryption, if applicable</td>
* </tr>
* <tr>
* <td><b>{@code wrapKey}</b></td>
* <td>encrypt key</td>
* </tr>
* <tr>
* <td><b>{@code unwrapKey}</b></td>
* <td>decrypt key and validate decryption, if applicable</td>
* </tr>
* <tr>
* <td><b>{@code deriveKey}</b></td>
* <td>derive key</td>
* </tr>
* <tr>
* <td><b>{@code deriveBits}</b></td>
* <td>derive bits not to be used as a key</td>
* </tr>
* </tbody>
* </table>
*
* <p>Other values <em>MAY</em> be used. For best interoperability with other applications however, it is
* recommended to use only the values above.</p>
*
* <p>Multiple unrelated key operations <em>SHOULD NOT</em> be specified for a key because of the potential
* vulnerabilities associated with using the same key with multiple algorithms. Thus, the combinations
* {@code sign} with {@code verify}, {@code encrypt} with {@code decrypt}, and {@code wrapKey} with
* {@code unwrapKey} are permitted, but other combinations <em>SHOULD NOT</em> be used.</p>
*
* @return the JWK {@code key_ops} value or {@code null} if not present.
*/
Set<String> getOperations();
/**
* Returns the required JWK
* <a href="https://www.rfc-editor.org/rfc/rfc7517.html#section-4.1">{@code kty} (Key Type)
* parameter</a> value. A value is required and may not be {@code null}.
*
* <p>The JWA specification <a href="https://www.rfc-editor.org/rfc/rfc7518.html#section-6.1">defines</a> the
* following {@code kty} values:</p>
*
* <table>
* <caption>JWK Key Types</caption>
* <thead>
* <tr>
* <th>Value</th>
* <th>Key Type</th>
* </tr>
* </thead>
* <tbody>
* <tr>
* <td><b>{@code EC}</b></td>
* <td>Elliptic Curve [<a href="https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf">DSS</a>]</td>
* </tr>
* <tr>
* <td><b>{@code RSA}</b></td>
* <td>RSA [<a href="https://datatracker.ietf.org/doc/html/rfc3447">RFC 3447</a>]</td>
* </tr>
* <tr>
* <td><b>{@code oct}</b></td>
* <td>Octet sequence (used to represent symmetric keys)</td>
* </tr>
* </tbody>
* </table>
*
* @return the JWK {@code kty} (Key Type) value.
*/
String getType();
/**
* Computes and returns the canonical <a href="https://www.rfc-editor.org/rfc/rfc7638">JWK Thumbprint</a> of this
* JWK using the {@code SHA-256} hash algorithm. This is a convenience method that delegates to
* {@link #thumbprint(HashAlgorithm)} with a {@code SHA-256} {@link HashAlgorithm} instance.
*
* @return the canonical <a href="https://www.rfc-editor.org/rfc/rfc7638">JWK Thumbprint</a> of this
* JWK using the {@code SHA-256} hash algorithm.
* @see #thumbprint(HashAlgorithm)
*/
JwkThumbprint thumbprint();
/**
* Computes and returns the canonical <a href="https://www.rfc-editor.org/rfc/rfc7638">JWK Thumbprint</a> of this
* JWK using the specified hash algorithm.
*
* @param alg the hash algorithm to use to compute the digest of the canonical JWK Thumbprint JSON form of this JWK.
* @return the canonical <a href="https://www.rfc-editor.org/rfc/rfc7638">JWK Thumbprint</a> of this
* JWK using the specified hash algorithm.
*/
JwkThumbprint thumbprint(HashAlgorithm alg);
/**
* Represents the JWK as its corresponding Java {@link Key} instance for use with Java cryptographic
* APIs.
*
* @return the JWK's corresponding Java {@link Key} instance for use with Java cryptographic APIs.
*/
K toKey();
}

View File

@ -0,0 +1,178 @@
/*
* Copyright (C) 2021 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.security;
import io.jsonwebtoken.lang.MapMutator;
import java.security.Key;
import java.util.Set;
/**
* A {@link SecurityBuilder} that produces a JWK. A JWK is an immutable set of name/value pairs that represent a
* cryptographic key as defined by
* <a href="https://www.rfc-editor.org/rfc/rfc7517.html">RFC 7517: JSON Web Key (JWK)</a>.
* The {@code JwkBuilder} interface represents common JWK properties that may be specified for any type of JWK.
* Builder subtypes support additional JWK properties specific to different types of cryptographic keys
* (e.g. Secret, Asymmetric, RSA, Elliptic Curve, etc).
*
* @param <K> the type of Java {@link Key} represented by the constructed JWK.
* @param <J> the type of {@link Jwk} created by the builder
* @param <T> the type of the builder, for subtype method chaining
* @see SecretJwkBuilder
* @see RsaPublicJwkBuilder
* @see RsaPrivateJwkBuilder
* @see EcPublicJwkBuilder
* @see EcPrivateJwkBuilder
* @see OctetPublicJwkBuilder
* @see OctetPrivateJwkBuilder
* @since JJWT_RELEASE_VERSION
*/
public interface JwkBuilder<K extends Key, J extends Jwk<K>, T extends JwkBuilder<K, J, T>>
extends MapMutator<String, Object, T>, SecurityBuilder<J, T> {
/**
* Sets the JWK <a href="https://www.rfc-editor.org/rfc/rfc7517.html#section-4.4">{@code alg} (Algorithm)
* Parameter</a>.
*
* <p>The {@code alg} (algorithm) parameter identifies the algorithm intended for use with the key. The
* value specified should either be one of the values in the IANA
* <a href="https://www.rfc-editor.org/rfc/rfc7518.html#section-7.1">JSON Web Signature and Encryption
* Algorithms</a> registry or be a value that contains a {@code Collision-Resistant Name}. The {@code alg}
* must be a CaSe-SeNsItIvE ASCII string.</p>
*
* @param alg the JWK {@code alg} value.
* @return the builder for method chaining.
* @throws IllegalArgumentException if {@code alg} is {@code null} or empty.
*/
T setAlgorithm(String alg) throws IllegalArgumentException;
/**
* Sets the JWK <a href="https://www.rfc-editor.org/rfc/rfc7517.html#section-4.5">{@code kid} (Key ID)
* Parameter</a>.
*
* <p>The {@code kid} (key ID) parameter is used to match a specific key. This is used, for instance,
* to choose among a set of keys within a {@code JWK Set} during key rollover. The structure of the
* {@code kid} value is unspecified. When {@code kid} values are used within a JWK Set, different keys
* within the {@code JWK Set} <em>SHOULD</em> use distinct {@code kid} values. (One example in which
* different keys might use the same {@code kid} value is if they have different {@code kty} (key type)
* values but are considered to be equivalent alternatives by the application using them.)</p>
*
* <p>The {@code kid} value is a CaSe-SeNsItIvE string, and it is optional. When used with JWS or JWE,
* the {@code kid} value is used to match a JWS or JWE {@code kid} Header Parameter value.</p>
*
* @param kid the JWK {@code kid} value.
* @return the builder for method chaining.
* @throws IllegalArgumentException if the argument is {@code null} or empty.
*/
T setId(String kid) throws IllegalArgumentException;
/**
* Sets the JWK's {@link #setId(String) kid} value to be the Base64URL-encoding of its {@code SHA-256}
* {@link Jwk#thumbprint(HashAlgorithm) thumbprint}. That is, the constructed JWK's {@code kid} value will equal
* <code>jwk.{@link Jwk#thumbprint(HashAlgorithm) thumbprint}({@link Jwks#HASH}.{@link StandardHashAlgorithms#SHA256 SHA256}).{@link JwkThumbprint#toString() toString()}</code>.
*
* <p>This is a convenience method that delegates to {@link #setIdFromThumbprint(HashAlgorithm)} using
* {@link Jwks#HASH}{@code .}{@link StandardHashAlgorithms#SHA256 SHA256}.</p>
*
* @return the builder for method chaining.
*/
T setIdFromThumbprint();
/**
* Sets the JWK's {@link #setId(String) kid} value to be the Base64URL-encoding of its
* {@link Jwk#thumbprint(HashAlgorithm) thumbprint} using the specified {@link HashAlgorithm}. That is, the
* constructed JWK's {@code kid} value will equal
* <code>{@link Jwk#thumbprint(HashAlgorithm) thumbprint}(alg).{@link JwkThumbprint#toString() toString()}.</code>
*
* @param alg the hash algorithm to use to compute the thumbprint.
* @return the builder for method chaining.
* @see StandardHashAlgorithms
*/
T setIdFromThumbprint(HashAlgorithm alg);
/**
* Sets the JWK <a href="https://www.rfc-editor.org/rfc/rfc7517.html#section-4.3">{@code key_ops}
* (Key Operations) Parameter</a> values.
*
* <p>The {@code key_ops} (key operations) parameter identifies the operation(s) for which the key is
* intended to be used. The {@code key_ops} parameter is intended for use cases in which public,
* private, or symmetric keys may be present.</p>
*
* <p>The JWK specification <a href="https://www.rfc-editor.org/rfc/rfc7517.html#section-4.3">defines</a> the
* following values:</p>
*
* <table>
* <caption>JWK Key Operations</caption>
* <thead>
* <tr>
* <th>Value</th>
* <th>Operation</th>
* </tr>
* </thead>
* <tbody>
* <tr>
* <td><b>{@code sign}</b></td>
* <td>compute digital signatures or MAC</td>
* </tr>
* <tr>
* <td><b>{@code verify}</b></td>
* <td>verify digital signatures or MAC</td>
* </tr>
* <tr>
* <td><b>{@code encrypt}</b></td>
* <td>encrypt content</td>
* </tr>
* <tr>
* <td><b>{@code decrypt}</b></td>
* <td>decrypt content and validate decryption, if applicable</td>
* </tr>
* <tr>
* <td><b>{@code wrapKey}</b></td>
* <td>encrypt key</td>
* </tr>
* <tr>
* <td><b>{@code unwrapKey}</b></td>
* <td>decrypt key and validate decryption, if applicable</td>
* </tr>
* <tr>
* <td><b>{@code deriveKey}</b></td>
* <td>derive key</td>
* </tr>
* <tr>
* <td><b>{@code deriveBits}</b></td>
* <td>derive bits not to be used as a key</td>
* </tr>
* </tbody>
* </table>
*
* <p>(Note that {@code key_ops} values intentionally match the {@code KeyUsage} values defined in the
* <a href="https://www.w3.org/TR/WebCryptoAPI/">Web Cryptography API</a> specification.)</p>
*
* <p>Other values <em>MAY</em> be used. For best interoperability with other applications however, it is
* recommended to use only the values above. Each value is a CaSe-SeNsItIvE string. Use of the
* {@code key_ops} member is <em>OPTIONAL</em>, unless the application requires its presence.</p>
*
* <p>Multiple unrelated key operations <em>SHOULD NOT</em> be specified for a key because of the potential
* vulnerabilities associated with using the same key with multiple algorithms. Thus, the combinations
* {@code sign} with {@code verify}, {@code encrypt} with {@code decrypt}, and {@code wrapKey} with
* {@code unwrapKey} are permitted, but other combinations <em>SHOULD NOT</em> be used.</p>
*
* @param ops the JWK {@code key_ops} value set.
* @return the builder for method chaining.
* @throws IllegalArgumentException if {@code ops} is {@code null} or empty.
*/
T setOperations(Set<String> ops) throws IllegalArgumentException;
}

View File

@ -0,0 +1,33 @@
/*
* Copyright (C) 2022 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.security;
/**
* Parses a JWK JSON string and produces its resulting {@link Jwk} instance.
*
* @since JJWT_RELEASE_VERSION
*/
public interface JwkParser {
/**
* Parses the specified JWK JSON string and returns the resulting {@link Jwk} instance.
*
* @param json the json string representing the JWK
* @return the {@link Jwk} instance corresponding to the specified JWK json string.
* @throws KeyException if the json string cannot be represented as a {@link Jwk}.
*/
Jwk<?> parse(String json) throws KeyException;
}

View File

@ -0,0 +1,61 @@
/*
* Copyright (C) 2022 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.security;
import io.jsonwebtoken.io.Deserializer;
import io.jsonwebtoken.lang.Builder;
import java.security.Provider;
import java.util.Map;
/**
* A builder to construct a {@link JwkParser}. Example usage:
* <blockquote><pre>
* Jwk&lt;?&gt; jwk = Jwks.parser()
* .setProvider(aJcaProvider) // optional
* .deserializeJsonWith(deserializer) // optional
* .build()
* .parse(jwkString);</pre></blockquote>
*
* @since JJWT_RELEASE_VERSION
*/
public interface JwkParserBuilder extends Builder<JwkParser> {
/**
* Sets the JCA Provider to use during cryptographic key factory operations, or {@code null} if the
* JCA subsystem preferred provider should be used.
*
* @param provider the JCA Provider to use during cryptographic key factory operations, or {@code null}
* if the JCA subsystem preferred provider should be used.
* @return the builder for method chaining.
*/
JwkParserBuilder setProvider(Provider provider);
/**
* Uses the specified deserializer to convert JSON Strings (UTF-8 byte arrays) into Java Map objects. The
* resulting Maps are then used to construct {@link Jwk} instances.
*
* <p>If this method is not called, JJWT will use whatever deserializer it can find at runtime, checking for the
* presence of well-known implementations such Jackson, Gson, and org.json. If one of these is not found
* in the runtime classpath, an exception will be thrown when the resulting {@link JwkParser}'s
* {@link JwkParser#parse(String) parse(json)} method is called.
*
* @param deserializer the deserializer to use when converting JSON Strings (UTF-8 byte arrays) into Map objects.
* @return the builder for method chaining.
*/
JwkParserBuilder deserializeJsonWith(Deserializer<Map<String, ?>> deserializer);
}

Some files were not shown because too many files have changed in this diff Show More