diff --git a/README.md b/README.md index d138f4f2..1ed0e6e2 100644 --- a/README.md +++ b/README.md @@ -7,10 +7,10 @@ JJWT aims to be the easiest to use and understand library for creating and verifying JSON Web Tokens (JWTs) on the JVM and Android. -JJWT is a pure Java implementation based -exclusively on the [JWT](https://tools.ietf.org/html/rfc7519), -[JWS](https://tools.ietf.org/html/rfc7515), [JWE](https://tools.ietf.org/html/rfc7516), -[JWK](https://tools.ietf.org/html/rfc7517) and [JWA](https://tools.ietf.org/html/rfc7518) RFC specifications and +JJWT is a pure Java implementation based +exclusively on the [JWT](https://tools.ietf.org/html/rfc7519), +[JWS](https://tools.ietf.org/html/rfc7515), [JWE](https://tools.ietf.org/html/rfc7516), +[JWK](https://tools.ietf.org/html/rfc7517) and [JWA](https://tools.ietf.org/html/rfc7518) RFC specifications and open source under the terms of the [Apache 2.0 License](http://www.apache.org/licenses/LICENSE-2.0). The library was created by [Okta's](http://www.okta.com) Senior Architect, [Les Hazlewood](https://github.com/lhazlewood) @@ -18,122 +18,126 @@ and is supported and maintained by a [community](https://github.com/jwtk/jjwt/gr [Okta](https://developer.okta.com/) is a complete authentication and user management API for developers. -We've also added some convenience extensions that are not part of the specification, such as JWS compression and claim +We've also added some convenience extensions that are not part of the specification, such as JWS compression and claim enforcement. ## Table of Contents -* [Features](#features) - * [Currently Unsupported Features](#features-unsupported) -* [Community](#community) - * [Getting Help](#help) - * [Questions](#help-questions) - * [Bugs and Feature Requests](#help-issues) - * [Contributing](#contributing) - * [Pull Requests](#contributing-pull-requests) - * [Help Wanted](#contributing-help-wanted) -* [What is a JSON Web Token?](#overview) -* [Installation](#install) - * [JDK Projects](#install-jdk) - * [Maven](#install-jdk-maven) - * [Gradle](#install-jdk-gradle) - * [Android Projects](#install-android) - * [Dependencies](#install-android-dependencies) - * [Proguard Exclusions](#install-android-proguard) - * [Understanding JJWT Dependencies](#install-understandingdependencies) -* [Quickstart](#quickstart) -* [Signed JWTs](#jws) - * [Signature Algorithm Keys](#jws-key) - * [HMAC-SHA](#jws-key-hmacsha) - * [RSA](#jws-key-rsa) - * [Elliptic Curve](#jws-key-ecdsa) - * [Creating Safe Keys](#jws-key-create) - * [Secret Keys](#jws-key-create-secret) - * [Asymetric Keys](#jws-key-create-asym) - * [Create a JWS](#jws-create) - * [Header](#jws-create-header) - * [Instance](#jws-create-header-instance) - * [Map](#jws-create-header-map) - * [Claims](#jws-create-claims) - * [Standard Claims](#jws-create-claims-standard) - * [Custom Claims](#jws-create-claims-custom) - * [Claims Instance](#jws-create-claims-instance) - * [Claims Map](#jws-create-claims-map) - * [Signing Key](#jws-create-key) - * [SecretKey Formats](#jws-create-key-secret) - * [Signature Algorithm Override](#jws-create-key-algoverride) - * [Compression](#jws-create-compression) - * [Read a JWS](#jws-read) - * [Verification Key](#jws-read-key) - * [Find the Verification Key at Runtime](#jws-read-key-resolver) - * [Claims Assertions](#jws-read-claims) - * [Accounting for Clock Skew](#jws-read-clock) - * [Custom Clock](#jws-read-clock-custom) - * [Decompression](#jws-read-decompression) +- [Features](#features) + - [Currently Unsupported Features](#features-unsupported) +- [Community](#community) + - [Getting Help](#help) + - [Questions](#help-questions) + - [Bugs and Feature Requests](#help-issues) + - [Contributing](#contributing) + - [Pull Requests](#contributing-pull-requests) + - [Help Wanted](#contributing-help-wanted) +- [What is a JSON Web Token?](#overview) +- [Installation](#install) + - [JDK Projects](#install-jdk) + - [Maven](#install-jdk-maven) + - [Gradle](#install-jdk-gradle) + - [Android Projects](#install-android) + - [Dependencies](#install-android-dependencies) + - [Proguard Exclusions](#install-android-proguard) + - [Understanding JJWT Dependencies](#install-understandingdependencies) +- [Quickstart](#quickstart) +- [Signed JWTs](#jws) + - [Signature Algorithm Keys](#jws-key) + - [HMAC-SHA](#jws-key-hmacsha) + - [RSA](#jws-key-rsa) + - [Elliptic Curve](#jws-key-ecdsa) + - [Creating Safe Keys](#jws-key-create) + - [Secret Keys](#jws-key-create-secret) + - [Asymetric Keys](#jws-key-create-asym) + - [Create a JWS](#jws-create) + - [Header](#jws-create-header) + - [Instance](#jws-create-header-instance) + - [Map](#jws-create-header-map) + - [Claims](#jws-create-claims) + - [Standard Claims](#jws-create-claims-standard) + - [Custom Claims](#jws-create-claims-custom) + - [Claims Instance](#jws-create-claims-instance) + - [Claims Map](#jws-create-claims-map) + - [Signing Key](#jws-create-key) + - [SecretKey Formats](#jws-create-key-secret) + - [Signature Algorithm Override](#jws-create-key-algoverride) + - [Compression](#jws-create-compression) + - [Read a JWS](#jws-read) + - [Verification Key](#jws-read-key) + - [Find the Verification Key at Runtime](#jws-read-key-resolver) + - [Claims Assertions](#jws-read-claims) + - [Accounting for Clock Skew](#jws-read-clock) + - [Custom Clock](#jws-read-clock-custom) + - [Decompression](#jws-read-decompression) -* [Compression](#compression) - * [Custom Compression Codec](#compression-custom) -* [JSON Processor](#json) - * [Custom JSON Processor](#json-custom) - * [Jackson ObjectMapper](#json-jackson) - * [Custom Claim Types](#json-jackson-custom-types) - * [Gson](#json-gson) -* [Base64 Support](#base64) - * [Base64 in Security Contexts](#base64-security) - * [Base64 is not Encryption](#base64-not-encryption) - * [Changing Base64 Characters](#base64-changing-characters) - * [Custom Base64 Codec](#base64-custom) +- [Compression](#compression) + - [Custom Compression Codec](#compression-custom) +- [JSON Processor](#json) + - [Custom JSON Processor](#json-custom) + - [Jackson ObjectMapper](#json-jackson) + - [Custom Claim Types](#json-jackson-custom-types) + - [Gson](#json-gson) +- [Base64 Support](#base64) + - [Base64 in Security Contexts](#base64-security) + - [Base64 is not Encryption](#base64-not-encryption) + - [Changing Base64 Characters](#base64-changing-characters) + - [Custom Base64 Codec](#base64-custom) + ## Features - * Fully functional on all JDKs and Android - * Automatic security best practices and assertions - * Easy to learn and read API - * Convenient and readable [fluent](http://en.wikipedia.org/wiki/Fluent_interface) interfaces, great for IDE auto-completion to write code quickly - * Fully RFC specification compliant on all implemented functionality, tested against RFC-specified test vectors - * Stable implementation with enforced 100% test code coverage. Literally every single method, statement and - conditional branch variant in the entire codebase is tested and required to pass on every build. - * Creating, parsing and verifying digitally signed compact JWTs (aka JWSs) with all standard JWS algorithms: - * HS256: HMAC using SHA-256 - * HS384: HMAC using SHA-384 - * HS512: HMAC using SHA-512 - * ES256: ECDSA using P-256 and SHA-256 - * ES384: ECDSA using P-384 and SHA-384 - * ES512: ECDSA using P-521 and SHA-512 - * RS256: RSASSA-PKCS-v1_5 using SHA-256 - * RS384: RSASSA-PKCS-v1_5 using SHA-384 - * RS512: RSASSA-PKCS-v1_5 using SHA-512 - * PS256: RSASSA-PSS using SHA-256 and MGF1 with SHA-2561 - * PS384: RSASSA-PSS using SHA-384 and MGF1 with SHA-3841 - * PS512: RSASSA-PSS using SHA-512 and MGF1 with SHA-5121 - - 1. Requires JDK 11 or a compatible JCA Provider (like BouncyCastle) in the runtime classpath. - * Convenience enhancements beyond the specification such as - * Body compression for any large JWT, not just JWEs - * Claims assertions (requiring specific values) - * Claim POJO marshaling and unmarshaling when using a compatible JSON parser (e.g. Jackson) - * Secure Key generation based on desired JWA algorithms - * and more... - +- Fully functional on all JDKs and Android +- Automatic security best practices and assertions +- Easy to learn and read API +- Convenient and readable [fluent](http://en.wikipedia.org/wiki/Fluent_interface) interfaces, great for IDE auto-completion to write code quickly +- Fully RFC specification compliant on all implemented functionality, tested against RFC-specified test vectors +- Stable implementation with enforced 100% test code coverage. Literally every single method, statement and + conditional branch variant in the entire codebase is tested and required to pass on every build. +- Creating, parsing and verifying digitally signed compact JWTs (aka JWSs) with all standard JWS algorithms: + - HS256: HMAC using SHA-256 + - HS384: HMAC using SHA-384 + - HS512: HMAC using SHA-512 + - ES256: ECDSA using P-256 and SHA-256 + - ES384: ECDSA using P-384 and SHA-384 + - ES512: ECDSA using P-521 and SHA-512 + - RS256: RSASSA-PKCS-v1_5 using SHA-256 + - RS384: RSASSA-PKCS-v1_5 using SHA-384 + - RS512: RSASSA-PKCS-v1_5 using SHA-512 + - PS256: RSASSA-PSS using SHA-256 and MGF1 with SHA-2561 + - PS384: RSASSA-PSS using SHA-384 and MGF1 with SHA-3841 + - PS512: RSASSA-PSS using SHA-512 and MGF1 with SHA-5121 + 1. Requires JDK 11 or a compatible JCA Provider (like BouncyCastle) in the runtime classpath. +- Convenience enhancements beyond the specification such as + - Body compression for any large JWT, not just JWEs + - Claims assertions (requiring specific values) + - Claim POJO marshaling and unmarshaling when using a compatible JSON parser (e.g. Jackson) + - Secure Key generation based on desired JWA algorithms + - and more... + + ### Currently Unsupported Features -* [Non-compact](https://tools.ietf.org/html/rfc7515#section-7.2) serialization and parsing. -* JWE (Encryption for JWT) +- [Non-compact](https://tools.ietf.org/html/rfc7515#section-7.2) serialization and parsing. +- JWE (Encryption for JWT) -These features will be implemented in a future release. Community contributions are welcome! +These features will be implemented in a future release. Community contributions are welcome! + ## Community + ### Getting Help -If you have trouble using JJWT, please first read the documentation on this page before asking questions. We try +If you have trouble using JJWT, please first read the documentation on this page before asking questions. We try very hard to ensure JJWT's documentation is robust, categorized with a table of contents, and up to date for each release. + #### Questions If the documentation or the API JavaDoc isn't sufficient, and you either have usability questions or are confused @@ -141,63 +145,68 @@ about something, please [ask your question here](https://stackoverflow.com/quest After asking your question, you may wish to join our [Slack](https://jwtk.slack.com/messages/CBNACTN3A) or [Gittr](https://gitter.im/jwtk/jjwt) chat rooms, but note that they may not always be attended. You will usually -have a better chance of having your question answered by +have a better chance of having your question answered by [asking your question here](https://stackoverflow.com/questions/ask?tags=jjwt&guided=false). - -If you believe you have found a bug or would like to suggest a feature enhancement, please create a new GitHub issue, + +If you believe you have found a bug or would like to suggest a feature enhancement, please create a new GitHub issue, however: -**Please do not create a GitHub issue to ask a question.** +**Please do not create a GitHub issue to ask a question.** -We use GitHub Issues to track actionable work that requires changes to JJWT's design and/or codebase. If you have a -usability question, instead please -[ask your question here](https://stackoverflow.com/questions/ask?tags=jjwt&guided=false), or try Slack or Gittr as +We use GitHub Issues to track actionable work that requires changes to JJWT's design and/or codebase. If you have a +usability question, instead please +[ask your question here](https://stackoverflow.com/questions/ask?tags=jjwt&guided=false), or try Slack or Gittr as described above. **If a GitHub Issue is created that does not represent actionable work for JJWT's codebase, it will be promptly closed.** + #### Bugs and Feature Requests -If you do not have a usability question and believe you have a legitimate bug or feature request, +If you do not have a usability question and believe you have a legitimate bug or feature request, please do [create a new JJWT issue](https://github.com/jwtk/jjwt/issues/new). -If you feel like you'd like to help fix a bug or implement the new feature yourself, please read the Contributing +If you feel like you'd like to help fix a bug or implement the new feature yourself, please read the Contributing section next before starting any work. + ### Contributing + #### Pull Requests -Simple Pull Requests that fix anything other than JJWT core code (documentation, JavaDoc, typos, test cases, etc) are +Simple Pull Requests that fix anything other than JJWT core code (documentation, JavaDoc, typos, test cases, etc) are always appreciated and have a high likelihood of being merged quickly. Please send them! -However, if you want or feel the need to change JJWT's functionality or core code, please do not issue a pull request -without [creating a new JJWT issue](https://github.com/jwtk/jjwt/issues/new) and discussing your desired +However, if you want or feel the need to change JJWT's functionality or core code, please do not issue a pull request +without [creating a new JJWT issue](https://github.com/jwtk/jjwt/issues/new) and discussing your desired changes **first**, _before you start working on it_. -It would be a shame to reject your earnest and genuinely appreciated pull request if it might not align with the -project's goals, design expectations or planned functionality. We've sadly had to reject large PRs in the past because -they were out of sync with project or design expectations - all because the PR author didn't first check in with +It would be a shame to reject your earnest and genuinely appreciated pull request if it might not align with the +project's goals, design expectations or planned functionality. We've sadly had to reject large PRs in the past because +they were out of sync with project or design expectations - all because the PR author didn't first check in with the team first before working on a solution. So, please [create a new JJWT issue](https://github.com/jwtk/jjwt/issues/new) first to discuss, and then we can see if -(or how) a PR is warranted. Thank you! +(or how) a PR is warranted. Thank you! + #### Help Wanted -If you would like to help, but don't know where to start, please visit the -[Help Wanted Issues](https://github.com/jwtk/jjwt/labels/help%20wanted) page and pick any of the +If you would like to help, but don't know where to start, please visit the +[Help Wanted Issues](https://github.com/jwtk/jjwt/labels/help%20wanted) page and pick any of the ones there, and we'll be happy to discuss and answer questions in the issue comments. -If any of those don't appeal to you, no worries! Any help you would like to offer would be +If any of those don't appeal to you, no worries! Any help you would like to offer would be appreciated based on the above caveats concerning [contributing pull reqeuests](#contributing-pull-requests). Feel free to discuss or ask questions first if you're not sure. :) + ## What is a JSON Web Token? Don't know what a JSON Web Token is? Read on. Otherwise, jump on down to the [Installation](#Installation) section. @@ -205,7 +214,7 @@ Don't know what a JSON Web Token is? Read on. Otherwise, jump on down to the [In JWT is a means of transmitting information between two parties in a compact, verifiable form. The bits of information encoded in the body of a JWT are called `claims`. The expanded form of the JWT is in a JSON format, so each `claim` is a key in the JSON object. - + JWTs can be cryptographically signed (making it a [JWS](https://tools.ietf.org/html/rfc7515)) or encrypted (making it a [JWE](https://tools.ietf.org/html/rfc7516)). This adds a powerful layer of verifiability to the user of JWTs. The receiver has a high degree of confidence that the JWT has not been tampered with by verifying the signature, for instance. @@ -216,15 +225,16 @@ The compact representation of a signed JWT is a string that has three parts, eac eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJKb2UifQ.ipevRNuRP6HflG8cFKnmUPtypruRC4fb1DWtoLL62SY ``` -Each part is [Base64URL](https://en.wikipedia.org/wiki/Base64)-encoded. The first part is the header, which at a -minimum needs to specify the algorithm used to sign the JWT. The second part is the body. This part has all -the claims of this JWT encoded in it. The final part is the signature. It's computed by passing a combination of +Each part is [Base64URL](https://en.wikipedia.org/wiki/Base64)-encoded. The first part is the header, which at a +minimum needs to specify the algorithm used to sign the JWT. The second part is the body. This part has all +the claims of this JWT encoded in it. The final part is the signature. It's computed by passing a combination of the header and body through the algorithm specified in the header. - -If you pass the first two parts through a base 64 url decoder, you'll get the following (formatting added for + +If you pass the first two parts through a base 64 url decoder, you'll get the following (formatting added for clarity): `header` + ``` { "alg": "HS256" @@ -232,34 +242,38 @@ clarity): ``` `body` + ``` { "sub": "Joe" } ``` -In this case, the information we have is that the HMAC using SHA-256 algorithm was used to sign the JWT. And, the +In this case, the information we have is that the HMAC using SHA-256 algorithm was used to sign the JWT. And, the body has a single claim, `sub` with value `Joe`. -There are a number of standard claims, called [Registered Claims](https://tools.ietf.org/html/rfc7519#section-4.1), +There are a number of standard claims, called [Registered Claims](https://tools.ietf.org/html/rfc7519#section-4.1), in the specification and `sub` (for subject) is one of them. To compute the signature, you need a secret key to sign it. We'll cover keys and algorithms later. + ## Installation Use your favorite Maven-compatible build tool to pull the dependencies from Maven Central. -The dependencies could differ slightly if you are working with a [JDK project](#install-jdk) or an +The dependencies could differ slightly if you are working with a [JDK project](#install-jdk) or an [Android project](#install-android). + ### JDK Projects If you're building a (non-Android) JDK project, you will want to define the following dependencies: + #### Maven ```xml @@ -280,7 +294,7 @@ If you're building a (non-Android) JDK project, you will want to define the foll 0.11.2 runtime - + ## Compression **The JWT specification only standardizes this feature for JWEs (Encrypted JWTs) and not JWSs (Signed JWTs), however -JJWT supports both**. If you are positive that a JWS you create with JJWT will _also_ be parsed with JJWT, you -can use this feature with JWSs, otherwise it is best to only use it for JWEs. +JJWT supports both**. If you are positive that a JWS you create with JJWT will _also_ be parsed with JJWT, you +can use this feature with JWSs, otherwise it is best to only use it for JWEs. -If a JWT's Claims set is sufficiently large - that is, it contains a lot of name/value pairs, or individual values are +If a JWT's Claims set is sufficiently large - that is, it contains a lot of name/value pairs, or individual values are very large or verbose - you can reduce the size of the created JWS by compressing the claims body. -This might be important to you if the resulting JWS is used in a URL for example, since URLs are best kept under -4096 characters due to browser, user mail agent, or HTTP gateway compatibility issues. Smaller JWTs also help reduce +This might be important to you if the resulting JWS is used in a URL for example, since URLs are best kept under +4096 characters due to browser, user mail agent, or HTTP gateway compatibility issues. Smaller JWTs also help reduce bandwidth utilization, which may or may not be important depending on your application's volume or needs. -If you want to compress your JWT, you can use the `JwtBuilder`'s `compressWith(CompressionAlgorithm)` method. For +If you want to compress your JWT, you can use the `JwtBuilder`'s `compressWith(CompressionAlgorithm)` method. For example: ```java Jwts.builder() - + .compressWith(CompressionCodecs.DEFLATE) // or CompressionCodecs.GZIP - + // .. etc ... ``` -If you use the `DEFLATE` or `GZIP` Compression Codecs - that's it, you're done. You don't have to do anything during +If you use the `DEFLATE` or `GZIP` Compression Codecs - that's it, you're done. You don't have to do anything during parsing or configure the `JwtParser` for compression - JJWT will automatically decompress the body as expected. + ### Custom Compression Codec If however, you used your own custom compression codec when creating the JWT (via `JwtBuilder` `compressWith`), then -you need to supply the codec to the `JwtParserBuilder` using the `setCompressionCodecResolver` method. For example: +you need to supply the codec to the `JwtParserBuilder` using the `setCompressionCodecResolver` method. For example: ```java CompressionCodecResolver ccr = new MyCompressionCodecResolver(); @@ -1225,63 +1281,63 @@ CompressionCodecResolver ccr = new MyCompressionCodecResolver(); Jwts.parserBuilder() .setCompressionCodecResolver(ccr) // <---- - + // .. etc ... ``` Typically a `CompressionCodecResolver` implementation will inspect the `zip` header to find out what algorithm was -used and then return a codec instance that supports that algorithm. For example: +used and then return a codec instance that supports that algorithm. For example: ```java public class MyCompressionCodecResolver implements CompressionCodecResolver { - + @Override public CompressionCodec resolveCompressionCodec(Header header) throws CompressionException { - + String alg = header.getCompressionAlgorithm(); - + CompressionCodec codec = getCompressionCodec(alg); //implement me - + return codec; } } ``` + ## JSON Support -A `JwtBuilder` will serialize the `Header` and `Claims` maps (and potentially any Java objects they -contain) to JSON with a `Serializer>` instance. Similarly, a `JwtParser` will +A `JwtBuilder` will serialize the `Header` and `Claims` maps (and potentially any Java objects they +contain) to JSON with a `Serializer>` instance. Similarly, a `JwtParser` will deserialize JSON into the `Header` and `Claims` using a `Deserializer>` instance. -If you don't explicitly configure a `JwtBuilder`'s `Serializer` or a `JwtParserBuilder`'s `Deserializer`, JJWT will +If you don't explicitly configure a `JwtBuilder`'s `Serializer` or a `JwtParserBuilder`'s `Deserializer`, JJWT will automatically attempt to discover and use the following JSON implementations if found in the runtime classpath. They are checked in order, and the first one found is used: -1. Jackson: This will automatically be used if you specify `io.jsonwebtoken:jjwt-jackson` as a project runtime - dependency. Jackson supports POJOs as claims with full marshaling/unmarshaling as necessary. - +1. Jackson: This will automatically be used if you specify `io.jsonwebtoken:jjwt-jackson` as a project runtime + dependency. Jackson supports POJOs as claims with full marshaling/unmarshaling as necessary. 2. Gson: This will automatically be used if you specify `io.jsonwebtoken:jjwt-gson` as a project runtime dependency. - Gson also supports POJOs as claims with full marshaling/unmarshaling as necessary. - -3. JSON-Java (`org.json`): This will be used automatically if you specify `io.jsonwebtoken:jjwt-orgjson` as a + Gson also supports POJOs as claims with full marshaling/unmarshaling as necessary. +3. JSON-Java (`org.json`): This will be used automatically if you specify `io.jsonwebtoken:jjwt-orgjson` as a project runtime dependency. - - **NOTE:** `org.json` APIs are natively enabled in Android environments so this is the recommended JSON processor for - Android applications _unless_ you want to use POJOs as claims. The `org.json` library supports simple - Object-to-JSON marshaling, but it *does not* support JSON-to-Object unmarshalling. -**If you want to use POJOs as claim values, use either the `io.jsonwebtoken:jjwt-jackson` or -`io.jsonwebtoken:jjwt-gson` dependency** (or implement your own Serializer and Deserializer if desired). **But beware**, -Jackson will force a sizable (> 1 MB) dependency to an Android application thus increasing the app download size for + **NOTE:** `org.json` APIs are natively enabled in Android environments so this is the recommended JSON processor for + Android applications _unless_ you want to use POJOs as claims. The `org.json` library supports simple + Object-to-JSON marshaling, but it _does not_ support JSON-to-Object unmarshalling. + +**If you want to use POJOs as claim values, use either the `io.jsonwebtoken:jjwt-jackson` or +`io.jsonwebtoken:jjwt-gson` dependency** (or implement your own Serializer and Deserializer if desired). **But beware**, +Jackson will force a sizable (> 1 MB) dependency to an Android application thus increasing the app download size for mobile users. + ### Custom JSON Processor -If you don't want to use JJWT's runtime dependency approach, or just want to customize how JSON serialization and +If you don't want to use JJWT's runtime dependency approach, or just want to customize how JSON serialization and deserialization works, you can implement the `Serializer` and `Deserializer` interfaces and specify instances of -them on the `JwtBuilder` and `JwtParserBuilder` respectively. For example: +them on the `JwtBuilder` and `JwtParserBuilder` respectively. For example: When creating a JWT: @@ -1291,7 +1347,7 @@ Serializer> serializer = getMySerializer(); //implement me Jwts.builder() .serializeToJsonWith(serializer) - + // ... etc ... ``` @@ -1303,26 +1359,27 @@ Deserializer> deserializer = getMyDeserializer(); //implement me Jwts.parserBuilder() .deserializeJsonWith(deserializer) - + // ... etc ... ``` + ### Jackson JSON Processor If you want to use Jackson for JSON processing, just including the `io.jsonwebtoken:jjwt-jackson` dependency as a runtime dependency is all that is necessary in most projects, since Gradle and Maven will automatically pull in the necessary Jackson dependencies as well. -After including this dependency, JJWT will automatically find the Jackson implementation on the runtime classpath and -use it internally for JSON parsing. There is nothing else you need to do - JJWT will automatically create a new +After including this dependency, JJWT will automatically find the Jackson implementation on the runtime classpath and +use it internally for JSON parsing. There is nothing else you need to do - JJWT will automatically create a new Jackson ObjectMapper for its needs as required. -However, if you have an application-wide Jackson `ObjectMapper` (as is typically recommended for most applications), +However, if you have an application-wide Jackson `ObjectMapper` (as is typically recommended for most applications), you can configure JJWT to use your own `ObjectMapper` instead. -You do this by declaring the `io.jsonwebtoken:jjwt-jackson` dependency with **compile** scope (not runtime -scope which is the typical JJWT default). That is: +You do this by declaring the `io.jsonwebtoken:jjwt-jackson` dependency with **compile** scope (not runtime +scope which is the typical JJWT default). That is: **Maven** @@ -1340,6 +1397,8 @@ scope which is the typical JJWT default). That is: ```groovy dependencies { compile 'io.jsonwebtoken:jjwt-jackson:0.11.2' + // if you are using gradle 7+, you need to use the implemetation dependency instead. + implementation 'io.jsonwebtoken:jjwt-jackson:0.11.2' } ``` @@ -1351,7 +1410,7 @@ ObjectMapper objectMapper = getMyObjectMapper(); //implement me String jws = Jwts.builder() .serializeToJsonWith(new JacksonSerializer(objectMapper)) - + // ... etc ... ``` @@ -1363,28 +1422,29 @@ ObjectMapper objectMapper = getMyObjectMapper(); //implement me Jwts.parserBuilder() .deserializeJsonWith(new JacksonDeserializer(objectMapper)) - + // ... etc ... ``` + #### Parsing of Custom Claim Types -By default JJWT will only convert simple claim types: String, Date, Long, Integer, Short and Byte. If you need to deserialize other types you can configure the `JacksonDeserializer` by passing a `Map` of claim names to types in through a constructor. For example: +By default JJWT will only convert simple claim types: String, Date, Long, Integer, Short and Byte. If you need to deserialize other types you can configure the `JacksonDeserializer` by passing a `Map` of claim names to types in through a constructor. For example: ```java new JacksonDeserializer(Maps.of("user", User.class).build()) ``` -This would trigger the value in the `user` claim to be deserialized into the custom type of `User`. Given the claims body of: +This would trigger the value in the `user` claim to be deserialized into the custom type of `User`. Given the claims body of: ```json { - "issuer": "https://example.com/issuer", - "user": { - "firstName": "Jill", - "lastName": "Coder" - } + "issuer": "https://example.com/issuer", + "user": { + "firstName": "Jill", + "lastName": "Coder" + } } ``` @@ -1400,24 +1460,25 @@ Jwts.parserBuilder() .parseClaimsJwt(aJwtString) .getBody() - + .get("user", User.class) // <----- ``` -**NOTE:** Using this constructor is mutually exclusive with the `JacksonDeserializer(ObjectMapper)` constructor -[described above](#json-jackson). This is because JJWT configures an `ObjectMapper` directly and could have negative -consequences for a shared `ObjectMapper` instance. This should work for most applications, if you need a more advanced +**NOTE:** Using this constructor is mutually exclusive with the `JacksonDeserializer(ObjectMapper)` constructor +[described above](#json-jackson). This is because JJWT configures an `ObjectMapper` directly and could have negative +consequences for a shared `ObjectMapper` instance. This should work for most applications, if you need a more advanced parsing options, [configure the mapper directly](#json-jackson). + ### Gson JSON Processor If you want to use Gson for JSON processing, just including the `io.jsonwebtoken:jjwt-gson` dependency as a runtime dependency is all that is necessary in most projects, since Gradle and Maven will automatically pull in the necessary Gson dependencies as well. -After including this dependency, JJWT will automatically find the Gson implementation on the runtime classpath and -use it internally for JSON parsing. There is nothing else you need to do - just declaring the dependency is +After including this dependency, JJWT will automatically find the Gson implementation on the runtime classpath and +use it internally for JSON parsing. There is nothing else you need to do - just declaring the dependency is all that is required, no code or config is necessary. If you're curious, JJWT will automatically create an internal default Gson instance for its own needs as follows: @@ -1426,11 +1487,11 @@ If you're curious, JJWT will automatically create an internal default Gson insta new GsonBuilder().disableHtmlEscaping().create(); ``` -However, if you prefer to use a different Gson instance instead of JJWT's default, you can configure JJWT to use your -own. +However, if you prefer to use a different Gson instance instead of JJWT's default, you can configure JJWT to use your +own. -You do this by declaring the `io.jsonwebtoken:jjwt-gson` dependency with **compile** scope (not runtime -scope which is the typical JJWT default). That is: +You do this by declaring the `io.jsonwebtoken:jjwt-gson` dependency with **compile** scope (not runtime +scope which is the typical JJWT default). That is: **Maven** @@ -1448,6 +1509,8 @@ scope which is the typical JJWT default). That is: ```groovy dependencies { compile 'io.jsonwebtoken:jjwt-gson:0.11.2' + // if you are using gradle 7+, you need to use the implemetation dependency instead. + implementation 'io.jsonwebtoken:jjwt-gson:0.11.2' } ``` @@ -1460,7 +1523,7 @@ Gson gson = getGson(); //implement me String jws = Jwts.builder() .serializeToJsonWith(new GsonSerializer(gson)) - + // ... etc ... ``` @@ -1472,68 +1535,72 @@ Gson gson = getGson(); //implement me Jwts.parser() .deserializeJsonWith(new GsonDeserializer(gson)) - + // ... etc ... ``` + ## Base64 Support -JJWT uses a very fast pure-Java [Base64](https://tools.ietf.org/html/rfc4648) codec for Base64 and +JJWT uses a very fast pure-Java [Base64](https://tools.ietf.org/html/rfc4648) codec for Base64 and Base64Url encoding and decoding that is guaranteed to work deterministically in all JDK and Android environments. -You can access JJWT's encoders and decoders using the `io.jsonwebtoken.io.Encoders` and `io.jsonwebtoken.io.Decoders` +You can access JJWT's encoders and decoders using the `io.jsonwebtoken.io.Encoders` and `io.jsonwebtoken.io.Decoders` utility classes. `io.jsonwebtoken.io.Encoders`: -* `BASE64` is an RFC 4648 [Base64](https://tools.ietf.org/html/rfc4648#section-4) encoder -* `BASE64URL` is an RFC 4648 [Base64URL](https://tools.ietf.org/html/rfc4648#section-5) encoder +- `BASE64` is an RFC 4648 [Base64](https://tools.ietf.org/html/rfc4648#section-4) encoder +- `BASE64URL` is an RFC 4648 [Base64URL](https://tools.ietf.org/html/rfc4648#section-5) encoder `io.jsonwebtoken.io.Decoders`: -* `BASE64` is an RFC 4648 [Base64](https://tools.ietf.org/html/rfc4648#section-4) decoder -* `BASE64URL` is an RFC 4648 [Base64URL](https://tools.ietf.org/html/rfc4648#section-5) decoder +- `BASE64` is an RFC 4648 [Base64](https://tools.ietf.org/html/rfc4648#section-4) decoder +- `BASE64URL` is an RFC 4648 [Base64URL](https://tools.ietf.org/html/rfc4648#section-5) decoder + ### Understanding Base64 in Security Contexts All cryptographic operations, like encryption and message digest calculations, result in binary data - raw byte arrays. Because raw byte arrays cannot be represented natively in JSON, the JWT -specifications employ the Base64URL encoding scheme to represent these raw byte values in JSON documents or compound +specifications employ the Base64URL encoding scheme to represent these raw byte values in JSON documents or compound structures like a JWT. -This means that the Base64 and Base64URL algorithms take a raw byte array and converts the bytes into a string suitable -to use in text documents and protocols like HTTP. These algorithms can also convert these strings back +This means that the Base64 and Base64URL algorithms take a raw byte array and converts the bytes into a string suitable +to use in text documents and protocols like HTTP. These algorithms can also convert these strings back into the original raw byte arrays for decryption or signature verification as necessary. -That's nice and convenient, but there are two very important properties of Base64 (and Base64URL) text strings that +That's nice and convenient, but there are two very important properties of Base64 (and Base64URL) text strings that are critical to remember when they are used in security scenarios like with JWTs: -* [Base64 is not encryption](#base64-not-encryption) -* [Changing Base64 characters](#base64-changing-characters) **does not automatically invalidate data**. +- [Base64 is not encryption](#base64-not-encryption) +- [Changing Base64 characters](#base64-changing-characters) **does not automatically invalidate data**. -#### Base64 is not encryption - -Base64-encoded text is _not_ encrypted. -While a byte array representation can be converted to text with the Base64 algorithms, -anyone in the world can take Base64-encoded text, decode it with any standard Base64 decoder, and obtain the -underlying raw byte array data. No key or secret is required to decode Base64 text - anyone can do it. +#### Base64 is not encryption + +Base64-encoded text is _not_ encrypted. + +While a byte array representation can be converted to text with the Base64 algorithms, +anyone in the world can take Base64-encoded text, decode it with any standard Base64 decoder, and obtain the +underlying raw byte array data. No key or secret is required to decode Base64 text - anyone can do it. Based on this, when encoding sensitive byte data with Base64 - like a shared or private key - **the resulting string NOT is safe to expose publicly**. A base64-encoded key is still sensitive information and must -be kept as secret and as safe as the original thing you got the bytes from (e.g. a Java `PrivateKey` or `SecretKey` +be kept as secret and as safe as the original thing you got the bytes from (e.g. a Java `PrivateKey` or `SecretKey` instance). -After Base64-encoding data into a string, it is possible to then encrypt the string to keep it safe from prying -eyes if desired, but this is different. Encryption is not encoding. They are separate concepts. +After Base64-encoding data into a string, it is possible to then encrypt the string to keep it safe from prying +eyes if desired, but this is different. Encryption is not encoding. They are separate concepts. + #### Changing Base64 Characters In an effort to see if signatures or encryption is truly validated correctly, some try to edit a JWT @@ -1545,57 +1612,60 @@ _But this doesn't always work. Changing base64 characters is an invalid test_. Why? -Because of the way the Base64 algorithm works, there are multiple Base64 strings that can represent the same raw byte +Because of the way the Base64 algorithm works, there are multiple Base64 strings that can represent the same raw byte array. -Going into the details of the Base64 algorithm is out of scope for this documentation, but there are many good +Going into the details of the Base64 algorithm is out of scope for this documentation, but there are many good Stackoverflow [answers](https://stackoverflow.com/questions/33663113/multiple-strings-base64-decoded-to-same-byte-array?noredirect=1&lq=1) and [JJWT issue comments](https://github.com/jwtk/jjwt/issues/211#issuecomment-283076269) that explain this in detail. Here's one [good answer](https://stackoverflow.com/questions/29941270/why-do-base64-decode-produce-same-byte-array-for-different-strings): -> Remember that Base64 encodes each 8 bit entity into 6 bit chars. The resulting string then needs exactly -> 11 * 8 / 6 bytes, or 14 2/3 chars. But you can't write partial characters. Only the first 4 bits (or 2/3 of the +> Remember that Base64 encodes each 8 bit entity into 6 bit chars. The resulting string then needs exactly +> 11 \* 8 / 6 bytes, or 14 2/3 chars. But you can't write partial characters. Only the first 4 bits (or 2/3 of the > last char) are significant. The last two bits are not decoded. Thus all of: > > dGVzdCBzdHJpbmo > dGVzdCBzdHJpbmp > dGVzdCBzdHJpbmq > dGVzdCBzdHJpbmr +> > All decode to the same 11 bytes (116, 101, 115, 116, 32, 115, 116, 114, 105, 110, 106). -As you can see by the above 4 examples, they all decode to the same exact 11 bytes. So just changing one or two +As you can see by the above 4 examples, they all decode to the same exact 11 bytes. So just changing one or two characters at the end of a Base64 string may not work and can often result in an invalid test. + ##### Adding Invalid Characters -JJWT's default Base64/Base64URL decoders automatically ignore illegal Base64 characters located in the beginning and -end of an encoded string. Therefore prepending or appending invalid characters like `{` or `]` or similar will also -not fail JJWT's signature checks either. Why? +JJWT's default Base64/Base64URL decoders automatically ignore illegal Base64 characters located in the beginning and +end of an encoded string. Therefore prepending or appending invalid characters like `{` or `]` or similar will also +not fail JJWT's signature checks either. Why? -Because such edits - whether changing a trailing character or two, or appending invalid characters - do not actually -change the _real_ signature, which in cryptographic contexts, is always a byte array. Instead, tests like these +Because such edits - whether changing a trailing character or two, or appending invalid characters - do not actually +change the _real_ signature, which in cryptographic contexts, is always a byte array. Instead, tests like these change a text encoding of the byte array, and as we covered above, they are different things. So JJWT 'cares' more about the real byte array and less about its text encoding because that is what actually matters -in cryptographic operations. In this sense, JJWT follows the [Robustness Principle](https://en.wikipedia.org/wiki/Robustness_principle) -in being _slightly_ lenient on what is accepted per the rules of Base64, but if anything in the real underlying +in cryptographic operations. In this sense, JJWT follows the [Robustness Principle](https://en.wikipedia.org/wiki/Robustness_principle) +in being _slightly_ lenient on what is accepted per the rules of Base64, but if anything in the real underlying byte array is changed, then yes, JJWT's cryptographic assertions will definitely fail. -To help understand JJWT's approach, we have to remember why signatures exist. From our documentation above on +To help understand JJWT's approach, we have to remember why signatures exist. From our documentation above on [signing JWTs](#jws): -> * guarantees it was created by someone we know (it is authentic), as well as -> * guarantees that no-one has manipulated or changed it after it was created (its integrity is maintained). +> - guarantees it was created by someone we know (it is authentic), as well as +> - guarantees that no-one has manipulated or changed it after it was created (its integrity is maintained). -Just prepending or appending invalid text to try to 'trick' the algorithm doesn't change the integrity of the -underlying claims or signature byte arrays, nor the authenticity of the claims byte array, because those byte +Just prepending or appending invalid text to try to 'trick' the algorithm doesn't change the integrity of the +underlying claims or signature byte arrays, nor the authenticity of the claims byte array, because those byte arrays are still obtained intact. -Please see [JJWT Issue #518](https://github.com/jwtk/jjwt/issues/518) and its referenced issues and links for more +Please see [JJWT Issue #518](https://github.com/jwtk/jjwt/issues/518) and its referenced issues and links for more information. + ### Custom Base64 If for some reason you want to specify your own Base64Url encoder and decoder, you can use the `JwtBuilder` @@ -1607,7 +1677,7 @@ Encoder base64UrlEncoder = getMyBase64UrlEncoder(); //implement String jws = Jwts.builder() .base64UrlEncodeWith(base64UrlEncoder) - + // ... etc ... ``` @@ -1619,7 +1689,7 @@ Decoder base64UrlDecoder = getMyBase64UrlDecoder(); //implement Jwts.parserBuilder() .base64UrlDecodeWith(base64UrlEncoder) - + // ... etc ... ``` @@ -1637,6 +1707,7 @@ Jwts.parserBuilder() Maintained by Les Hazlewood & [Okta](https://okta.com/) + ## License This project is open-source via the [Apache 2.0 License](http://www.apache.org/licenses/LICENSE-2.0).