diff --git a/api/src/main/java/io/jsonwebtoken/Claims.java b/api/src/main/java/io/jsonwebtoken/Claims.java index 4d43d1cf5..8854232ce 100644 --- a/api/src/main/java/io/jsonwebtoken/Claims.java +++ b/api/src/main/java/io/jsonwebtoken/Claims.java @@ -15,6 +15,8 @@ */ package io.jsonwebtoken; +import io.jsonwebtoken.io.Reader; + import java.util.Date; import java.util.Map; import java.util.Set; @@ -147,10 +149,9 @@ public interface Claims extends Map, Identifiable { * Returns the JWTs claim ({@code claimName}) value as a type {@code requiredType}, or {@code null} if not present. * *

JJWT only converts simple String, Date, Long, Integer, Short and Byte types automatically. Anything more - * complex is expected to be already converted to your desired type by the JSON - * {@link io.jsonwebtoken.io.Deserializer Deserializer} implementation. You may specify a custom Deserializer for a - * JwtParser with the desired conversion configuration via the - * {@link JwtParserBuilder#deserializer deserializer} method. + * complex is expected to be already converted to your desired type by the JSON parser. You may specify a custom + * JSON {@link io.jsonwebtoken.io.Reader Reader} with the desired configuration via the + * {@link JwtParserBuilder#jsonReader(Reader)} method. * See custom JSON processor for more * information. If using Jackson, you can specify custom claim POJO types as described in * custom claim types. diff --git a/api/src/main/java/io/jsonwebtoken/JwtBuilder.java b/api/src/main/java/io/jsonwebtoken/JwtBuilder.java index 3cd3986f4..fce9642d3 100644 --- a/api/src/main/java/io/jsonwebtoken/JwtBuilder.java +++ b/api/src/main/java/io/jsonwebtoken/JwtBuilder.java @@ -846,6 +846,7 @@ public interface JwtBuilder extends ClaimsMutator { * @deprecated since JJWT_RELEASE_VERSION in favor of the more modern builder-style * {@link #encoder(Encoder)} method. */ + @SuppressWarnings("DeprecatedIsStillUsed") @Deprecated JwtBuilder base64UrlEncodeWith(Encoder base64UrlEncoder); @@ -888,6 +889,7 @@ public interface JwtBuilder extends ClaimsMutator { * @since 0.10.0 * @deprecated since JJWT_RELEASE_VERSION in favor of {@link #jsonWriter(Writer)} */ + @SuppressWarnings("DeprecatedIsStillUsed") @Deprecated JwtBuilder serializeToJsonWith(Serializer> serializer); @@ -905,20 +907,6 @@ public interface JwtBuilder extends ClaimsMutator { */ JwtBuilder jsonWriter(Writer> writer); - /** - * Perform Map-to-JSON serialization with the specified Serializer. This is used by the builder to convert - * JWT/JWS/JWE headers and Claims Maps to JSON strings as required by the JWT specification. - * - *

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 - * in the runtime classpath, an exception will be thrown when the {@link #compact()} method is invoked.

- * - * @param serializer the serializer to use when converting Map objects to JSON strings. - * @return the builder for method chaining. - * @since JJWT_RELEASE_VERSION - */ - JwtBuilder serializer(Serializer> serializer); - /** * Actually builds the JWT and serializes it to a compact, URL-safe string according to the * JWT Compact Serialization diff --git a/api/src/main/java/io/jsonwebtoken/JwtParserBuilder.java b/api/src/main/java/io/jsonwebtoken/JwtParserBuilder.java index f49871630..c34bcebec 100644 --- a/api/src/main/java/io/jsonwebtoken/JwtParserBuilder.java +++ b/api/src/main/java/io/jsonwebtoken/JwtParserBuilder.java @@ -18,6 +18,7 @@ import io.jsonwebtoken.io.CompressionAlgorithm; import io.jsonwebtoken.io.Decoder; import io.jsonwebtoken.io.Deserializer; +import io.jsonwebtoken.io.Reader; import io.jsonwebtoken.lang.Builder; import io.jsonwebtoken.security.AeadAlgorithm; import io.jsonwebtoken.security.KeyAlgorithm; @@ -710,7 +711,7 @@ public interface JwtParserBuilder extends Builder { * @param base64UrlDecoder the decoder to use when Base64Url-decoding * @return the parser builder for method chaining. */ - JwtParserBuilder decoder(Decoder base64UrlDecoder); + JwtParserBuilder decoder(Decoder base64UrlDecoder); /** * Uses the specified deserializer to convert JSON Strings (UTF-8 byte arrays) into Java Map objects. This is @@ -724,26 +725,27 @@ public interface JwtParserBuilder extends Builder { * * @param deserializer the deserializer to use when converting JSON Strings (UTF-8 byte arrays) into Map objects. * @return the builder for method chaining. - * @deprecated since JJWT_RELEASE_VERSION in favor of the shorter and more modern builder-style named - * {@link #deserializer(Deserializer)}. This method will be removed before the JJWT 1.0 release. + * @deprecated since JJWT_RELEASE_VERSION in favor of {@link #jsonReader(Reader)}. + * This method will be removed before the JJWT 1.0 release. */ @Deprecated JwtParserBuilder deserializeJsonWith(Deserializer> deserializer); /** - * Uses the specified deserializer to convert JSON Strings (UTF-8 byte arrays) into Java Map objects. This is - * used by the parser after Base64Url-decoding to convert JWT/JWS/JWT JSON headers and claims into Java Map - * objects. + * Uses the specified JSON {@link Reader} to deserialize JSON (UTF-8 byte streams) into Java Map objects. This is + * used by the parser after Base64Url-decoding to convert JWT/JWS/JWT headers and Claims into Java Map + * instances. * - *

If this method is not called, JJWT will use whatever deserializer it can find at runtime, checking for the + *

If this method is not called, JJWT will use whatever Reader 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 one of the various {@code parse}* methods is * invoked.

* - * @param deserializer the deserializer to use when converting JSON Strings (UTF-8 byte arrays) into Map objects. + * @param reader the reader to use to deserialize JSON (UTF-8 byte streams) into Map instances. * @return the builder for method chaining. + * @since JJWT_RELEASE_VERSION */ - JwtParserBuilder deserializer(Deserializer> deserializer); + JwtParserBuilder jsonReader(Reader> reader); /** * Returns an immutable/thread-safe {@link JwtParser} created from the configuration from this JwtParserBuilder. diff --git a/api/src/main/java/io/jsonwebtoken/io/Base64.java b/api/src/main/java/io/jsonwebtoken/io/Base64.java index c03d00a9b..467fbc540 100644 --- a/api/src/main/java/io/jsonwebtoken/io/Base64.java +++ b/api/src/main/java/io/jsonwebtoken/io/Base64.java @@ -224,20 +224,21 @@ private int ctoi(char c) { } /** - * Decodes a BASE64 encoded char array that is known to be reasonably well formatted. The preconditions are:
- * + The array must have a line length of 76 chars OR no line separators at all (one line).
+ * Decodes a BASE64-encoded {@code CharSequence} that is known to be reasonably well formatted. The preconditions + * are:
+ * + The sequence must have a line length of 76 chars OR no line separators at all (one line).
* + Line separator must be "\r\n", as specified in RFC 2045 - * + The array must not contain illegal characters within the encoded string
- * + The array CAN have illegal characters at the beginning and end, those will be dealt with appropriately.
+ * + The sequence must not contain illegal characters within the encoded string
+ * + The sequence CAN have illegal characters at the beginning and end, those will be dealt with appropriately.
* - * @param sArr The source array. Length 0 will return an empty array. null will throw an exception. + * @param seq The source sequence. Length 0 will return an empty array. null will throw an exception. * @return The decoded array of bytes. May be of length 0. * @throws DecodingException on illegal input */ - final byte[] decodeFast(char[] sArr) throws DecodingException { + byte[] decodeFast(CharSequence seq) throws DecodingException { // Check special case - int sLen = sArr != null ? sArr.length : 0; + int sLen = seq != null ? seq.length() : 0; if (sLen == 0) { return new byte[0]; } @@ -245,19 +246,19 @@ final byte[] decodeFast(char[] sArr) throws DecodingException { int sIx = 0, eIx = sLen - 1; // Start and end index after trimming. // Trim illegal chars from start - while (sIx < eIx && IALPHABET[sArr[sIx]] < 0) { + while (sIx < eIx && IALPHABET[seq.charAt(sIx)] < 0) { sIx++; } // Trim illegal chars from end - while (eIx > 0 && IALPHABET[sArr[eIx]] < 0) { + while (eIx > 0 && IALPHABET[seq.charAt(eIx)] < 0) { eIx--; } // get the padding count (=) (0, 1 or 2) - int pad = sArr[eIx] == '=' ? (sArr[eIx - 1] == '=' ? 2 : 1) : 0; // Count '=' at end. + int pad = seq.charAt(eIx) == '=' ? (seq.charAt(eIx - 1) == '=' ? 2 : 1) : 0; // Count '=' at end. int cCnt = eIx - sIx + 1; // Content count including possible separators - int sepCnt = sLen > 76 ? (sArr[76] == '\r' ? cCnt / 78 : 0) << 1 : 0; + int sepCnt = sLen > 76 ? (seq.charAt(76) == '\r' ? cCnt / 78 : 0) << 1 : 0; int len = ((cCnt - sepCnt) * 6 >> 3) - pad; // The number of decoded bytes byte[] dArr = new byte[len]; // Preallocate byte[] of exact length @@ -267,7 +268,7 @@ final byte[] decodeFast(char[] sArr) throws DecodingException { for (int cc = 0, eLen = (len / 3) * 3; d < eLen; ) { // Assemble three bytes into an int from four "valid" characters. - int i = ctoi(sArr[sIx++]) << 18 | ctoi(sArr[sIx++]) << 12 | ctoi(sArr[sIx++]) << 6 | ctoi(sArr[sIx++]); + int i = ctoi(seq.charAt(sIx++)) << 18 | ctoi(seq.charAt(sIx++)) << 12 | ctoi(seq.charAt(sIx++)) << 6 | ctoi(seq.charAt(sIx++)); // Add the bytes dArr[d++] = (byte) (i >> 16); @@ -285,7 +286,7 @@ final byte[] decodeFast(char[] sArr) throws DecodingException { // Decode last 1-3 bytes (incl '=') into 1-3 bytes int i = 0; for (int j = 0; sIx <= eIx - pad; j++) { - i |= ctoi(sArr[sIx++]) << (18 - j * 6); + i |= ctoi(seq.charAt(sIx++)) << (18 - j * 6); } for (int r = 16; d < len; r -= 8) { @@ -533,7 +534,7 @@ public final byte[] decodeFast(byte[] sArr) { * little faster. * @return A BASE64 encoded array. Never null. */ - final String encodeToString(byte[] sArr, boolean lineSep) { + String encodeToString(byte[] sArr, boolean lineSep) { // Reuse char[] since we can't create a String incrementally anyway and StringBuffer/Builder would be slower. return new String(encodeToChar(sArr, lineSep)); } diff --git a/api/src/main/java/io/jsonwebtoken/io/Base64Decoder.java b/api/src/main/java/io/jsonwebtoken/io/Base64Decoder.java index fa76d3aa5..e0cb96333 100644 --- a/api/src/main/java/io/jsonwebtoken/io/Base64Decoder.java +++ b/api/src/main/java/io/jsonwebtoken/io/Base64Decoder.java @@ -23,7 +23,7 @@ * * @since 0.10.0 */ -class Base64Decoder extends Base64Support implements Decoder { +class Base64Decoder extends Base64Support implements Decoder { Base64Decoder() { super(Base64.DEFAULT); @@ -34,8 +34,8 @@ class Base64Decoder extends Base64Support implements Decoder { } @Override - public byte[] decode(String s) throws DecodingException { + public byte[] decode(CharSequence s) throws DecodingException { Assert.notNull(s, "String argument cannot be null"); - return this.base64.decodeFast(s.toCharArray()); + return this.base64.decodeFast(s); } } \ No newline at end of file diff --git a/api/src/main/java/io/jsonwebtoken/io/Decoders.java b/api/src/main/java/io/jsonwebtoken/io/Decoders.java index 57a14149a..6b7c7e66a 100644 --- a/api/src/main/java/io/jsonwebtoken/io/Decoders.java +++ b/api/src/main/java/io/jsonwebtoken/io/Decoders.java @@ -28,13 +28,13 @@ public final class Decoders { * Very fast Base64 decoder guaranteed to * work in all >= Java 7 JDK and Android environments. */ - public static final Decoder BASE64 = new ExceptionPropagatingDecoder<>(new Base64Decoder()); + public static final Decoder BASE64 = new ExceptionPropagatingDecoder<>(new Base64Decoder()); /** * Very fast Base64Url decoder guaranteed to * work in all >= Java 7 JDK and Android environments. */ - public static final Decoder BASE64URL = new ExceptionPropagatingDecoder<>(new Base64UrlDecoder()); + public static final Decoder BASE64URL = new ExceptionPropagatingDecoder<>(new Base64UrlDecoder()); private Decoders() { //prevent instantiation } diff --git a/api/src/main/java/io/jsonwebtoken/lang/Assert.java b/api/src/main/java/io/jsonwebtoken/lang/Assert.java index 2fee08bb6..7c97f856c 100644 --- a/api/src/main/java/io/jsonwebtoken/lang/Assert.java +++ b/api/src/main/java/io/jsonwebtoken/lang/Assert.java @@ -145,7 +145,7 @@ public static void hasLength(String text) { * @return the string if it has text * @see Strings#hasText */ - public static String hasText(String text, String message) { + public static T hasText(T text, String message) { if (!Strings.hasText(text)) { throw new IllegalArgumentException(message); } diff --git a/api/src/main/java/io/jsonwebtoken/lang/Strings.java b/api/src/main/java/io/jsonwebtoken/lang/Strings.java index 212f7f66d..862873f82 100644 --- a/api/src/main/java/io/jsonwebtoken/lang/Strings.java +++ b/api/src/main/java/io/jsonwebtoken/lang/Strings.java @@ -15,6 +15,8 @@ */ package io.jsonwebtoken.lang; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.ArrayList; @@ -41,6 +43,8 @@ public final class Strings { */ public static final String EMPTY = ""; + private static final CharBuffer EMPTY_BUF = CharBuffer.wrap(EMPTY); + private static final String FOLDER_SEPARATOR = "/"; private static final String WINDOWS_FOLDER_SEPARATOR = "\\"; @@ -238,10 +242,13 @@ public static CharSequence clean(CharSequence str) { * @return the specified string's UTF-8 bytes, or {@code null} if the string is {@code null}. * @since JJWT_RELEASE_VERSION */ - public static byte[] utf8(String s) { + public static byte[] utf8(CharSequence s) { byte[] bytes = null; if (s != null) { - bytes = s.getBytes(UTF_8); + CharBuffer cb = s instanceof CharBuffer ? (CharBuffer) s : CharBuffer.wrap(s); + ByteBuffer buf = UTF_8.encode(cb); + bytes = new byte[buf.remaining()]; + buf.get(bytes); } return bytes; } @@ -257,6 +264,19 @@ public static String utf8(byte[] utf8Bytes) { return new String(utf8Bytes, UTF_8); } + /** + * Returns a {@code CharBuffer} that wraps {@code seq}, or an empty buffer if {@code seq} is null. If + * {@code seq} is already a {@code CharBuffer}, it is returned unmodified. + * + * @param seq the {@code CharSequence} to wrap. + * @return a {@code CharBuffer} that wraps {@code seq}, or an empty buffer if {@code seq} is null. + */ + public static CharBuffer wrap(CharSequence seq) { + if (!hasLength(seq)) return EMPTY_BUF; + if (seq instanceof CharBuffer) return (CharBuffer) seq; + return CharBuffer.wrap(seq); + } + /** * Returns a String representation (1s and 0s) of the specified byte. * @@ -823,8 +843,7 @@ private static void validateLocalePart(String localePart) { for (int i = 0; i < localePart.length(); i++) { char ch = localePart.charAt(i); if (ch != '_' && ch != ' ' && !Character.isLetterOrDigit(ch)) { - throw new IllegalArgumentException( - "Locale part \"" + localePart + "\" contains invalid characters"); + throw new IllegalArgumentException("Locale part \"" + localePart + "\" contains invalid characters"); } } } @@ -1049,8 +1068,7 @@ public static Properties splitArrayElementsIntoProperties(String[] array, String * @return a Properties instance representing the array contents, * or null if the array to process was null or empty */ - public static Properties splitArrayElementsIntoProperties( - String[] array, String delimiter, String charsToDelete) { + public static Properties splitArrayElementsIntoProperties(String[] array, String delimiter, String charsToDelete) { if (Objects.isEmpty(array)) { return null; @@ -1109,8 +1127,7 @@ public static String[] tokenizeToStringArray(String str, String delimiters) { * @see java.lang.String#trim() * @see #delimitedListToStringArray */ - public static String[] tokenizeToStringArray( - String str, String delimiters, boolean trimTokens, boolean ignoreEmptyTokens) { + public static String[] tokenizeToStringArray(String str, String delimiters, boolean trimTokens, boolean ignoreEmptyTokens) { if (str == null) { return null; diff --git a/api/src/test/groovy/io/jsonwebtoken/io/Base64Test.groovy b/api/src/test/groovy/io/jsonwebtoken/io/Base64Test.groovy index d53f31df3..3a6cd4a55 100644 --- a/api/src/test/groovy/io/jsonwebtoken/io/Base64Test.groovy +++ b/api/src/test/groovy/io/jsonwebtoken/io/Base64Test.groovy @@ -67,8 +67,7 @@ class Base64Test { String encoded = Base64.DEFAULT.encodeToString(bytes, true) def r = new StringReader(encoded) - String line = '' - + String line while ((line = r.readLine()) != null) { assertTrue line.length() <= 76 } @@ -82,7 +81,7 @@ class Base64Test { @Test void testDecodeFastWithEmptyCharArray() { - byte[] bytes = Base64.DEFAULT.decodeFast(new char[0]) + byte[] bytes = Base64.DEFAULT.decodeFast(Strings.EMPTY) assertEquals 0, bytes.length } @@ -90,7 +89,7 @@ class Base64Test { void testDecodeFastWithSurroundingIllegalCharacters() { String expected = 'Hello 世界' def encoded = '***SGVsbG8g5LiW55WM!!!' - byte[] bytes = Base64.DEFAULT.decodeFast(encoded.toCharArray()) + byte[] bytes = Base64.DEFAULT.decodeFast(encoded) String result = new String(bytes, Strings.UTF_8) assertEquals expected, result } @@ -99,7 +98,7 @@ class Base64Test { void testDecodeFastWithIntermediateIllegalInboundCharacters() { def encoded = 'SGVsbG8g*5LiW55WM' try { - Base64.DEFAULT.decodeFast(encoded.toCharArray()) + Base64.DEFAULT.decodeFast(encoded) fail() } catch (DecodingException de) { assertEquals 'Illegal base64 character: \'*\'', de.getMessage() @@ -110,7 +109,7 @@ class Base64Test { void testDecodeFastWithIntermediateIllegalOutOfBoundCharacters() { def encoded = 'SGVsbG8g世5LiW55WM' try { - Base64.DEFAULT.decodeFast(encoded.toCharArray()) + Base64.DEFAULT.decodeFast(encoded) fail() } catch (DecodingException de) { assertEquals 'Illegal base64 character: \'世\'', de.getMessage() @@ -121,7 +120,7 @@ class Base64Test { void testDecodeFastWithIntermediateIllegalSpaceCharacters() { def encoded = 'SGVsbG8g 5LiW55WM' try { - Base64.DEFAULT.decodeFast(encoded.toCharArray()) + Base64.DEFAULT.decodeFast(encoded) fail() } catch (DecodingException de) { assertEquals 'Illegal base64 character: \' \'', de.getMessage() @@ -134,23 +133,24 @@ class Base64Test { byte[] bytes = PLAINTEXT.getBytes(Strings.UTF_8) String encoded = Base64.DEFAULT.encodeToString(bytes, true) - byte[] resultBytes = Base64.DEFAULT.decodeFast(encoded.toCharArray()) + byte[] resultBytes = Base64.DEFAULT.decodeFast(encoded) assertTrue Arrays.equals(bytes, resultBytes) assertEquals PLAINTEXT, new String(resultBytes, Strings.UTF_8) } private static String encode(String s) { - byte[] bytes = s.getBytes(Strings.UTF_8); + byte[] bytes = s.getBytes(Strings.UTF_8) return Base64.DEFAULT.encodeToString(bytes, false) } private static String decode(String s) { - byte[] bytes = Base64.DEFAULT.decodeFast(s.toCharArray()) + byte[] bytes = Base64.DEFAULT.decodeFast(s) return new String(bytes, Strings.UTF_8) } - @Test // https://tools.ietf.org/html/rfc4648#page-12 + @Test + // https://tools.ietf.org/html/rfc4648#page-12 void testRfc4648Base64TestVectors() { assertEquals "", encode("") @@ -181,16 +181,17 @@ class Base64Test { } private static String urlEncode(String s) { - byte[] bytes = s.getBytes(Strings.UTF_8); + byte[] bytes = s.getBytes(Strings.UTF_8) return Base64.URL_SAFE.encodeToString(bytes, false) } private static String urlDecode(String s) { - byte[] bytes = Base64.URL_SAFE.decodeFast(s.toCharArray()) + byte[] bytes = Base64.URL_SAFE.decodeFast(s) return new String(bytes, Strings.UTF_8) } - @Test //same test vectors above, but with padding removed & some specials swapped: https://brockallen.com/2014/10/17/base64url-encoding/ + @Test + //same test vectors above, but with padding removed & some specials swapped: https://brockallen.com/2014/10/17/base64url-encoding/ void testRfc4648Base64UrlTestVectors() { assertEquals "", urlEncode("") @@ -216,9 +217,9 @@ class Base64Test { def input = 'special: [\r\n \t], ascii[32..126]: [ !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~]\n' def expected = "c3BlY2lhbDogWw0KIAldLCBhc2NpaVszMi4uMTI2XTogWyAhIiMkJSYnKCkqKywtLi8wMTIzNDU2Nzg5Ojs8PT4/QEFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaW1xdXl9gYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXp7fH1+XQo=" - .replace("=", "") - .replace("+", "-") - .replace("/", "_") + .replace("=", "") + .replace("+", "-") + .replace("/", "_") assertEquals expected, urlEncode(input) assertEquals input, urlDecode(expected) } diff --git a/extensions/gson/src/main/java/io/jsonwebtoken/gson/io/GsonDeserializer.java b/extensions/gson/src/main/java/io/jsonwebtoken/gson/io/GsonDeserializer.java index d06e26810..88a3cb4b9 100644 --- a/extensions/gson/src/main/java/io/jsonwebtoken/gson/io/GsonDeserializer.java +++ b/extensions/gson/src/main/java/io/jsonwebtoken/gson/io/GsonDeserializer.java @@ -18,7 +18,6 @@ import com.google.gson.Gson; import io.jsonwebtoken.io.DeserializationException; import io.jsonwebtoken.io.Deserializer; -import io.jsonwebtoken.lang.Objects; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -51,11 +50,8 @@ public T deserialize(byte[] bytes) throws DeserializationException { @Deprecated protected T readValue(byte[] bytes) throws IOException { - Reader reader = new InputStreamReader(new ByteArrayInputStream(bytes)); - try { + try (Reader reader = new InputStreamReader(new ByteArrayInputStream(bytes))) { return read(reader); - } finally { - Objects.nullSafeClose(reader); } } } diff --git a/extensions/gson/src/main/java/io/jsonwebtoken/gson/io/GsonSerializer.java b/extensions/gson/src/main/java/io/jsonwebtoken/gson/io/GsonSerializer.java index cd302109c..ac969c71f 100644 --- a/extensions/gson/src/main/java/io/jsonwebtoken/gson/io/GsonSerializer.java +++ b/extensions/gson/src/main/java/io/jsonwebtoken/gson/io/GsonSerializer.java @@ -19,7 +19,6 @@ import io.jsonwebtoken.io.SerializationException; import io.jsonwebtoken.io.Serializer; import io.jsonwebtoken.lang.Assert; -import io.jsonwebtoken.lang.Objects; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -52,11 +51,8 @@ public byte[] serialize(T t) throws SerializationException { protected byte[] writeValueAsBytes(T t) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(256); - OutputStreamWriter writer = new OutputStreamWriter(baos, StandardCharsets.UTF_8); - try { + try (OutputStreamWriter writer = new OutputStreamWriter(baos, StandardCharsets.UTF_8)) { write(writer, t); - } finally { - Objects.nullSafeClose(writer); } return baos.toByteArray(); } diff --git a/extensions/jackson/src/main/java/io/jsonwebtoken/jackson/io/JacksonDeserializer.java b/extensions/jackson/src/main/java/io/jsonwebtoken/jackson/io/JacksonDeserializer.java index 8b42497bf..2f32ef2b5 100644 --- a/extensions/jackson/src/main/java/io/jsonwebtoken/jackson/io/JacksonDeserializer.java +++ b/extensions/jackson/src/main/java/io/jsonwebtoken/jackson/io/JacksonDeserializer.java @@ -19,7 +19,6 @@ import io.jsonwebtoken.io.DeserializationException; import io.jsonwebtoken.io.Deserializer; import io.jsonwebtoken.lang.Assert; -import io.jsonwebtoken.lang.Objects; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -103,11 +102,8 @@ public T deserialize(byte[] bytes) throws DeserializationException { */ protected T readValue(byte[] bytes) throws IOException { Assert.notNull(bytes, "byte array argument cannot be null."); - Reader reader = new InputStreamReader(new ByteArrayInputStream(bytes), StandardCharsets.UTF_8); - try { + try (Reader reader = new InputStreamReader(new ByteArrayInputStream(bytes), StandardCharsets.UTF_8)) { return read(reader); - } finally { - Objects.nullSafeClose(reader); } } } diff --git a/extensions/jackson/src/main/java/io/jsonwebtoken/jackson/io/JacksonSerializer.java b/extensions/jackson/src/main/java/io/jsonwebtoken/jackson/io/JacksonSerializer.java index 8cd0d06a9..489b6fb52 100644 --- a/extensions/jackson/src/main/java/io/jsonwebtoken/jackson/io/JacksonSerializer.java +++ b/extensions/jackson/src/main/java/io/jsonwebtoken/jackson/io/JacksonSerializer.java @@ -19,7 +19,6 @@ import io.jsonwebtoken.io.SerializationException; import io.jsonwebtoken.io.Serializer; import io.jsonwebtoken.lang.Assert; -import io.jsonwebtoken.lang.Objects; import java.io.ByteArrayOutputStream; import java.io.OutputStreamWriter; @@ -72,14 +71,11 @@ public byte[] serialize(T t) throws SerializationException { */ protected byte[] writeValueAsBytes(T t) throws com.fasterxml.jackson.core.JsonProcessingException { ByteArrayOutputStream baos = new ByteArrayOutputStream(256); - OutputStreamWriter writer = new OutputStreamWriter(baos, StandardCharsets.UTF_8); - try { + try (OutputStreamWriter writer = new OutputStreamWriter(baos, StandardCharsets.UTF_8)) { write(writer, t); } catch (Throwable ex) { String msg = "Unable to write value as bytes: " + ex.getMessage(); throw new JsonProcessingException(msg, ex); - } finally { - Objects.nullSafeClose(writer); } return baos.toByteArray(); } diff --git a/extensions/jackson/src/test/groovy/io/jsonwebtoken/jackson/io/JacksonDeserializerTest.groovy b/extensions/jackson/src/test/groovy/io/jsonwebtoken/jackson/io/JacksonDeserializerTest.groovy index 54934fcd3..0d4667983 100644 --- a/extensions/jackson/src/test/groovy/io/jsonwebtoken/jackson/io/JacksonDeserializerTest.groovy +++ b/extensions/jackson/src/test/groovy/io/jsonwebtoken/jackson/io/JacksonDeserializerTest.groovy @@ -82,7 +82,8 @@ class JacksonDeserializerTest { long currentTime = System.currentTimeMillis() - byte[] serialized = Strings.utf8("""{ + String json = """ + { "oneKey":"oneValue", "custom": { "stringValue": "s-value", @@ -103,7 +104,9 @@ class JacksonDeserializerTest { } } } - """) + """ + + byte[] serialized = Strings.utf8(json) CustomBean expectedCustomBean = new CustomBean() .setByteArrayValue("bytes".getBytes("UTF-8")) diff --git a/extensions/jackson/src/test/groovy/io/jsonwebtoken/jackson/io/JacksonWriterTest.groovy b/extensions/jackson/src/test/groovy/io/jsonwebtoken/jackson/io/JacksonWriterTest.groovy index beeef2a1f..d852db730 100644 --- a/extensions/jackson/src/test/groovy/io/jsonwebtoken/jackson/io/JacksonWriterTest.groovy +++ b/extensions/jackson/src/test/groovy/io/jsonwebtoken/jackson/io/JacksonWriterTest.groovy @@ -110,7 +110,7 @@ class JacksonWriterTest { @Test void testWriteObject() { - byte[] expected = Strings.utf8('{"hello":"世界"}') + byte[] expected = Strings.utf8('{"hello":"世界"}' as String) byte[] result = write([hello: '世界']) assertArrayEquals expected, result } diff --git a/extensions/orgjson/src/main/java/io/jsonwebtoken/orgjson/io/OrgJsonDeserializer.java b/extensions/orgjson/src/main/java/io/jsonwebtoken/orgjson/io/OrgJsonDeserializer.java index 478d9547e..3f672dd71 100644 --- a/extensions/orgjson/src/main/java/io/jsonwebtoken/orgjson/io/OrgJsonDeserializer.java +++ b/extensions/orgjson/src/main/java/io/jsonwebtoken/orgjson/io/OrgJsonDeserializer.java @@ -38,14 +38,11 @@ public Object deserialize(byte[] bytes) throws DeserializationException { if (Objects.isEmpty(bytes)) { throw new DeserializationException("Invalid JSON: null or zero length byte array."); } - Reader reader = new InputStreamReader(new ByteArrayInputStream(bytes), StandardCharsets.UTF_8); - try { + try (Reader reader = new InputStreamReader(new ByteArrayInputStream(bytes), StandardCharsets.UTF_8)) { return read(reader); } catch (Throwable t) { String msg = "Unable to deserialize JSON bytes: " + t.getMessage(); throw new DeserializationException(msg, t); - } finally { - Objects.nullSafeClose(reader); } } } diff --git a/extensions/orgjson/src/main/java/io/jsonwebtoken/orgjson/io/OrgJsonSerializer.java b/extensions/orgjson/src/main/java/io/jsonwebtoken/orgjson/io/OrgJsonSerializer.java index e62ab60d7..b57b48d37 100644 --- a/extensions/orgjson/src/main/java/io/jsonwebtoken/orgjson/io/OrgJsonSerializer.java +++ b/extensions/orgjson/src/main/java/io/jsonwebtoken/orgjson/io/OrgJsonSerializer.java @@ -17,7 +17,6 @@ import io.jsonwebtoken.io.SerializationException; import io.jsonwebtoken.io.Serializer; -import io.jsonwebtoken.lang.Objects; import io.jsonwebtoken.lang.Strings; import java.io.ByteArrayOutputStream; @@ -37,15 +36,12 @@ public class OrgJsonSerializer extends OrgJsonWriter implements Serializer @Override public byte[] serialize(T object) throws SerializationException { ByteArrayOutputStream baos = new ByteArrayOutputStream(256); - Writer writer = new OutputStreamWriter(baos, StandardCharsets.UTF_8); - try { + try (Writer writer = new OutputStreamWriter(baos, StandardCharsets.UTF_8)) { write(writer, object); } catch (Throwable t) { String msg = "Unable to serialize object of type " + object.getClass().getName() + " to JSON: " + t.getMessage(); throw new SerializationException(msg, t); - } finally { - Objects.nullSafeClose(writer); } return baos.toByteArray(); } diff --git a/impl/src/main/java/io/jsonwebtoken/impl/DefaultClaims.java b/impl/src/main/java/io/jsonwebtoken/impl/DefaultClaims.java index ea3985d9e..70a6a5a42 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/DefaultClaims.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/DefaultClaims.java @@ -62,7 +62,7 @@ public DefaultClaims(Map map) { @Override public String getName() { - return "JWT Claim"; + return "JWT Claims"; } @Override diff --git a/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtBuilder.java b/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtBuilder.java index 4e9a9e9e6..a0b261140 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtBuilder.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtBuilder.java @@ -23,10 +23,10 @@ import io.jsonwebtoken.Jwts; import io.jsonwebtoken.impl.io.MapSerializer; import io.jsonwebtoken.impl.io.SerializingMapWriter; -import io.jsonwebtoken.impl.lang.BiConsumer; import io.jsonwebtoken.impl.lang.Bytes; import io.jsonwebtoken.impl.lang.Function; import io.jsonwebtoken.impl.lang.Functions; +import io.jsonwebtoken.impl.lang.Nameable; import io.jsonwebtoken.impl.lang.Parameter; import io.jsonwebtoken.impl.lang.Services; import io.jsonwebtoken.impl.security.DefaultAeadRequest; @@ -103,9 +103,6 @@ public class DefaultJwtBuilder implements JwtBuilder { private Writer> jsonWriter; - protected BiConsumer> headerSerializer; - protected BiConsumer> claimsSerializer; - protected Encoder encoder = Encoders.BASE64URL; private boolean encodePayload = true; protected CompressionAlgorithm compressionAlgorithm; @@ -141,41 +138,32 @@ protected Function wrap(Function fn, String fmt, Object... ar return Functions.wrap(fn, SecurityException.class, fmt, args); } + @SuppressWarnings("deprecation") @Override public JwtBuilder serializeToJsonWith(final Serializer> serializer) { - return serializer(serializer); + return jsonWriter(new SerializingMapWriter(serializer)); } @Override public JwtBuilder jsonWriter(Writer> writer) { this.jsonWriter = Assert.notNull(writer, "JSON Writer cannot be null."); - this.headerSerializer = new MapSerializer(writer, "header"); - this.claimsSerializer = new MapSerializer(writer, "claims"); return this; } - private BiConsumer> serializerFor(Map map) { - return map instanceof Claims ? this.claimsSerializer : this.headerSerializer; - } - private byte[] serialize(Map map) { - BiConsumer> ser = serializerFor(map); + Nameable nameable = Assert.isInstanceOf(Nameable.class, map, "JWT internal maps implement Nameable."); + Writer> jsonWriter = Assert.stateNotNull(this.jsonWriter, "JSON Writer cannot be null."); + MapSerializer serializer = new MapSerializer(jsonWriter, nameable.getName()); ByteArrayOutputStream baos = new ByteArrayOutputStream(256); java.io.Writer writer = new OutputStreamWriter(baos, StandardCharsets.UTF_8); try { - ser.accept(writer, map); + serializer.accept(writer, map); } finally { Objects.nullSafeClose(writer); } return baos.toByteArray(); } - @Override - public JwtBuilder serializer(Serializer> serializer) { - Assert.notNull(serializer, "Serializer cannot be null."); - return jsonWriter(new SerializingMapWriter(serializer)); - } - @Override public JwtBuilder base64UrlEncodeWith(Encoder encoder) { return encoder(encoder); diff --git a/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java b/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java index e05a7696b..bf548f8b6 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java @@ -39,8 +39,10 @@ import io.jsonwebtoken.ProtectedHeader; import io.jsonwebtoken.SigningKeyResolver; import io.jsonwebtoken.UnsupportedJwtException; +import io.jsonwebtoken.impl.io.JwtDeserializer; import io.jsonwebtoken.impl.lang.Bytes; import io.jsonwebtoken.impl.lang.Function; +import io.jsonwebtoken.impl.lang.RedactedSupplier; import io.jsonwebtoken.impl.security.DefaultAeadResult; import io.jsonwebtoken.impl.security.DefaultDecryptionKeyRequest; import io.jsonwebtoken.impl.security.DefaultVerifySecureDigestRequest; @@ -48,13 +50,11 @@ import io.jsonwebtoken.impl.security.ProviderKey; import io.jsonwebtoken.io.CompressionAlgorithm; import io.jsonwebtoken.io.Decoder; -import io.jsonwebtoken.io.DecodingException; -import io.jsonwebtoken.io.DeserializationException; -import io.jsonwebtoken.io.Deserializer; -import io.jsonwebtoken.lang.Arrays; +import io.jsonwebtoken.io.Reader; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.lang.DateFormats; +import io.jsonwebtoken.lang.Objects; import io.jsonwebtoken.lang.Strings; import io.jsonwebtoken.security.AeadAlgorithm; import io.jsonwebtoken.security.DecryptAeadRequest; @@ -68,6 +68,10 @@ import io.jsonwebtoken.security.WeakKeyException; import javax.crypto.SecretKey; +import java.io.ByteArrayInputStream; +import java.io.InputStreamReader; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; import java.nio.charset.StandardCharsets; import java.security.Key; import java.security.PrivateKey; @@ -187,9 +191,9 @@ public class DefaultJwtParser implements JwtParser { private final Locator keyLocator; - private final Decoder decoder; + private final Decoder decoder; - private final Deserializer> deserializer; + private final Reader> jsonReader; private final ClaimsBuilder expectedClaims; @@ -210,8 +214,8 @@ public class DefaultJwtParser implements JwtParser { Set critical, long allowedClockSkewMillis, DefaultClaims expectedClaims, - Decoder base64UrlDecoder, - Deserializer> deserializer, + Decoder base64UrlDecoder, + Reader> jsonReader, CompressionCodecResolver compressionCodecResolver, Collection extraZipAlgs, Collection> extraSigAlgs, @@ -227,7 +231,7 @@ public class DefaultJwtParser implements JwtParser { this.allowedClockSkewMillis = allowedClockSkewMillis; this.expectedClaims = Jwts.claims().add(expectedClaims); this.decoder = Assert.notNull(base64UrlDecoder, "base64UrlDecoder cannot be null."); - this.deserializer = Assert.notNull(deserializer, "Deserializer cannot be null."); + this.jsonReader = Assert.notNull(jsonReader, "jsonReader cannot be null."); this.sigAlgFn = new IdLocator<>(DefaultHeader.ALGORITHM, Jwts.SIG.get(), extraSigAlgs, MISSING_JWS_ALG_MSG); this.keyAlgFn = new IdLocator<>(DefaultHeader.ALGORITHM, Jwts.KEY.get(), extraKeyAlgs, MISSING_JWE_ALG_MSG); @@ -361,13 +365,27 @@ private void verifySignature(final TokenizedJwt tokenized, final JwsHeader jwsHe //re-create the jwt part without the signature. This is what is needed for signature verification: byte[] verificationInput; - String jwtPrefix = tokenized.getProtected() + SEPARATOR_CHAR; if (jwsHeader.isPayloadEncoded()) { - jwtPrefix += tokenized.getPayload(); - verificationInput = jwtPrefix.getBytes(StandardCharsets.US_ASCII); + int len = tokenized.getProtected().length() + 1 + tokenized.getPayload().length(); + CharBuffer cb = CharBuffer.allocate(len); + cb.put(Strings.wrap(tokenized.getProtected())); + cb.put(SEPARATOR_CHAR); + cb.put(Strings.wrap(tokenized.getPayload())); + cb.rewind(); + ByteBuffer bb = StandardCharsets.US_ASCII.encode(cb); + bb.rewind(); + verificationInput = new byte[bb.remaining()]; + bb.get(verificationInput); } else { - byte[] prefixBytes = jwtPrefix.getBytes(StandardCharsets.US_ASCII); - verificationInput = Bytes.concat(prefixBytes, payload); + ByteBuffer headerBuf = StandardCharsets.US_ASCII.encode(Strings.wrap(tokenized.getProtected())); + headerBuf.rewind(); + ByteBuffer buf = ByteBuffer.allocate(headerBuf.remaining() + 1 + Bytes.length(payload)); + buf.put(headerBuf); + buf.put((byte) SEPARATOR_CHAR); + buf.put(payload); + buf.rewind(); + verificationInput = new byte[buf.remaining()]; + buf.get(verificationInput); } try { @@ -395,15 +413,16 @@ private void verifySignature(final TokenizedJwt tokenized, final JwsHeader jwsHe @Override public Jwt parse(String compact) { - return parse(compact, Bytes.EMPTY); + CharBuffer buffer = Strings.wrap(compact); // so compact.subsequence calls don't add new Strings on the heap + return parse(buffer, Bytes.EMPTY); } - private Jwt parse(String compact, final byte[] unencodedPayload) throws ExpiredJwtException, MalformedJwtException, SignatureException { + private Jwt parse(CharSequence compact, final byte[] unencodedPayload) throws ExpiredJwtException, MalformedJwtException, SignatureException { Assert.hasText(compact, "JWT String cannot be null or empty."); final TokenizedJwt tokenized = jwtTokenizer.tokenize(compact); - final String base64UrlHeader = tokenized.getProtected(); + final CharSequence base64UrlHeader = tokenized.getProtected(); if (!Strings.hasText(base64UrlHeader)) { String msg = "Compact JWT strings MUST always have a Base64Url protected header per https://tools.ietf.org/html/rfc7519#section-7.2 (steps 2-4)."; throw new MalformedJwtException(msg); @@ -433,7 +452,7 @@ private void verifySignature(final TokenizedJwt tokenized, final JwsHeader jwsHe } final boolean unsecured = Jwts.SIG.NONE.getId().equalsIgnoreCase(alg); - final String base64UrlDigest = tokenized.getDigest(); + final CharSequence base64UrlDigest = tokenized.getDigest(); final boolean hasDigest = Strings.hasText(base64UrlDigest); if (unsecured) { if (tokenized instanceof TokenizedJwe) { @@ -473,7 +492,7 @@ private void verifySignature(final TokenizedJwt tokenized, final JwsHeader jwsHe } // =============== Payload ================= - final String payloadToken = tokenized.getPayload(); + final CharSequence payloadToken = tokenized.getPayload(); byte[] payload; boolean integrityVerified = false; // only true after successful signature verification or AEAD decryption @@ -516,10 +535,10 @@ private void verifySignature(final TokenizedJwt tokenized, final JwsHeader jwsHe JweHeader jweHeader = Assert.stateIsInstance(JweHeader.class, header, "Not a JweHeader. "); byte[] cekBytes = Bytes.EMPTY; //ignored unless using an encrypted key algorithm - String base64Url = tokenizedJwe.getEncryptedKey(); + CharSequence base64Url = tokenizedJwe.getEncryptedKey(); if (Strings.hasText(base64Url)) { cekBytes = decode(base64Url, "JWE encrypted key"); - if (Arrays.length(cekBytes) == 0) { + if (Bytes.isEmpty(cekBytes)) { String msg = "Compact JWE string represents an encrypted key, but the key is empty."; throw new MalformedJwtException(msg); } @@ -529,7 +548,7 @@ private void verifySignature(final TokenizedJwt tokenized, final JwsHeader jwsHe if (Strings.hasText(base64Url)) { iv = decode(base64Url, "JWE Initialization Vector"); } - if (Arrays.length(iv) == 0) { + if (Bytes.isEmpty(iv)) { String msg = "Compact JWE strings must always contain an Initialization Vector."; throw new MalformedJwtException(msg); } @@ -537,13 +556,15 @@ private void verifySignature(final TokenizedJwt tokenized, final JwsHeader jwsHe // The AAD (Additional Authenticated Data) scheme for compact JWEs is to use the ASCII bytes of the // raw base64url text as the AAD, and NOT the base64url-decoded bytes per // https://www.rfc-editor.org/rfc/rfc7516.html#section-5.1, Step 14. - final byte[] aad = base64UrlHeader.getBytes(StandardCharsets.US_ASCII); + ByteBuffer buf = StandardCharsets.US_ASCII.encode(Strings.wrap(base64UrlHeader)); + final byte[] aad = new byte[buf.remaining()]; + buf.get(aad); base64Url = base64UrlDigest; //guaranteed to be non-empty via the `alg` + digest check above: Assert.hasText(base64Url, "JWE AAD Authentication Tag cannot be null or empty."); tag = decode(base64Url, "JWE AAD Authentication Tag"); - if (Arrays.length(tag) == 0) { + if (Bytes.isEmpty(tag)) { String msg = "Compact JWE strings must always contain an AAD Authentication Tag."; throw new MalformedJwtException(msg); } @@ -636,7 +657,7 @@ private void verifySignature(final TokenizedJwt tokenized, final JwsHeader jwsHe jwt = new DefaultJwe<>((JweHeader) header, body, iv, tag); } else if (hasDigest) { JwsHeader jwsHeader = Assert.isInstanceOf(JwsHeader.class, header, "JwsHeader required."); - jwt = new DefaultJws<>(jwsHeader, body, base64UrlDigest); + jwt = new DefaultJws<>(jwsHeader, body, base64UrlDigest.toString()); } else { //noinspection rawtypes jwt = new DefaultJwt(header, body); @@ -881,21 +902,25 @@ public Jwe onClaimsJwe(Jwe jwe) { }); } - protected byte[] decode(String base64UrlEncoded, String name) { + protected byte[] decode(CharSequence base64UrlEncoded, String name) { try { return decoder.decode(base64UrlEncoded); - } catch (DecodingException e) { - String msg = "Invalid Base64Url " + name + ": " + base64UrlEncoded; - throw new MalformedJwtException(msg, e); + } catch (Throwable t) { + // Don't disclose potentially-sensitive information per https://github.com/jwtk/jjwt/issues/824: + String value = "payload".equals(name) ? RedactedSupplier.REDACTED_VALUE : base64UrlEncoded.toString(); + String msg = "Invalid Base64Url " + name + ": " + value; + throw new MalformedJwtException(msg, t); } } protected Map deserialize(byte[] bytes, final String name) { + ByteArrayInputStream is = new ByteArrayInputStream(bytes); + java.io.Reader r = new InputStreamReader(is); try { - return deserializer.deserialize(bytes); - } catch (MalformedJwtException | DeserializationException e) { - String s = new String(bytes, StandardCharsets.UTF_8); - throw new MalformedJwtException("Unable to read " + name + " JSON: " + s, e); + JwtDeserializer> jwtd = new JwtDeserializer<>(jsonReader, name); + return jwtd.apply(r); + } finally { + Objects.nullSafeClose(r); } } } diff --git a/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParserBuilder.java b/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParserBuilder.java index 90dc6ae3e..406b91267 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParserBuilder.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParserBuilder.java @@ -23,12 +23,15 @@ import io.jsonwebtoken.Jwts; import io.jsonwebtoken.Locator; import io.jsonwebtoken.SigningKeyResolver; +import io.jsonwebtoken.impl.io.DeserializingMapReader; import io.jsonwebtoken.impl.lang.Services; import io.jsonwebtoken.impl.security.ConstantKeyLocator; import io.jsonwebtoken.io.CompressionAlgorithm; import io.jsonwebtoken.io.Decoder; import io.jsonwebtoken.io.Decoders; +import io.jsonwebtoken.io.DecodingException; import io.jsonwebtoken.io.Deserializer; +import io.jsonwebtoken.io.Reader; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.lang.Strings; @@ -88,9 +91,9 @@ public class DefaultJwtParserBuilder implements JwtParserBuilder { @SuppressWarnings("deprecation") private CompressionCodecResolver compressionCodecResolver; - private Decoder decoder = Decoders.BASE64URL; + private Decoder decoder = Decoders.BASE64URL; - private Deserializer> deserializer; + private Reader> jsonReader; private final ClaimsBuilder expectedClaims = Jwts.claims(); @@ -123,23 +126,29 @@ public JwtParserBuilder provider(Provider provider) { @Override public JwtParserBuilder deserializeJsonWith(Deserializer> deserializer) { - return deserializer(deserializer); + Assert.notNull(deserializer, "deserializer cannot be null."); + return jsonReader(new DeserializingMapReader(deserializer)); } @Override - public JwtParserBuilder deserializer(Deserializer> deserializer) { - Assert.notNull(deserializer, "deserializer cannot be null."); - this.deserializer = deserializer; + public JwtParserBuilder jsonReader(Reader> reader) { + this.jsonReader = Assert.notNull(reader, "JSON Reader cannot be null."); return this; } @Override - public JwtParserBuilder base64UrlDecodeWith(Decoder decoder) { - return decoder(decoder); + public JwtParserBuilder base64UrlDecodeWith(final Decoder decoder) { + Assert.notNull(decoder, "decoder cannot be null."); + return decoder(new Decoder() { + @Override + public byte[] decode(CharSequence charSequence) throws DecodingException { + return decoder.decode(charSequence.toString()); + } + }); } @Override - public JwtParserBuilder decoder(Decoder decoder) { + public JwtParserBuilder decoder(Decoder decoder) { Assert.notNull(decoder, "decoder cannot be null."); this.decoder = decoder; return this; @@ -349,14 +358,10 @@ public JwtParserBuilder setCompressionCodecResolver(CompressionCodecResolver res @Override public JwtParser build() { - // Only lookup the deserializer IF it is null. It is possible a Deserializer implementation was set - // that is NOT exposed as a service and no other implementations are available for lookup. - if (this.deserializer == null) { - // try to find one based on the services available: + if (this.jsonReader == null) { //noinspection unchecked - this.deserializer = Services.loadFirst(Deserializer.class); + jsonReader(Services.loadFirst(Reader.class)); } - if (this.signingKeyResolver != null && this.signatureVerificationKey != null) { String msg = "Both a 'signingKeyResolver and a 'verifyWith' key cannot be configured. " + "Choose either, or prefer `keyLocator` when possible."; @@ -408,7 +413,7 @@ public JwtParser build() { allowedClockSkewMillis, expClaims, decoder, - new JwtDeserializer<>(deserializer), + jsonReader, compressionCodecResolver, extraZipAlgs, extraSigAlgs, diff --git a/impl/src/main/java/io/jsonwebtoken/impl/DefaultTokenizedJwe.java b/impl/src/main/java/io/jsonwebtoken/impl/DefaultTokenizedJwe.java index 33de58b06..45952d5e8 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/DefaultTokenizedJwe.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/DefaultTokenizedJwe.java @@ -21,22 +21,23 @@ class DefaultTokenizedJwe extends DefaultTokenizedJwt implements TokenizedJwe { - private final String encryptedKey; - private final String iv; + private final CharSequence encryptedKey; + private final CharSequence iv; - DefaultTokenizedJwe(String protectedHeader, String body, String digest, String encryptedKey, String iv) { + DefaultTokenizedJwe(CharSequence protectedHeader, CharSequence body, CharSequence digest, + CharSequence encryptedKey, CharSequence iv) { super(protectedHeader, body, digest); this.encryptedKey = encryptedKey; this.iv = iv; } @Override - public String getEncryptedKey() { + public CharSequence getEncryptedKey() { return this.encryptedKey; } @Override - public String getIv() { + public CharSequence getIv() { return this.iv; } diff --git a/impl/src/main/java/io/jsonwebtoken/impl/DefaultTokenizedJwt.java b/impl/src/main/java/io/jsonwebtoken/impl/DefaultTokenizedJwt.java index 9a54b9b4e..3a020d5ec 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/DefaultTokenizedJwt.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/DefaultTokenizedJwt.java @@ -23,28 +23,28 @@ class DefaultTokenizedJwt implements TokenizedJwt { - private final String protectedHeader; - private final String payload; - private final String digest; + private final CharSequence protectedHeader; + private final CharSequence payload; + private final CharSequence digest; - DefaultTokenizedJwt(String protectedHeader, String payload, String digest) { + DefaultTokenizedJwt(CharSequence protectedHeader, CharSequence payload, CharSequence digest) { this.protectedHeader = protectedHeader; this.payload = payload; this.digest = digest; } @Override - public String getProtected() { + public CharSequence getProtected() { return this.protectedHeader; } @Override - public String getPayload() { + public CharSequence getPayload() { return this.payload; } @Override - public String getDigest() { + public CharSequence getDigest() { return this.digest; } diff --git a/impl/src/main/java/io/jsonwebtoken/impl/JwtDeserializer.java b/impl/src/main/java/io/jsonwebtoken/impl/JwtDeserializer.java deleted file mode 100644 index 0a7f12a5e..000000000 --- a/impl/src/main/java/io/jsonwebtoken/impl/JwtDeserializer.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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.impl; - -import io.jsonwebtoken.MalformedJwtException; -import io.jsonwebtoken.io.DeserializationException; -import io.jsonwebtoken.io.Deserializer; -import io.jsonwebtoken.io.IOException; -import io.jsonwebtoken.lang.Assert; - -import java.nio.charset.StandardCharsets; - -/** - * A {@link Deserializer} implementation that wraps another Deserializer implementation to add common JWT related - * error handling. - * - * @param type of object to deserialize. - * @since 0.11.3 - */ -class JwtDeserializer implements Deserializer { - - static final String MALFORMED_ERROR = "Malformed JWT JSON: "; - static final String MALFORMED_COMPLEX_ERROR = "Malformed or excessively complex JWT JSON. This could reflect a potential malicious JWT, please investigate the JWT source further. JSON: "; - - private final Deserializer deserializer; - - JwtDeserializer(Deserializer deserializer) { - Assert.notNull(deserializer, "deserializer cannot be null."); - this.deserializer = deserializer; - } - - @Override - public T deserialize(byte[] bytes) throws DeserializationException { - try { - return deserializer.deserialize(bytes); - } catch (DeserializationException e) { - throw new MalformedJwtException(MALFORMED_ERROR + new String(bytes, StandardCharsets.UTF_8), e); - } catch (StackOverflowError e) { - throw new IOException(MALFORMED_COMPLEX_ERROR + new String(bytes, StandardCharsets.UTF_8), e); - } - } -} diff --git a/impl/src/main/java/io/jsonwebtoken/impl/JwtTokenizer.java b/impl/src/main/java/io/jsonwebtoken/impl/JwtTokenizer.java index 084bc6f49..a0845fe51 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/JwtTokenizer.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/JwtTokenizer.java @@ -17,28 +17,28 @@ import io.jsonwebtoken.MalformedJwtException; import io.jsonwebtoken.lang.Assert; +import io.jsonwebtoken.lang.Strings; public class JwtTokenizer { static final char DELIMITER = '.'; private static final String DELIM_ERR_MSG_PREFIX = "Invalid compact JWT string: Compact JWSs must contain " + - "exactly 2 period characters, and compact JWEs must contain exactly 4. Found: "; + "exactly 2 period characters, and compact JWEs must contain exactly 4. Found: "; @SuppressWarnings("unchecked") - public T tokenize(String jwt) { + public T tokenize(CharSequence jwt) { Assert.hasText(jwt, "Argument cannot be null or empty."); - String protectedHeader = ""; //Both JWS and JWE - String body = ""; //JWS payload or JWE Ciphertext - String encryptedKey = ""; //JWE only - String iv = ""; //JWE only - String digest; //JWS Signature or JWE AAD Tag + CharSequence protectedHeader = Strings.EMPTY; //Both JWS and JWE + CharSequence body = Strings.EMPTY; //JWS payload or JWE Ciphertext + CharSequence encryptedKey = Strings.EMPTY; //JWE only + CharSequence iv = Strings.EMPTY; //JWE only + CharSequence digest; //JWS Signature or JWE AAD Tag int delimiterCount = 0; - - StringBuilder sb = new StringBuilder(128); + int start = 0; for (int i = 0; i < jwt.length(); i++) { @@ -51,7 +51,8 @@ public T tokenize(String jwt) { if (c == DELIMITER) { - String token = sb.toString(); + CharSequence token = jwt.subSequence(start, i); + start = i + 1; switch (delimiterCount) { case 0: @@ -62,7 +63,7 @@ public T tokenize(String jwt) { encryptedKey = token; //for JWE break; case 2: - body = ""; //clear out value set for JWS + body = Strings.EMPTY; //clear out value set for JWS iv = token; break; case 3: @@ -70,10 +71,7 @@ public T tokenize(String jwt) { break; } - sb.setLength(0); delimiterCount++; - } else { - sb.append(c); } } @@ -82,7 +80,7 @@ public T tokenize(String jwt) { throw new MalformedJwtException(msg); } - digest = sb.toString(); + digest = jwt.subSequence(start, jwt.length()); if (delimiterCount == 2) { return (T) new DefaultTokenizedJwt(protectedHeader, body, digest); diff --git a/impl/src/main/java/io/jsonwebtoken/impl/TokenizedJwe.java b/impl/src/main/java/io/jsonwebtoken/impl/TokenizedJwe.java index b9c97826d..4c335bb41 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/TokenizedJwe.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/TokenizedJwe.java @@ -17,7 +17,7 @@ public interface TokenizedJwe extends TokenizedJwt { - String getEncryptedKey(); + CharSequence getEncryptedKey(); - String getIv(); + CharSequence getIv(); } diff --git a/impl/src/main/java/io/jsonwebtoken/impl/TokenizedJwt.java b/impl/src/main/java/io/jsonwebtoken/impl/TokenizedJwt.java index 948e4f87f..5269b2e4e 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/TokenizedJwt.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/TokenizedJwt.java @@ -26,21 +26,21 @@ public interface TokenizedJwt { * * @return protected header. */ - String getProtected(); + CharSequence getProtected(); /** * Returns the Payload for a JWS or Ciphertext for a JWE. * * @return the Payload for a JWS or Ciphertext for a JWE. */ - String getPayload(); + CharSequence getPayload(); /** * Returns the Signature for JWS or AAD Tag for JWE. * * @return the Signature for JWS or AAD Tag for JWE. */ - String getDigest(); + CharSequence getDigest(); /** * Returns a new {@link Header} instance with the specified map state. diff --git a/impl/src/main/java/io/jsonwebtoken/impl/io/Codec.java b/impl/src/main/java/io/jsonwebtoken/impl/io/Codec.java index 441f027d2..38aebc4c2 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/io/Codec.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/io/Codec.java @@ -23,15 +23,15 @@ import io.jsonwebtoken.io.Encoders; import io.jsonwebtoken.lang.Assert; -public class Codec implements Converter { +public class Codec implements Converter { public static final Codec BASE64 = new Codec(Encoders.BASE64, Decoders.BASE64); public static final Codec BASE64URL = new Codec(Encoders.BASE64URL, Decoders.BASE64URL); private final Encoder encoder; - private final Decoder decoder; + private final Decoder decoder; - public Codec(Encoder encoder, Decoder decoder) { + public Codec(Encoder encoder, Decoder decoder) { this.encoder = Assert.notNull(encoder, "Encoder cannot be null."); this.decoder = Assert.notNull(decoder, "Decoder cannot be null."); } @@ -42,7 +42,7 @@ public String applyTo(byte[] a) { } @Override - public byte[] applyFrom(String b) { + public byte[] applyFrom(CharSequence b) { try { return this.decoder.decode(b); } catch (DecodingException e) { diff --git a/impl/src/main/java/io/jsonwebtoken/impl/io/DeserializingMapReader.java b/impl/src/main/java/io/jsonwebtoken/impl/io/DeserializingMapReader.java new file mode 100644 index 000000000..0a4f96374 --- /dev/null +++ b/impl/src/main/java/io/jsonwebtoken/impl/io/DeserializingMapReader.java @@ -0,0 +1,35 @@ +package io.jsonwebtoken.impl.io; + +import io.jsonwebtoken.io.Deserializer; +import io.jsonwebtoken.io.Reader; +import io.jsonwebtoken.lang.Assert; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.nio.charset.StandardCharsets; +import java.util.Map; + +public class DeserializingMapReader implements Reader> { + + private final Deserializer> deserializer; + + public DeserializingMapReader(Deserializer> deserializer) { + this.deserializer = Assert.notNull(deserializer, "Deserializer cannot be null."); + } + + @Override + public Map read(java.io.Reader in) throws IOException { + int len = 256; + ByteArrayOutputStream baos = new ByteArrayOutputStream(len); + try (OutputStreamWriter writer = new OutputStreamWriter(baos, StandardCharsets.UTF_8)) { + char[] buf = new char[len]; + while (len != -1) { + len = in.read(buf, 0, buf.length); + if (len > 0) writer.write(buf, 0, len); + } + } + byte[] data = baos.toByteArray(); + return deserializer.deserialize(data); + } +} diff --git a/impl/src/main/java/io/jsonwebtoken/impl/io/JwtDeserializer.java b/impl/src/main/java/io/jsonwebtoken/impl/io/JwtDeserializer.java new file mode 100644 index 000000000..ada3e9c47 --- /dev/null +++ b/impl/src/main/java/io/jsonwebtoken/impl/io/JwtDeserializer.java @@ -0,0 +1,57 @@ +/* + * 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.impl.io; + +import io.jsonwebtoken.MalformedJwtException; +import io.jsonwebtoken.impl.lang.Function; +import io.jsonwebtoken.io.DeserializationException; +import io.jsonwebtoken.io.Reader; +import io.jsonwebtoken.lang.Assert; + +/** + * Function that wraps a {@link Reader} to add JWT-related error handling. + * + * @param type of object to deserialize. + * @since 0.11.3 + */ +public final class JwtDeserializer implements Function { + + static final String MALFORMED_ERROR = "Malformed %s JSON: %s"; + static final String MALFORMED_COMPLEX_ERROR = "Malformed or excessively complex %s JSON. This could reflect a " + + "potential malicious JWT, please investigate the JWT source further. Cause: %s"; + + private final Reader reader; + private final String name; + + public JwtDeserializer(Reader reader, String name) { + this.reader = Assert.notNull(reader, "reader cannot be null."); + this.name = Assert.hasText(name, "name cannot be null or empty."); + } + + @Override + public T apply(java.io.Reader reader) { + Assert.notNull(reader, "Reader argument cannot be null."); + try { + return this.reader.read(reader); + } catch (StackOverflowError e) { + String msg = String.format(MALFORMED_COMPLEX_ERROR, this.name, e.getMessage()); + throw new DeserializationException(msg, e); + } catch (Throwable t) { + String msg = String.format(MALFORMED_ERROR, this.name, t.getMessage()); + throw new MalformedJwtException(msg, t); + } + } +} diff --git a/impl/src/main/java/io/jsonwebtoken/impl/lang/Converters.java b/impl/src/main/java/io/jsonwebtoken/impl/lang/Converters.java index 138808bd8..6e04d5e8b 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/lang/Converters.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/lang/Converters.java @@ -31,11 +31,11 @@ public final class Converters { public static final Converter BASE64URL_BYTES = Converters.forEncoded(byte[].class, Codec.BASE64URL); public static final Converter X509_CERTIFICATE = - Converters.forEncoded(X509Certificate.class, JwtX509StringConverter.INSTANCE); + Converters.forEncoded(X509Certificate.class, JwtX509StringConverter.INSTANCE); public static final Converter BIGINT_UBYTES = new BigIntegerUBytesConverter(); public static final Converter BIGINT = Converters.forEncoded(BigInteger.class, - compound(BIGINT_UBYTES, Codec.BASE64URL)); + compound(BIGINT_UBYTES, Codec.BASE64URL)); //prevent instantiation private Converters() { @@ -53,7 +53,7 @@ public static Converter, Object> forList(Converter elemen return CollectionConverter.forList(elementConverter); } - public static Converter forEncoded(Class elementType, Converter elementConverter) { + public static Converter forEncoded(Class elementType, Converter elementConverter) { return new EncodedObjectConverter<>(elementType, elementConverter); } diff --git a/impl/src/main/java/io/jsonwebtoken/impl/lang/EncodedObjectConverter.java b/impl/src/main/java/io/jsonwebtoken/impl/lang/EncodedObjectConverter.java index c6f0f33ef..dbea3478a 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/lang/EncodedObjectConverter.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/lang/EncodedObjectConverter.java @@ -20,9 +20,9 @@ public class EncodedObjectConverter implements Converter { private final Class type; - private final Converter converter; + private final Converter converter; - public EncodedObjectConverter(Class type, Converter converter) { + public EncodedObjectConverter(Class type, Converter converter) { this.type = Assert.notNull(type, "Value type cannot be null."); this.converter = Assert.notNull(converter, "Value converter cannot be null."); } @@ -38,11 +38,11 @@ public T applyFrom(Object value) { Assert.notNull(value, "Value argument cannot be null."); if (type.isInstance(value)) { return type.cast(value); - } else if (value instanceof String) { - return converter.applyFrom((String) value); + } else if (value instanceof CharSequence) { + return converter.applyFrom((CharSequence) value); } else { String msg = "Values must be either String or " + type.getName() + - " instances. Value type found: " + value.getClass().getName() + "."; + " instances. Value type found: " + value.getClass().getName() + "."; throw new IllegalArgumentException(msg); } } diff --git a/impl/src/main/java/io/jsonwebtoken/impl/lang/UriStringConverter.java b/impl/src/main/java/io/jsonwebtoken/impl/lang/UriStringConverter.java index 6b20ba9c1..5a4dad824 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/lang/UriStringConverter.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/lang/UriStringConverter.java @@ -19,7 +19,7 @@ import java.net.URI; -public class UriStringConverter implements Converter { +public class UriStringConverter implements Converter { @Override public String applyTo(URI uri) { @@ -28,10 +28,10 @@ public String applyTo(URI uri) { } @Override - public URI applyFrom(String s) { + public URI applyFrom(CharSequence s) { Assert.hasText(s, "URI string cannot be null or empty."); try { - return URI.create(s); + return URI.create(s.toString()); } catch (Exception e) { String msg = "Unable to convert String value '" + s + "' to URI instance: " + e.getMessage(); throw new IllegalArgumentException(msg, e); diff --git a/impl/src/main/java/io/jsonwebtoken/impl/security/JwtX509StringConverter.java b/impl/src/main/java/io/jsonwebtoken/impl/security/JwtX509StringConverter.java index 8e94d74fb..359ff95cf 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/security/JwtX509StringConverter.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/security/JwtX509StringConverter.java @@ -25,7 +25,7 @@ import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; -public class JwtX509StringConverter implements Converter { +public class JwtX509StringConverter implements Converter { public static final JwtX509StringConverter INSTANCE = new JwtX509StringConverter(); @@ -57,7 +57,7 @@ protected X509Certificate toCert(final byte[] der) throws SecurityException { } @Override - public X509Certificate applyFrom(String s) { + public X509Certificate applyFrom(CharSequence s) { Assert.hasText(s, "X.509 Certificate encoded string cannot be null or empty."); try { byte[] der = Decoders.BASE64.decode(s); //RFC requires Base64, not Base64Url diff --git a/impl/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy index 3f412cc94..ccb99a696 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy @@ -74,7 +74,8 @@ class JwtParserTest { Jwts.parser().enableUnsecured().build().parse(bad) fail() } catch (MalformedJwtException expected) { - assertEquals 'Unable to read claims JSON: ' + junkPayload, expected.getMessage() + String prefix = 'Malformed claims JSON: ' + assertTrue expected.getMessage().startsWith(prefix) } } diff --git a/impl/src/test/groovy/io/jsonwebtoken/JwtsTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/JwtsTest.groovy index 6835d81ec..6f8c3bfb3 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/JwtsTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/JwtsTest.groovy @@ -146,7 +146,7 @@ class JwtsTest { Jwts.parser().setSigningKey(key).build().parseClaimsJws(compact) fail() } catch (MalformedJwtException e) { - String expected = 'Invalid claims: Invalid JWT Claim \'exp\' (Expiration Time) value: -42-. ' + + String expected = 'Invalid claims: Invalid JWT Claims \'exp\' (Expiration Time) value: -42-. ' + 'String value is not a JWT NumericDate, nor is it ISO-8601-formatted. All heuristics exhausted. ' + 'Cause: Unparseable date: "-42-"' assertEquals expected, e.getMessage() diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/DefaultClaimsTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/DefaultClaimsTest.groovy index 7bb2e8923..ae987402d 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/DefaultClaimsTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/DefaultClaimsTest.groovy @@ -344,7 +344,7 @@ class DefaultClaimsTest { claims.put(param.getId(), val) fail() } catch (IllegalArgumentException iae) { - String msg = "Invalid JWT Claim $param value: hi. Cannot create Date from object of type io.jsonwebtoken.impl.DefaultClaimsTest\$1." + String msg = "Invalid JWT Claims $param value: hi. Cannot create Date from object of type io.jsonwebtoken.impl.DefaultClaimsTest\$1." assertEquals msg, iae.getMessage() } } diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/DefaultJwtBuilderTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/DefaultJwtBuilderTest.groovy index 542f6bc66..6611b5e5d 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/DefaultJwtBuilderTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/DefaultJwtBuilderTest.groovy @@ -284,12 +284,12 @@ class DefaultJwtBuilderTest { throw new SerializationException('foo', new Exception()) } } - def b = new DefaultJwtBuilder().serializer(serializer) + def b = new DefaultJwtBuilder().serializeToJsonWith(serializer) try { b.setPayload('foo').compact() fail() } catch (SerializationException expected) { - assertEquals 'Cannot serialize header to JSON. Cause: foo', expected.getMessage() + assertEquals 'Cannot serialize JWT header to JSON. Cause: foo', expected.getMessage() } } @@ -309,7 +309,7 @@ class DefaultJwtBuilderTest { b.compact() fail() } catch (SerializationException expected) { - assertEquals 'Cannot serialize claims to JSON. Cause: dummy text', expected.message + assertEquals 'Cannot serialize JWT Claims to JSON. Cause: dummy text', expected.message } } diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/DefaultJwtParserBuilderTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/DefaultJwtParserBuilderTest.groovy index 18ecf1fac..29e2137d1 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/DefaultJwtParserBuilderTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/DefaultJwtParserBuilderTest.groovy @@ -20,10 +20,7 @@ import io.jsonwebtoken.* import io.jsonwebtoken.impl.security.ConstantKeyLocator import io.jsonwebtoken.impl.security.LocatingKeyResolver import io.jsonwebtoken.impl.security.TestKeys -import io.jsonwebtoken.io.Decoder -import io.jsonwebtoken.io.DecodingException -import io.jsonwebtoken.io.DeserializationException -import io.jsonwebtoken.io.Deserializer +import io.jsonwebtoken.io.* import io.jsonwebtoken.security.* import org.hamcrest.CoreMatchers import org.junit.Before @@ -102,14 +99,22 @@ class DefaultJwtParserBuilderTest { @Test void testBase64UrlEncodeWithCustomDecoder() { - def decoder = new Decoder() { + + String jwt = Jwts.builder().claim('foo', 'bar').compact() + + boolean invoked = false + Decoder decoder = new Decoder() { @Override - Object decode(Object o) throws DecodingException { - return null + byte[] decode(String s) throws DecodingException { + invoked = true + return Decoders.BASE64URL.decode(s) } } - def b = builder.base64UrlDecodeWith(decoder).build() - assertSame decoder, b.decoder + def parser = builder.base64UrlDecodeWith(decoder).enableUnsecured().build() + assertFalse invoked + + assertEquals 'bar', parser.parseClaimsJwt(jwt).getPayload().get('foo') + assertTrue invoked } @Test(expected = IllegalArgumentException) @@ -126,7 +131,7 @@ class DefaultJwtParserBuilderTest { } } def p = builder.deserializeJsonWith(deserializer) - assertSame deserializer, p.deserializer + assertSame deserializer, p.jsonReader.deserializer def alg = Jwts.SIG.HS256 def key = alg.key().build() @@ -337,17 +342,15 @@ class DefaultJwtParserBuilderTest { @Test void testDefaultDeserializer() { - JwtParser parser = builder.build() - assertThat parser.deserializer, CoreMatchers.instanceOf(JwtDeserializer) + JwtParser parser = builder.build() // perform ServiceLoader lookup + assertThat parser.jsonReader, CoreMatchers.instanceOf(Reader) } @Test void testUserSetDeserializerWrapped() { Deserializer deserializer = niceMock(Deserializer) JwtParser parser = builder.deserializeJsonWith(deserializer).build() - - assertThat parser.deserializer, CoreMatchers.instanceOf(JwtDeserializer) - assertSame deserializer, parser.deserializer.deserializer + assertSame deserializer, parser.jsonReader.deserializer } @Test diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/DefaultJwtParserTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/DefaultJwtParserTest.groovy index 5a3e718e6..190eff83c 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/DefaultJwtParserTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/DefaultJwtParserTest.groovy @@ -68,22 +68,23 @@ class DefaultJwtParserTest { @Test void testDesrializeJsonWithCustomSerializer() { + boolean invoked = false def deserializer = new Deserializer() { @Override Object deserialize(byte[] bytes) throws DeserializationException { + invoked = true return OBJECT_MAPPER.readValue(bytes, Map.class) } } def pb = Jwts.parser().deserializeJsonWith(deserializer) - def p = pb.build() as DefaultJwtParser - assertTrue("Expected wrapping deserializer to be instance of JwtDeserializer", p.deserializer instanceof JwtDeserializer) - assertSame deserializer, p.deserializer.deserializer + assertFalse invoked def key = Jwts.SIG.HS256.key().build() - String jws = Jwts.builder().claim('foo', 'bar').signWith(key, Jwts.SIG.HS256).compact() + assertFalse invoked assertEquals 'bar', pb.verifyWith(key).build().parseClaimsJws(jws).getPayload().get('foo') + assertTrue invoked } @Test(expected = MalformedJwtException) @@ -288,4 +289,17 @@ class DefaultJwtParserTest { assertEquals msg, expected.message } } + + @Test + void testInvalidB64UrlPayload() { + def jwt = Encoders.BASE64URL.encode(Strings.utf8('{"alg":"none"}')) + jwt += ".F!3!#." // <-- invalid Base64URL payload + try { + Jwts.parser().enableUnsecured().build().parseClaimsJwt(jwt) + fail() + } catch (MalformedJwtException expected) { + String msg = 'Invalid Base64Url payload: ' + assertEquals msg, expected.message + } + } } diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/JwtDeserializerTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/JwtDeserializerTest.groovy deleted file mode 100644 index 1e05c50b1..000000000 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/JwtDeserializerTest.groovy +++ /dev/null @@ -1,78 +0,0 @@ -/* - * 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.impl - -import io.jsonwebtoken.MalformedJwtException -import io.jsonwebtoken.io.DeserializationException -import io.jsonwebtoken.io.Deserializer -import io.jsonwebtoken.io.IOException -import org.junit.Assert -import org.junit.Test - -import java.nio.charset.StandardCharsets - -import static org.easymock.EasyMock.expect -import static org.easymock.EasyMock.mock -import static org.easymock.EasyMock.replay -import static org.junit.Assert.assertEquals - -class JwtDeserializerTest { - - /** - * It's common for JSON parsers to throw a StackOverflowError when body is deeply nested. Since it's common - * across multiple parsers, JJWT handles the exception when parsing. - */ - @Test - void testParserStackOverflowError() { - - String json = '{"test": "testParserStackOverflowError"}' - byte[] jsonBytes = json.getBytes(StandardCharsets.UTF_8) - - // create a Deserializer that will throw a StackOverflowError - Deserializer> deserializer = mock(Deserializer) - expect(deserializer.deserialize(jsonBytes)).andThrow(new StackOverflowError("Test exception: testParserStackOverflowError" )) - replay(deserializer) - - try { - new JwtDeserializer<>(deserializer).deserialize(jsonBytes) - Assert.fail("Expected IOException") - } catch (IOException e) { - assertEquals JwtDeserializer.MALFORMED_COMPLEX_ERROR + json, e.message - } - } - - /** - * Check that a DeserializationException is wrapped and rethrown as a MalformedJwtException with a developer friendly message. - */ - @Test - void testDeserializationExceptionMessage() { - - String json = '{"test": "testDeserializationExceptionMessage"}' - byte[] jsonBytes = json.getBytes(StandardCharsets.UTF_8) - - // create a Deserializer that will throw a DeserializationException - Deserializer> deserializer = mock(Deserializer) - expect(deserializer.deserialize(jsonBytes)).andThrow(new DeserializationException("Test exception: testDeserializationExceptionMessage" )) - replay(deserializer) - - try { - new JwtDeserializer<>(deserializer).deserialize(jsonBytes) - Assert.fail("Expected MalformedJwtException") - } catch (MalformedJwtException e) { - assertEquals JwtDeserializer.MALFORMED_ERROR + json, e.message - } - } -} diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/JwtTokenizerTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/JwtTokenizerTest.groovy index 1fec882b1..548621173 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/JwtTokenizerTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/JwtTokenizerTest.groovy @@ -16,40 +16,61 @@ package io.jsonwebtoken.impl import io.jsonwebtoken.MalformedJwtException +import org.junit.Before import org.junit.Test +import java.nio.CharBuffer + import static org.junit.Assert.* class JwtTokenizerTest { - @Test(expected= MalformedJwtException) + private JwtTokenizer tokenizer + + @Before + void setUp() { + tokenizer = new JwtTokenizer() + } + + @Test(expected = MalformedJwtException) void testParseWithWhitespaceInBase64UrlHeader() { def input = 'header .body.signature' - new JwtTokenizer().tokenize(input) + tokenizer.tokenize(input) } - @Test(expected= MalformedJwtException) + @Test(expected = MalformedJwtException) void testParseWithWhitespaceInBase64UrlBody() { def input = 'header. body.signature' - new JwtTokenizer().tokenize(input) + tokenizer.tokenize(input) } - @Test(expected= MalformedJwtException) + @Test(expected = MalformedJwtException) void testParseWithWhitespaceInBase64UrlSignature() { def input = 'header.body. signature' - new JwtTokenizer().tokenize(input) + tokenizer.tokenize(input) } - @Test(expected= MalformedJwtException) + @Test(expected = MalformedJwtException) void testParseWithWhitespaceInBase64UrlJweBody() { def input = 'header.encryptedKey.initializationVector. body.authenticationTag' - new JwtTokenizer().tokenize(input) + tokenizer.tokenize(input) } - @Test(expected= MalformedJwtException) + @Test(expected = MalformedJwtException) void testParseWithWhitespaceInBase64UrlJweTag() { def input = 'header.encryptedKey.initializationVector.body. authenticationTag' - new JwtTokenizer().tokenize(input) + tokenizer.tokenize(input) + } + + @Test + void testEmptyJws() { + def input = CharBuffer.wrap('header..digest'.toCharArray()) + def t = tokenizer.tokenize(input) + assertTrue t instanceof TokenizedJwt + assertFalse t instanceof TokenizedJwe + assertEquals 'header', t.getProtected().toString() + assertEquals '', t.getPayload().toString() + assertEquals 'digest', t.getDigest().toString() } @Test @@ -57,11 +78,11 @@ class JwtTokenizerTest { def input = 'header.encryptedKey.initializationVector.body.authenticationTag' - def t = new JwtTokenizer().tokenize(input) + def t = tokenizer.tokenize(input) assertNotNull t assertTrue t instanceof TokenizedJwe - TokenizedJwe tjwe = (TokenizedJwe)t + TokenizedJwe tjwe = (TokenizedJwe) t assertEquals 'header', tjwe.getProtected() assertEquals 'encryptedKey', tjwe.getEncryptedKey() assertEquals 'initializationVector', tjwe.getIv() diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/io/CodecTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/io/CodecTest.groovy index 681400bcc..e7ed598dc 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/io/CodecTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/io/CodecTest.groovy @@ -24,7 +24,7 @@ class CodecTest { @Test void testDecodingExceptionThrowsIAE() { - String s = 't#t' + CharSequence s = 't#t' try { Codec.BASE64URL.applyFrom(s) fail() diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/io/JwtDeserializerTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/io/JwtDeserializerTest.groovy new file mode 100644 index 000000000..0b54a1803 --- /dev/null +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/io/JwtDeserializerTest.groovy @@ -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.impl.io + +import io.jsonwebtoken.MalformedJwtException +import io.jsonwebtoken.impl.lang.Bytes +import io.jsonwebtoken.io.DeserializationException +import io.jsonwebtoken.io.Reader +import org.junit.Test + +import static org.junit.Assert.* + +class JwtDeserializerTest { + + /** + * It's possible for JSON parsers to throw a StackOverflowError when body is deeply nested. Since it's possible + * across multiple parsers, JJWT handles the exception when parsing.*/ + @Test + void testStackOverflowError() { + def err = new StackOverflowError('foo') + // create a Reader that will throw a StackOverflowError + def reader = new Reader() { + @Override + Object read(java.io.Reader reader) throws IOException { + throw err + } + } + try { + // doesn't matter for this test, just has to be non-null: + def r = new InputStreamReader(new ByteArrayInputStream(Bytes.EMPTY)) + new JwtDeserializer<>(reader, 'claims').apply(r) + fail() + } catch (DeserializationException e) { + String msg = String.format(JwtDeserializer.MALFORMED_COMPLEX_ERROR, 'claims', 'foo') + assertEquals msg, e.message + } + } + + /** + * Check that a DeserializationException is wrapped and rethrown as a MalformedJwtException with a developer friendly message.*/ + @Test + void testDeserializationExceptionMessage() { + def ex = new IOException('foo') + // create a Reader that will throw a StackOverflowError + def reader = new Reader() { + @Override + Object read(java.io.Reader reader) throws IOException { + throw ex + } + } + try { + // doesn't matter for this test, just has to be non-null: + def r = new InputStreamReader(new ByteArrayInputStream(Bytes.EMPTY)) + new JwtDeserializer<>(reader, 'claims').apply(r) + fail() + } catch (MalformedJwtException e) { + String msg = String.format(JwtDeserializer.MALFORMED_ERROR, 'claims', 'foo') + assertEquals msg, e.message + assertSame ex, e.cause + } + } +} diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/security/RFC7517AppendixCTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/security/RFC7517AppendixCTest.groovy index edef0af96..73e45fb40 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/security/RFC7517AppendixCTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/security/RFC7517AppendixCTest.groovy @@ -19,8 +19,6 @@ import io.jsonwebtoken.Jwe import io.jsonwebtoken.JweHeader import io.jsonwebtoken.Jwts import io.jsonwebtoken.io.Encoders -import io.jsonwebtoken.io.SerializationException -import io.jsonwebtoken.io.Serializer import io.jsonwebtoken.security.* import org.junit.Test @@ -299,9 +297,9 @@ class RFC7517AppendixCTest { return RFC_P2S } } - def serializer = new Serializer() { + def writer = new io.jsonwebtoken.io.Writer() { @Override - byte[] serialize(Object o) throws SerializationException { + void write(Writer out, Object o) throws IOException { assertTrue o instanceof JweHeader JweHeader header = (JweHeader) o @@ -315,7 +313,7 @@ class RFC7517AppendixCTest { //JSON serialization order isn't guaranteed, so now that we've asserted the values are correct, //return the exact serialization order expected in the RFC test: - return RFC_JWE_PROTECTED_HEADER_JSON.getBytes(StandardCharsets.UTF_8) + out.write(RFC_JWE_PROTECTED_HEADER_JSON) } } @@ -325,7 +323,7 @@ class RFC7517AppendixCTest { .setPayload(RFC_JWK_JSON) .header().contentType('jwk+json').pbes2Count(RFC_P2C).and() .encryptWith(key, alg, enc) - .serializer(serializer) //ensure header created as expected with an assertion serializer + .jsonWriter(writer) //ensure header created as expected with an assertion serializer .compact() assertEquals RFC_COMPACT_JWE, compact diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/security/RFC7520Section4Test.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/security/RFC7520Section4Test.groovy index 20dc16a59..057eb9504 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/security/RFC7520Section4Test.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/security/RFC7520Section4Test.groovy @@ -15,11 +15,10 @@ */ package io.jsonwebtoken.impl.security + import io.jsonwebtoken.Jwts import io.jsonwebtoken.io.Decoders import io.jsonwebtoken.io.Encoders -import io.jsonwebtoken.io.SerializationException -import io.jsonwebtoken.io.Serializer import io.jsonwebtoken.lang.Strings import io.jsonwebtoken.security.EcPrivateJwk import io.jsonwebtoken.security.Jwks @@ -204,21 +203,21 @@ class RFC7520Section4Test { def alg = Jwts.SIG.RS256 // because Maps are not guaranteed to have the same order as defined in the RFC, we create an asserting - // serializer here to check the constructed data, and then, after guaranteeing the same data, return + // writer here to check the constructed data, and then, after guaranteeing the same data, return // the order expected by the RFC - def serializer = new Serializer>() { + def writer = new io.jsonwebtoken.io.Writer>() { @Override - byte[] serialize(Map m) throws SerializationException { + void write(Writer out, Map m) throws IOException { assertEquals 2, m.size() assertEquals alg.getId(), m.get('alg') assertEquals jwk.getId(), m.get('kid') - //everything has been asserted per the RFC - return the exact order as shown in the RFC: - return utf8(FIGURE_9) + //everything has been asserted per the RFC - write the exact order as shown in the RFC: + out.write(FIGURE_9) } } String result = Jwts.builder() - .serializer(serializer) // assert input, return RFC ordered string + .jsonWriter(writer) // assert input, return RFC ordered string .header().keyId(jwk.getId()).and() .setPayload(FIGURE_7) .signWith(key, alg) @@ -243,21 +242,21 @@ class RFC7520Section4Test { String kid = 'bilbo.baggins@hobbiton.example' // because Maps are not guaranteed to have the same order as defined in the RFC, we create an asserting - // serializer here to check the constructed data, and then, after guaranteeing the same data, return + // writer here to check the constructed data, and then, after guaranteeing the same data, return // the order expected by the RFC - def serializer = new Serializer>() { + def writer = new io.jsonwebtoken.io.Writer>() { @Override - byte[] serialize(Map m) throws SerializationException { + void write(Writer out, Map m) throws IOException { assertEquals 2, m.size() assertEquals alg.getId(), m.get('alg') assertEquals kid, m.get('kid') //everything has been asserted per the RFC - return the exact order as shown in the RFC: - return utf8(FIGURE_16) + out.write(FIGURE_16) } } String result = Jwts.builder() - .serializer(serializer) // assert input, return RFC ordered string + .jsonWriter(writer) // assert input, return RFC ordered string .header().keyId(kid).and() .setPayload(FIGURE_7) .signWith(key, alg) @@ -289,21 +288,21 @@ class RFC7520Section4Test { def alg = Jwts.SIG.ES512 // because Maps are not guaranteed to have the same order as defined in the RFC, we create an asserting - // serializer here to check the constructed data, and then, after guaranteeing the same data, return + // writer here to check the constructed data, and then, after guaranteeing the same data, return // the order expected by the RFC - def serializer = new Serializer>() { + def writer = new io.jsonwebtoken.io.Writer>() { @Override - byte[] serialize(Map m) throws SerializationException { + void write(Writer out, Map m) throws IOException { assertEquals 2, m.size() assertEquals alg.getId(), m.get('alg') assertEquals jwk.getId(), m.get('kid') //everything has been asserted per the RFC - return the exact order as shown in the RFC: - return utf8(FIGURE_23) + out.write(FIGURE_23) } } String result = Jwts.builder() - .serializer(serializer) // assert input, return RFC ordered string + .jsonWriter(writer) // assert input, return RFC ordered string .header().keyId(jwk.getId()).and() .setPayload(FIGURE_7) .signWith(key, alg) @@ -334,21 +333,21 @@ class RFC7520Section4Test { def alg = Jwts.SIG.HS256 // because Maps are not guaranteed to have the same order as defined in the RFC, we create an asserting - // serializer here to check the constructed data, and then, after guaranteeing the same data, return + // writer here to check the constructed data, and then, after guaranteeing the same data, return // the order expected by the RFC - def serializer = new Serializer>() { + def writer = new io.jsonwebtoken.io.Writer>() { @Override - byte[] serialize(Map m) throws SerializationException { + void write(Writer out, Map m) throws IOException { assertEquals 2, m.size() assertEquals alg.getId(), m.get('alg') assertEquals jwk.getId(), m.get('kid') //everything has been asserted per the RFC - return the exact order as shown in the RFC: - return utf8(FIGURE_30) + out.write(FIGURE_30) } } String result = Jwts.builder() - .serializer(serializer) // assert input, return RFC ordered string + .jsonWriter(writer) // assert input, return RFC ordered string .header().keyId(jwk.getId()).and() .setPayload(FIGURE_7) .signWith(key, alg) @@ -372,21 +371,21 @@ class RFC7520Section4Test { def alg = Jwts.SIG.HS256 // because Maps are not guaranteed to have the same order as defined in the RFC, we create an asserting - // serializer here to check the constructed data, and then, after guaranteeing the same data, return + // writer here to check the constructed data, and then, after guaranteeing the same data, return // the order expected by the RFC - def serializer = new Serializer>() { + def writer = new io.jsonwebtoken.io.Writer>() { @Override - byte[] serialize(Map m) throws SerializationException { + void write(Writer out, Map m) throws IOException { assertEquals 2, m.size() assertEquals alg.getId(), m.get('alg') assertEquals jwk.getId(), m.get('kid') //everything has been asserted per the RFC - return the exact order as shown in the RFC: - return utf8(FIGURE_37) + out.write(FIGURE_37) } } String result = Jwts.builder() - .serializer(serializer) // assert input, return RFC ordered string + .jsonWriter(writer) // assert input, return RFC ordered string .header().keyId(jwk.getId()).and() .setPayload(FIGURE_7) .signWith(key, alg) diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/security/RFC7520Section5Test.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/security/RFC7520Section5Test.groovy index 309e21a10..ffa293617 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/security/RFC7520Section5Test.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/security/RFC7520Section5Test.groovy @@ -19,8 +19,6 @@ import io.jsonwebtoken.Jwts import io.jsonwebtoken.impl.lang.CheckedFunction import io.jsonwebtoken.io.Decoders import io.jsonwebtoken.io.Encoders -import io.jsonwebtoken.io.SerializationException -import io.jsonwebtoken.io.Serializer import io.jsonwebtoken.lang.Strings import io.jsonwebtoken.security.* import org.junit.Test @@ -458,22 +456,22 @@ class RFC7520Section5Test { } // because Maps are not guaranteed to have the same order as defined in the RFC, we create an asserting - // serializer here to check the constructed data, and then, after guaranteeing the same data, return + // writer here to check the constructed data, and then, after guaranteeing the same data, return // the order expected by the RFC - def serializer = new Serializer>() { + def writer = new io.jsonwebtoken.io.Writer>() { @Override - byte[] serialize(Map m) throws SerializationException { + void write(Writer out, Map m) throws IOException { assertEquals 3, m.size() assertEquals alg.getId(), m.get('alg') assertEquals jwk.getId(), m.get('kid') assertEquals enc.getId(), m.get('enc') //everything has been asserted per the RFC - return the exact order as shown in the RFC: - return utf8(FIGURE_77) + out.write(FIGURE_77) } } String result = Jwts.builder() - .serializer(serializer) // assert input, return RFC ordered string + .jsonWriter(writer) // assert input, return RFC ordered string .header().keyId(jwk.getId()).and() .setPayload(FIGURE_72) .encryptWith(key, alg, enc) @@ -522,22 +520,22 @@ class RFC7520Section5Test { } // because Maps are not guaranteed to have the same order as defined in the RFC, we create an asserting - // serializer here to check the constructed data, and then, after guaranteeing the same data, return + // writer here to check the constructed data, and then, after guaranteeing the same data, return // the order expected by the RFC - def serializer = new Serializer>() { + def writer = new io.jsonwebtoken.io.Writer>() { @Override - byte[] serialize(Map m) throws SerializationException { + void write(Writer out, Map m) throws IOException { assertEquals 3, m.size() assertEquals alg.getId(), m.get('alg') assertEquals jwk.getId(), m.get('kid') assertEquals enc.getId(), m.get('enc') //everything has been asserted per the RFC - return the exact order as shown in the RFC: - return utf8(FIGURE_88) + out.write(FIGURE_88) } } String result = Jwts.builder() - .serializer(serializer) // assert input, return RFC ordered string + .jsonWriter(writer) // assert input, return RFC ordered string .header().keyId(jwk.getId()).and() .setPayload(FIGURE_72) .encryptWith(key, alg, enc) @@ -581,11 +579,11 @@ class RFC7520Section5Test { } // because Maps are not guaranteed to have the same order as defined in the RFC, we create an asserting - // serializer here to check the constructed data, and then, after guaranteeing the same data, return + // writer here to check the constructed data, and then, after guaranteeing the same data, return // the order expected by the RFC - def serializer = new Serializer>() { + def writer = new io.jsonwebtoken.io.Writer>() { @Override - byte[] serialize(Map m) throws SerializationException { + void write(Writer out, Map m) throws IOException { assertEquals 5, m.size() assertEquals alg.getId(), m.get('alg') assertEquals FIGURE_99, m.get('p2s') @@ -593,12 +591,12 @@ class RFC7520Section5Test { assertEquals cty, m.get('cty') assertEquals enc.getId(), m.get('enc') //everything has been asserted per the RFC - return the exact order as shown in the RFC: - return utf8(FIGURE_101) + out.write(FIGURE_101) } } String result = Jwts.builder() - .serializer(serializer) // assert input, return RFC ordered string + .jsonWriter(writer) // assert input, return RFC ordered string .header().contentType(cty).pbes2Count(p2c).and() .setPayload(FIGURE_95) .encryptWith(key, alg, enc) @@ -647,23 +645,23 @@ class RFC7520Section5Test { } // because Maps are not guaranteed to have the same order as defined in the RFC, we create an asserting - // serializer here to check the constructed data, and then, after guaranteeing the same data, return + // writer here to check the constructed data, and then, after guaranteeing the same data, return // the order expected by the RFC - def serializer = new Serializer>() { + def writer = new io.jsonwebtoken.io.Writer>() { @Override - byte[] serialize(Map m) throws SerializationException { + void write(Writer out, Map m) throws IOException { assertEquals 4, m.size() assertEquals alg.getId(), m.get('alg') assertEquals jwk.getId(), m.get('kid') assertEquals enc.getId(), m.get('enc') assertEquals RFC_EPK.toPublicJwk(), m.get('epk') //everything has been asserted per the RFC - return the exact order as shown in the RFC: - return utf8(FIGURE_113) + out.write(FIGURE_113) } } String result = Jwts.builder() - .serializer(serializer) // assert input, return RFC ordered string + .jsonWriter(writer) // assert input, return RFC ordered string .header().keyId(jwk.getId()).and() .setPayload(FIGURE_72) .encryptWith(encKey, alg, enc)