- jdk8
+ nonJDK7
- 1.8
+ [1.8,)
-
+
-Xdoclint:none
diff --git a/src/main/java/io/jsonwebtoken/CompressionCodec.java b/src/main/java/io/jsonwebtoken/CompressionCodec.java
index b1b3dd6cc..b17153d3c 100644
--- a/src/main/java/io/jsonwebtoken/CompressionCodec.java
+++ b/src/main/java/io/jsonwebtoken/CompressionCodec.java
@@ -25,9 +25,9 @@
public interface CompressionCodec {
/**
- * The algorithm name to use as the JWT's {@code calg} header value.
+ * The algorithm name to use as the JWT's {@code zip} header value.
*
- * @return the algorithm name to use as the JWT's {@code calg} header value.
+ * @return the algorithm name to use as the JWT's {@code zip} header value.
*/
String getAlgorithmName();
diff --git a/src/main/java/io/jsonwebtoken/CompressionCodecResolver.java b/src/main/java/io/jsonwebtoken/CompressionCodecResolver.java
index afb2e82a8..65dc7980c 100644
--- a/src/main/java/io/jsonwebtoken/CompressionCodecResolver.java
+++ b/src/main/java/io/jsonwebtoken/CompressionCodecResolver.java
@@ -16,7 +16,7 @@
package io.jsonwebtoken;
/**
- * Looks for a JWT {@code calg} header, and if found, returns the corresponding {@link CompressionCodec} the parser
+ * Looks for a JWT {@code zip} header, and if found, returns the corresponding {@link CompressionCodec} the parser
* can use to decompress the JWT body.
*
* JJWT's default {@link JwtParser} implementation supports both the
@@ -34,12 +34,12 @@
public interface CompressionCodecResolver {
/**
- * Looks for a JWT {@code calg} header, and if found, returns the corresponding {@link CompressionCodec} the parser
+ * Looks for a JWT {@code zip} header, and if found, returns the corresponding {@link CompressionCodec} the parser
* can use to decompress the JWT body.
*
* @param header of the JWT
- * @return CompressionCodec matching the {@code calg} header, or null if there is no {@code calg} header.
- * @throws CompressionException if a {@code calg} header value is found and not supported.
+ * @return CompressionCodec matching the {@code zip} header, or null if there is no {@code zip} header.
+ * @throws CompressionException if a {@code zip} header value is found and not supported.
*/
CompressionCodec resolveCompressionCodec(Header header) throws CompressionException;
diff --git a/src/main/java/io/jsonwebtoken/Header.java b/src/main/java/io/jsonwebtoken/Header.java
index 1e7687bca..589af0d55 100644
--- a/src/main/java/io/jsonwebtoken/Header.java
+++ b/src/main/java/io/jsonwebtoken/Header.java
@@ -109,24 +109,24 @@ public interface Header> extends Map {
T setContentType(String cty);
/**
- * Returns the JWT calg
(Compression Algorithm) header value or {@code null} if not present.
+ * Returns the JWT zip
(Compression Algorithm) header value or {@code null} if not present.
*
- * @return the {@code calg} header parameter value or {@code null} if not present.
+ * @return the {@code zip} header parameter value or {@code null} if not present.
* @since 0.6.0
*/
String getCompressionAlgorithm();
/**
- * Sets the JWT calg
(Compression Algorithm) header parameter value. A {@code null} value will remove
+ * Sets the JWT zip
(Compression Algorithm) header parameter value. A {@code null} value will remove
* the property from the JSON map.
*
*
The compression algorithm is NOT part of the JWT specification
* and must be used carefully since, is not expected that other libraries (including previous versions of this one)
* be able to deserialize a compressed JTW body correctly.
*
- * @param calg the JWT compression algorithm {@code calg} value or {@code null} to remove the property from the JSON map.
+ * @param zip the JWT compression algorithm {@code zip} value or {@code null} to remove the property from the JSON map.
* @since 0.6.0
*/
- T setCompressionAlgorithm(String calg);
+ T setCompressionAlgorithm(String zip);
}
diff --git a/src/main/java/io/jsonwebtoken/JwtBuilder.java b/src/main/java/io/jsonwebtoken/JwtBuilder.java
index 5626218cc..bff3f1521 100644
--- a/src/main/java/io/jsonwebtoken/JwtBuilder.java
+++ b/src/main/java/io/jsonwebtoken/JwtBuilder.java
@@ -15,6 +15,8 @@
*/
package io.jsonwebtoken;
+import io.jsonwebtoken.codec.Encoder;
+
import java.security.Key;
import java.util.Date;
import java.util.Map;
@@ -345,11 +347,36 @@ public interface JwtBuilder extends ClaimsMutator {
* This is a convenience method: the string argument is first BASE64-decoded to a byte array and this resulting
* byte array is used to invoke {@link #signWith(SignatureAlgorithm, byte[])}.
*
+ * Deprecation Notice: Deprecated as of 0.10.0, will be removed in 1.0.0
+ *
+ * This method has been deprecated because the {@code key} argument for this method can be confusing: keys for
+ * cryptographic operations are always binary (byte arrays), and many people were confused as to how bytes were
+ * obtained from the String argument.
+ *
+ * This method always expected a String argument that was effectively the same as the result of the following
+ * (pseudocode):
+ *
+ * {@code String base64EncodedSecretKey = base64Encode(secretKeyBytes);}
+ *
+ * However, a non-trivial number of JJWT users were confused by the method signature and attempted to
+ * use raw password strings as the key argument - for example {@code signWith(HS256, myPassword)} - which is
+ * almost always incorrect for cryptographic hashes and can produce erroneous or insecure results.
+ *
+ * See this
+ *
+ * StackOverflow answer explaining why raw (non-base64-encoded) strings are almost always incorrect for
+ * signature operations.
+ *
+ * Finally, please use the {@link #signWith(SignatureAlgorithm, Key)} method, as this method and the
+ * {@code byte[]} variant will be removed before the 1.0.0 release.
+ *
* @param alg the JWS algorithm to use to digitally sign the JWT, thereby producing a JWS.
* @param base64EncodedSecretKey the BASE64-encoded algorithm-specific signing key to use to digitally sign the
* JWT.
* @return the builder for method chaining.
+ * @deprecated as of 0.10.0 - use {@link #signWith(SignatureAlgorithm, Key)} instead.
*/
+ @Deprecated
JwtBuilder signWith(SignatureAlgorithm alg, String base64EncodedSecretKey);
/**
@@ -387,6 +414,18 @@ public interface JwtBuilder extends ClaimsMutator {
*/
JwtBuilder compressWith(CompressionCodec codec);
+ /**
+ * Perform Base64Url encoding with the specified Encoder.
+ *
+ * JJWT uses a spec-compliant encoder that works on all supported JDK versions, but you may call this method
+ * to specify a different encoder if you desire.
+ *
+ * @param base64UrlEncoder the encoder to use when Base64Url-encoding
+ * @return the builder for method chaining.
+ * @since 0.10.0
+ */
+ JwtBuilder base64UrlEncodeWith(Encoder base64UrlEncoder);
+
/**
* Actually builds the JWT and serializes it to a compact, URL-safe string according to the
* JWT Compact Serialization
diff --git a/src/main/java/io/jsonwebtoken/JwtParser.java b/src/main/java/io/jsonwebtoken/JwtParser.java
index 1dcdcf58f..b5283bb0e 100644
--- a/src/main/java/io/jsonwebtoken/JwtParser.java
+++ b/src/main/java/io/jsonwebtoken/JwtParser.java
@@ -15,6 +15,7 @@
*/
package io.jsonwebtoken;
+import io.jsonwebtoken.codec.Decoder;
import io.jsonwebtoken.impl.DefaultClock;
import java.security.Key;
@@ -164,20 +165,43 @@ public interface JwtParser {
/**
* Sets the signing key used to verify any discovered JWS digital signature. If the specified JWT string is not
* a JWS (no signature), this key is not used.
- *
+ *
*
Note that this key MUST be a valid key for the signature algorithm found in the JWT header
* (as the {@code alg} header parameter).
- *
+ *
*
This method overwrites any previously set key.
- *
+ *
*
This is a convenience method: the string argument is first BASE64-decoded to a byte array and this resulting
* byte array is used to invoke {@link #setSigningKey(byte[])}.
*
- * @param base64EncodedKeyBytes the BASE64-encoded algorithm-specific signature verification key to use to validate
+ * Deprecation Notice: Deprecated as of 0.10.0, will be removed in 1.0.0
+ *
+ * This method has been deprecated because the {@code key} argument for this method can be confusing: keys for
+ * cryptographic operations are always binary (byte arrays), and many people were confused as to how bytes were
+ * obtained from the String argument.
+ *
+ * This method always expected a String argument that was effectively the same as the result of the following
+ * (pseudocode):
+ *
+ * {@code String base64EncodedSecretKey = base64Encode(secretKeyBytes);}
+ *
+ * However, a non-trivial number of JJWT users were confused by the method signature and attempted to
+ * use raw password strings as the key argument - for example {@code setSigningKey(myPassword)} - which is
+ * almost always incorrect for cryptographic hashes and can produce erroneous or insecure results.
+ *
+ * See this
+ *
+ * StackOverflow answer explaining why raw (non-base64-encoded) strings are almost always incorrect for
+ * signature operations.
+ *
+ * Finally, please use the {@link #setSigningKey(Key) setSigningKey(Key)} instead, as this method and the
+ * {@code byte[]} variant will be removed before the 1.0.0 release.
+ *
+ * @param base64EncodedSecretKey the BASE64-encoded algorithm-specific signature verification key to use to validate
* any discovered JWS digital signature.
* @return the parser for method chaining.
*/
- JwtParser setSigningKey(String base64EncodedKeyBytes);
+ JwtParser setSigningKey(String base64EncodedSecretKey);
/**
* Sets the signing key used to verify any discovered JWS digital signature. If the specified JWT string is not
@@ -246,6 +270,18 @@ public interface JwtParser {
*/
JwtParser setCompressionCodecResolver(CompressionCodecResolver compressionCodecResolver);
+ /**
+ * Perform Base64Url decoding with the specified Decoder
+ *
+ * JJWT uses a spec-compliant decoder that works on all supported JDK versions, but you may call this method
+ * to specify a different decoder if you desire.
+ *
+ * @param base64UrlDecoder the decoder to use when Base64Url-decoding
+ * @return the parser for method chaining.
+ * @since 0.10.0
+ */
+ JwtParser base64UrlDecodeWith(Decoder base64UrlDecoder);
+
/**
* Returns {@code true} if the specified JWT compact string represents a signed JWT (aka a 'JWS'), {@code false}
* otherwise.
diff --git a/src/main/java/io/jsonwebtoken/codec/CodecException.java b/src/main/java/io/jsonwebtoken/codec/CodecException.java
new file mode 100644
index 000000000..aa2743285
--- /dev/null
+++ b/src/main/java/io/jsonwebtoken/codec/CodecException.java
@@ -0,0 +1,13 @@
+package io.jsonwebtoken.codec;
+
+import io.jsonwebtoken.JwtException;
+
+/**
+ * @since 0.10.0
+ */
+public class CodecException extends JwtException {
+
+ public CodecException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/src/main/java/io/jsonwebtoken/codec/Decoder.java b/src/main/java/io/jsonwebtoken/codec/Decoder.java
new file mode 100644
index 000000000..591ab5a18
--- /dev/null
+++ b/src/main/java/io/jsonwebtoken/codec/Decoder.java
@@ -0,0 +1,18 @@
+package io.jsonwebtoken.codec;
+
+import io.jsonwebtoken.codec.impl.Base64Decoder;
+import io.jsonwebtoken.codec.impl.Base64UrlDecoder;
+import io.jsonwebtoken.codec.impl.ExceptionPropagatingDecoder;
+
+/**
+ * @param
+ * @param
+ * @since 0.10.0
+ */
+public interface Decoder {
+
+ Decoder BASE64 = new ExceptionPropagatingDecoder<>(new Base64Decoder());
+ Decoder BASE64URL = new ExceptionPropagatingDecoder<>(new Base64UrlDecoder());
+
+ R decode(T t) throws DecodingException;
+}
diff --git a/src/main/java/io/jsonwebtoken/codec/DecodingException.java b/src/main/java/io/jsonwebtoken/codec/DecodingException.java
new file mode 100644
index 000000000..a3a8446e9
--- /dev/null
+++ b/src/main/java/io/jsonwebtoken/codec/DecodingException.java
@@ -0,0 +1,11 @@
+package io.jsonwebtoken.codec;
+
+/**
+ * @since 0.10.0
+ */
+public class DecodingException extends CodecException {
+
+ public DecodingException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/src/main/java/io/jsonwebtoken/codec/Encoder.java b/src/main/java/io/jsonwebtoken/codec/Encoder.java
new file mode 100644
index 000000000..e6c070c4e
--- /dev/null
+++ b/src/main/java/io/jsonwebtoken/codec/Encoder.java
@@ -0,0 +1,19 @@
+package io.jsonwebtoken.codec;
+
+import io.jsonwebtoken.codec.impl.Base64Encoder;
+import io.jsonwebtoken.codec.impl.Base64UrlEncoder;
+import io.jsonwebtoken.codec.impl.ExceptionPropagatingEncoder;
+
+/**
+ * @param
+ * @param
+ * @since 0.10.0
+ */
+public interface Encoder {
+
+ Encoder BASE64 = new ExceptionPropagatingEncoder<>(new Base64Encoder());
+ Encoder BASE64URL = new ExceptionPropagatingEncoder<>(new Base64UrlEncoder());
+
+ R encode(T t) throws EncodingException;
+
+}
diff --git a/src/main/java/io/jsonwebtoken/codec/EncodingException.java b/src/main/java/io/jsonwebtoken/codec/EncodingException.java
new file mode 100644
index 000000000..d538d12b2
--- /dev/null
+++ b/src/main/java/io/jsonwebtoken/codec/EncodingException.java
@@ -0,0 +1,11 @@
+package io.jsonwebtoken.codec;
+
+/**
+ * @since 0.10.0
+ */
+public class EncodingException extends CodecException {
+
+ public EncodingException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/src/main/java/io/jsonwebtoken/codec/impl/Base64.java b/src/main/java/io/jsonwebtoken/codec/impl/Base64.java
new file mode 100644
index 000000000..238b35f96
--- /dev/null
+++ b/src/main/java/io/jsonwebtoken/codec/impl/Base64.java
@@ -0,0 +1,649 @@
+package io.jsonwebtoken.codec.impl;
+
+import java.util.Arrays;
+
+/**
+ * A very fast and memory efficient class to encode and decode to and from BASE64 or BASE64URL in full accordance
+ * with RFC 4648.
+ *
+ * Based initially on MigBase64 with continued modifications for Base64 URL support and JDK-standard code formatting.
+ *
+ * This encode/decode algorithm doesn't create any temporary arrays as many other codecs do, it only
+ * allocates the resulting array. This produces less garbage and it is possible to handle arrays twice
+ * as large as algorithms that create a temporary array.
+ *
+ * There is also a "fast" version of all decode methods that works the same way as the normal ones, but
+ * has a few demands on the decoded input. Normally though, these fast versions should be used if the source if
+ * the input is known and it hasn't bee tampered with.
+ *
+ * @author Mikael Grev
+ * @author Les Hazlewood
+ */
+@SuppressWarnings("Duplicates")
+final class Base64 { //final and package-protected on purpose
+
+ private static final char[] BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray();
+ private static final char[] BASE64URL_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".toCharArray();
+ private static final int[] BASE64_IALPHABET = new int[256];
+ private static final int[] BASE64URL_IALPHABET = new int[256];
+
+ static {
+ Arrays.fill(BASE64_IALPHABET, -1);
+ System.arraycopy(BASE64_IALPHABET, 0, BASE64URL_IALPHABET, 0, BASE64_IALPHABET.length);
+ for (int i = 0, iS = BASE64_ALPHABET.length; i < iS; i++) {
+ BASE64_IALPHABET[BASE64_ALPHABET[i]] = i;
+ BASE64URL_IALPHABET[BASE64URL_ALPHABET[i]] = i;
+ }
+ BASE64_IALPHABET['='] = 0;
+ BASE64URL_IALPHABET['='] = 0;
+ }
+
+ static final Base64 DEFAULT = new Base64(false);
+ static final Base64 URL_SAFE = new Base64(true);
+
+ private final boolean urlsafe;
+ private final char[] ALPHABET;
+ private final int[] IALPHABET;
+
+ private Base64(boolean urlsafe) {
+ this.urlsafe = urlsafe;
+ this.ALPHABET = urlsafe ? BASE64URL_ALPHABET : BASE64_ALPHABET;
+ this.IALPHABET = urlsafe ? BASE64URL_IALPHABET : BASE64_IALPHABET;
+ }
+
+ // ****************************************************************************************
+ // * char[] version
+ // ****************************************************************************************
+
+ /**
+ * Encodes a raw byte array into a BASE64 char[]
representation in accordance with RFC 2045.
+ *
+ * @param sArr The bytes to convert. If null
or length 0 an empty array will be returned.
+ * @param lineSep Optional "\r\n" after 76 characters, unless end of file.
+ * No line separator will be in breach of RFC 2045 which specifies max 76 per line but will be a
+ * little faster.
+ * @return A BASE64 encoded array. Never null
.
+ */
+ private char[] encodeToChar(byte[] sArr, boolean lineSep) {
+
+ // Check special case
+ int sLen = sArr != null ? sArr.length : 0;
+ if (sLen == 0) {
+ return new char[0];
+ }
+
+ int eLen = (sLen / 3) * 3; // # of bytes that can encode evenly into 24-bit chunks
+ int left = sLen - eLen; // # of bytes that remain after 24-bit chunking. Always 0, 1 or 2
+
+ int cCnt = (((sLen - 1) / 3 + 1) << 2); // # of base64-encoded characters including padding
+ int dLen = cCnt + (lineSep ? (cCnt - 1) / 76 << 1 : 0); // Length of returned char array with padding and any line separators
+
+ int padCount = 0;
+ if (left == 2) {
+ padCount = 1;
+ } else if (left == 1) {
+ padCount = 2;
+ }
+
+ char[] dArr = new char[urlsafe ? (dLen - padCount) : dLen];
+
+ // Encode even 24-bits
+ for (int s = 0, d = 0, cc = 0; s < eLen; ) {
+
+ // Copy next three bytes into lower 24 bits of int, paying attension to sign.
+ int i = (sArr[s++] & 0xff) << 16 | (sArr[s++] & 0xff) << 8 | (sArr[s++] & 0xff);
+
+ // Encode the int into four chars
+ dArr[d++] = ALPHABET[(i >>> 18) & 0x3f];
+ dArr[d++] = ALPHABET[(i >>> 12) & 0x3f];
+ dArr[d++] = ALPHABET[(i >>> 6) & 0x3f];
+ dArr[d++] = ALPHABET[i & 0x3f];
+
+ // Add optional line separator
+ if (lineSep && ++cc == 19 && d < dLen - 2) {
+ dArr[d++] = '\r';
+ dArr[d++] = '\n';
+ cc = 0;
+ }
+ }
+
+ // Pad and encode last bits if source isn't even 24 bits.
+ if (left > 0) {
+ // Prepare the int
+ int i = ((sArr[eLen] & 0xff) << 10) | (left == 2 ? ((sArr[sLen - 1] & 0xff) << 2) : 0);
+
+ // Set last four chars
+ dArr[dLen - 4] = ALPHABET[i >> 12];
+ dArr[dLen - 3] = ALPHABET[(i >>> 6) & 0x3f];
+ //dArr[dLen - 2] = left == 2 ? ALPHABET[i & 0x3f] : '=';
+ //dArr[dLen - 1] = '=';
+ if (left == 2) {
+ dArr[dLen - 2] = ALPHABET[i & 0x3f];
+ } else if (!urlsafe) { // if not urlsafe, we need to include the padding characters
+ dArr[dLen - 2] = '=';
+ }
+ if (!urlsafe) { // include padding
+ dArr[dLen - 1] = '=';
+ }
+ }
+ return dArr;
+ }
+
+ /*
+ * Decodes a BASE64 encoded char array. All illegal characters will be ignored and can handle both arrays with
+ * and without line separators.
+ *
+ * @param sArr The source array. null
or length 0 will return an empty array.
+ * @return The decoded array of bytes. May be of length 0. Will be null
if the legal characters
+ * (including '=') isn't divideable by 4. (I.e. definitely corrupted).
+ *
+ public final byte[] decode(char[] sArr) {
+ // Check special case
+ int sLen = sArr != null ? sArr.length : 0;
+ if (sLen == 0) {
+ return new byte[0];
+ }
+
+ // Count illegal characters (including '\r', '\n') to know what size the returned array will be,
+ // so we don't have to reallocate & copy it later.
+ int sepCnt = 0; // Number of separator characters. (Actually illegal characters, but that's a bonus...)
+ for (int i = 0; i < sLen; i++) { // If input is "pure" (I.e. no line separators or illegal chars) base64 this loop can be commented out.
+ if (IALPHABET[sArr[i]] < 0) {
+ sepCnt++;
+ }
+ }
+
+ // Check so that legal chars (including '=') are evenly divideable by 4 as specified in RFC 2045.
+ if ((sLen - sepCnt) % 4 != 0) {
+ return null;
+ }
+
+ int pad = 0;
+ for (int i = sLen; i > 1 && IALPHABET[sArr[--i]] <= 0; ) {
+ if (sArr[i] == '=') {
+ pad++;
+ }
+ }
+
+ int len = ((sLen - sepCnt) * 6 >> 3) - pad;
+
+ byte[] dArr = new byte[len]; // Preallocate byte[] of exact length
+
+ for (int s = 0, d = 0; d < len; ) {
+ // Assemble three bytes into an int from four "valid" characters.
+ int i = 0;
+ for (int j = 0; j < 4; j++) { // j only increased if a valid char was found.
+ int c = IALPHABET[sArr[s++]];
+ if (c >= 0) {
+ i |= c << (18 - j * 6);
+ } else {
+ j--;
+ }
+ }
+ // Add the bytes
+ dArr[d++] = (byte) (i >> 16);
+ if (d < len) {
+ dArr[d++] = (byte) (i >> 8);
+ if (d < len) {
+ dArr[d++] = (byte) i;
+ }
+ }
+ }
+ return dArr;
+ }
+ */
+
+ /**
+ * 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).
+ * + 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.
+ *
+ * @param sArr The source array. Length 0 will return an empty array. null
will throw an exception.
+ * @return The decoded array of bytes. May be of length 0.
+ */
+ final byte[] decodeFast(char[] sArr) {
+
+ // Check special case
+ int sLen = sArr != null ? sArr.length : 0;
+ if (sLen == 0) {
+ return new byte[0];
+ }
+
+ int sIx = 0, eIx = sLen - 1; // Start and end index after trimming.
+
+ // Trim illegal chars from start
+ while (sIx < eIx && IALPHABET[sArr[sIx]] < 0) {
+ sIx++;
+ }
+
+ // Trim illegal chars from end
+ while (eIx > 0 && IALPHABET[sArr[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 cCnt = eIx - sIx + 1; // Content count including possible separators
+ int sepCnt = sLen > 76 ? (sArr[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
+
+ // Decode all but the last 0 - 2 bytes.
+ int d = 0;
+ for (int cc = 0, eLen = (len / 3) * 3; d < eLen; ) {
+
+ // Assemble three bytes into an int from four "valid" characters.
+ int i = IALPHABET[sArr[sIx++]] << 18 | IALPHABET[sArr[sIx++]] << 12 | IALPHABET[sArr[sIx++]] << 6 | IALPHABET[sArr[sIx++]];
+
+ // Add the bytes
+ dArr[d++] = (byte) (i >> 16);
+ dArr[d++] = (byte) (i >> 8);
+ dArr[d++] = (byte) i;
+
+ // If line separator, jump over it.
+ if (sepCnt > 0 && ++cc == 19) {
+ sIx += 2;
+ cc = 0;
+ }
+ }
+
+ if (d < len) {
+ // Decode last 1-3 bytes (incl '=') into 1-3 bytes
+ int i = 0;
+ for (int j = 0; sIx <= eIx - pad; j++) {
+ i |= IALPHABET[sArr[sIx++]] << (18 - j * 6);
+ }
+
+ for (int r = 16; d < len; r -= 8) {
+ dArr[d++] = (byte) (i >> r);
+ }
+ }
+
+ return dArr;
+ }
+
+ // ****************************************************************************************
+ // * byte[] version
+ // ****************************************************************************************
+
+ /*
+ * Encodes a raw byte array into a BASE64 byte[]
representation i accordance with RFC 2045.
+ *
+ * @param sArr The bytes to convert. If null
or length 0 an empty array will be returned.
+ * @param lineSep Optional "\r\n" after 76 characters, unless end of file.
+ * No line separator will be in breach of RFC 2045 which specifies max 76 per line but will be a
+ * little faster.
+ * @return A BASE64 encoded array. Never null
.
+ *
+ public final byte[] encodeToByte(byte[] sArr, boolean lineSep) {
+ return encodeToByte(sArr, 0, sArr != null ? sArr.length : 0, lineSep);
+ }
+
+ /**
+ * Encodes a raw byte array into a BASE64 byte[]
representation i accordance with RFC 2045.
+ *
+ * @param sArr The bytes to convert. If null
an empty array will be returned.
+ * @param sOff The starting position in the bytes to convert.
+ * @param sLen The number of bytes to convert. If 0 an empty array will be returned.
+ * @param lineSep Optional "\r\n" after 76 characters, unless end of file.
+ * No line separator will be in breach of RFC 2045 which specifies max 76 per line but will be a
+ * little faster.
+ * @return A BASE64 encoded array. Never null
.
+ *
+ public final byte[] encodeToByte(byte[] sArr, int sOff, int sLen, boolean lineSep) {
+
+ // Check special case
+ if (sArr == null || sLen == 0) {
+ return new byte[0];
+ }
+
+ int eLen = (sLen / 3) * 3; // Length of even 24-bits.
+ int cCnt = ((sLen - 1) / 3 + 1) << 2; // Returned character count
+ int dLen = cCnt + (lineSep ? (cCnt - 1) / 76 << 1 : 0); // Length of returned array
+ byte[] dArr = new byte[dLen];
+
+ // Encode even 24-bits
+ for (int s = sOff, d = 0, cc = 0; s < sOff + eLen; ) {
+
+ // Copy next three bytes into lower 24 bits of int, paying attension to sign.
+ int i = (sArr[s++] & 0xff) << 16 | (sArr[s++] & 0xff) << 8 | (sArr[s++] & 0xff);
+
+ // Encode the int into four chars
+ dArr[d++] = (byte) ALPHABET[(i >>> 18) & 0x3f];
+ dArr[d++] = (byte) ALPHABET[(i >>> 12) & 0x3f];
+ dArr[d++] = (byte) ALPHABET[(i >>> 6) & 0x3f];
+ dArr[d++] = (byte) ALPHABET[i & 0x3f];
+
+ // Add optional line separator
+ if (lineSep && ++cc == 19 && d < dLen - 2) {
+ dArr[d++] = '\r';
+ dArr[d++] = '\n';
+ cc = 0;
+ }
+ }
+
+ // Pad and encode last bits if source isn't an even 24 bits.
+ int left = sLen - eLen; // 0 - 2.
+ if (left > 0) {
+ // Prepare the int
+ int i = ((sArr[sOff + eLen] & 0xff) << 10) | (left == 2 ? ((sArr[sOff + sLen - 1] & 0xff) << 2) : 0);
+
+ // Set last four chars
+ dArr[dLen - 4] = (byte) ALPHABET[i >> 12];
+ dArr[dLen - 3] = (byte) ALPHABET[(i >>> 6) & 0x3f];
+ dArr[dLen - 2] = left == 2 ? (byte) ALPHABET[i & 0x3f] : (byte) '=';
+ dArr[dLen - 1] = '=';
+ }
+ return dArr;
+ }
+
+ /**
+ * Decodes a BASE64 encoded byte array. All illegal characters will be ignored and can handle both arrays with
+ * and without line separators.
+ *
+ * @param sArr The source array. Length 0 will return an empty array. null
will throw an exception.
+ * @return The decoded array of bytes. May be of length 0. Will be null
if the legal characters
+ * (including '=') isn't divideable by 4. (I.e. definitely corrupted).
+ *
+ public final byte[] decode(byte[] sArr) {
+ return decode(sArr, 0, sArr.length);
+ }
+
+ /**
+ * Decodes a BASE64 encoded byte array. All illegal characters will be ignored and can handle both arrays with
+ * and without line separators.
+ *
+ * @param sArr The source array. null
will throw an exception.
+ * @param sOff The starting position in the source array.
+ * @param sLen The number of bytes to decode from the source array. Length 0 will return an empty array.
+ * @return The decoded array of bytes. May be of length 0. Will be null
if the legal characters
+ * (including '=') isn't divideable by 4. (I.e. definitely corrupted).
+ *
+ public final byte[] decode(byte[] sArr, int sOff, int sLen) {
+
+ // Count illegal characters (including '\r', '\n') to know what size the returned array will be,
+ // so we don't have to reallocate & copy it later.
+ int sepCnt = 0; // Number of separator characters. (Actually illegal characters, but that's a bonus...)
+ for (int i = 0; i < sLen; i++) { // If input is "pure" (I.e. no line separators or illegal chars) base64 this loop can be commented out.
+ if (IALPHABET[sArr[sOff + i] & 0xff] < 0) {
+ sepCnt++;
+ }
+ }
+
+ // Check so that legal chars (including '=') are evenly divideable by 4 as specified in RFC 2045.
+ if ((sLen - sepCnt) % 4 != 0) {
+ return null;
+ }
+
+ int pad = 0;
+ for (int i = sLen; i > 1 && IALPHABET[sArr[sOff + --i] & 0xff] <= 0; ) {
+ if (sArr[sOff + i] == '=') {
+ pad++;
+ }
+ }
+
+ int len = ((sLen - sepCnt) * 6 >> 3) - pad;
+
+ byte[] dArr = new byte[len]; // Preallocate byte[] of exact length
+
+ for (int s = 0, d = 0; d < len; ) {
+ // Assemble three bytes into an int from four "valid" characters.
+ int i = 0;
+ for (int j = 0; j < 4; j++) { // j only increased if a valid char was found.
+ int c = IALPHABET[sArr[sOff + s++] & 0xff];
+ if (c >= 0) {
+ i |= c << (18 - j * 6);
+ } else {
+ j--;
+ }
+ }
+
+ // Add the bytes
+ dArr[d++] = (byte) (i >> 16);
+ if (d < len) {
+ dArr[d++] = (byte) (i >> 8);
+ if (d < len) {
+ dArr[d++] = (byte) i;
+ }
+ }
+ }
+
+ return dArr;
+ }
+
+
+ /*
+ * Decodes a BASE64 encoded byte array that is known to be resonably well formatted. The method is about twice as
+ * fast as {@link #decode(byte[])}. The preconditions are:
+ * + The array 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.
+ *
+ * @param sArr The source array. Length 0 will return an empty array. null
will throw an exception.
+ * @return The decoded array of bytes. May be of length 0.
+ *
+ public final byte[] decodeFast(byte[] sArr) {
+
+ // Check special case
+ int sLen = sArr.length;
+ if (sLen == 0) {
+ return new byte[0];
+ }
+
+ int sIx = 0, eIx = sLen - 1; // Start and end index after trimming.
+
+ // Trim illegal chars from start
+ while (sIx < eIx && IALPHABET[sArr[sIx] & 0xff] < 0) {
+ sIx++;
+ }
+
+ // Trim illegal chars from end
+ while (eIx > 0 && IALPHABET[sArr[eIx] & 0xff] < 0) {
+ eIx--;
+ }
+
+ // get the padding count (=) (0, 1 or 2)
+ int pad = sArr[eIx] == '=' ? (sArr[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 len = ((cCnt - sepCnt) * 6 >> 3) - pad; // The number of decoded bytes
+ byte[] dArr = new byte[len]; // Preallocate byte[] of exact length
+
+ // Decode all but the last 0 - 2 bytes.
+ int d = 0;
+ for (int cc = 0, eLen = (len / 3) * 3; d < eLen; ) {
+
+ // Assemble three bytes into an int from four "valid" characters.
+ int i = IALPHABET[sArr[sIx++]] << 18 | IALPHABET[sArr[sIx++]] << 12 | IALPHABET[sArr[sIx++]] << 6 | IALPHABET[sArr[sIx++]];
+
+ // Add the bytes
+ dArr[d++] = (byte) (i >> 16);
+ dArr[d++] = (byte) (i >> 8);
+ dArr[d++] = (byte) i;
+
+ // If line separator, jump over it.
+ if (sepCnt > 0 && ++cc == 19) {
+ sIx += 2;
+ cc = 0;
+ }
+ }
+
+ if (d < len) {
+ // Decode last 1-3 bytes (incl '=') into 1-3 bytes
+ int i = 0;
+ for (int j = 0; sIx <= eIx - pad; j++) {
+ i |= IALPHABET[sArr[sIx++]] << (18 - j * 6);
+ }
+
+ for (int r = 16; d < len; r -= 8) {
+ dArr[d++] = (byte) (i >> r);
+ }
+ }
+
+ return dArr;
+ }
+ */
+
+ // ****************************************************************************************
+ // * String version
+ // ****************************************************************************************
+
+ /**
+ * Encodes a raw byte array into a BASE64 String
representation i accordance with RFC 2045.
+ *
+ * @param sArr The bytes to convert. If null
or length 0 an empty array will be returned.
+ * @param lineSep Optional "\r\n" after 76 characters, unless end of file.
+ * No line separator will be in breach of RFC 2045 which specifies max 76 per line but will be a
+ * little faster.
+ * @return A BASE64 encoded array. Never null
.
+ */
+ final 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));
+ }
+
+ /*
+ * Decodes a BASE64 encoded String
. All illegal characters will be ignored and can handle both strings with
+ * and without line separators.
+ * Note! It can be up to about 2x the speed to call decode(str.toCharArray())
instead. That
+ * will create a temporary array though. This version will use str.charAt(i)
to iterate the string.
+ *
+ * @param str The source string. null
or length 0 will return an empty array.
+ * @return The decoded array of bytes. May be of length 0. Will be null
if the legal characters
+ * (including '=') isn't divideable by 4. (I.e. definitely corrupted).
+ *
+ public final byte[] decode(String str) {
+
+ // Check special case
+ int sLen = str != null ? str.length() : 0;
+ if (sLen == 0) {
+ return new byte[0];
+ }
+
+ // Count illegal characters (including '\r', '\n') to know what size the returned array will be,
+ // so we don't have to reallocate & copy it later.
+ int sepCnt = 0; // Number of separator characters. (Actually illegal characters, but that's a bonus...)
+ for (int i = 0; i < sLen; i++) { // If input is "pure" (I.e. no line separators or illegal chars) base64 this loop can be commented out.
+ if (IALPHABET[str.charAt(i)] < 0) {
+ sepCnt++;
+ }
+ }
+
+ // Check so that legal chars (including '=') are evenly divideable by 4 as specified in RFC 2045.
+ if ((sLen - sepCnt) % 4 != 0) {
+ return null;
+ }
+
+ // Count '=' at end
+ int pad = 0;
+ for (int i = sLen; i > 1 && IALPHABET[str.charAt(--i)] <= 0; ) {
+ if (str.charAt(i) == '=') {
+ pad++;
+ }
+ }
+
+ int len = ((sLen - sepCnt) * 6 >> 3) - pad;
+
+ byte[] dArr = new byte[len]; // Preallocate byte[] of exact length
+
+ for (int s = 0, d = 0; d < len; ) {
+ // Assemble three bytes into an int from four "valid" characters.
+ int i = 0;
+ for (int j = 0; j < 4; j++) { // j only increased if a valid char was found.
+ int c = IALPHABET[str.charAt(s++)];
+ if (c >= 0) {
+ i |= c << (18 - j * 6);
+ } else {
+ j--;
+ }
+ }
+ // Add the bytes
+ dArr[d++] = (byte) (i >> 16);
+ if (d < len) {
+ dArr[d++] = (byte) (i >> 8);
+ if (d < len) {
+ dArr[d++] = (byte) i;
+ }
+ }
+ }
+ return dArr;
+ }
+
+ /**
+ * Decodes a BASE64 encoded string that is known to be resonably well formatted. The method is about twice as
+ * fast as {@link #decode(String)}. The preconditions are:
+ * + The array 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.
+ *
+ * @param s The source string. Length 0 will return an empty array. null
will throw an exception.
+ * @return The decoded array of bytes. May be of length 0.
+ *
+ public final byte[] decodeFast(String s) {
+
+ // Check special case
+ int sLen = s.length();
+ if (sLen == 0) {
+ return new byte[0];
+ }
+
+ int sIx = 0, eIx = sLen - 1; // Start and end index after trimming.
+
+ // Trim illegal chars from start
+ while (sIx < eIx && IALPHABET[s.charAt(sIx) & 0xff] < 0) {
+ sIx++;
+ }
+
+ // Trim illegal chars from end
+ while (eIx > 0 && IALPHABET[s.charAt(eIx) & 0xff] < 0) {
+ eIx--;
+ }
+
+ // get the padding count (=) (0, 1 or 2)
+ int pad = s.charAt(eIx) == '=' ? (s.charAt(eIx - 1) == '=' ? 2 : 1) : 0; // Count '=' at end.
+ int cCnt = eIx - sIx + 1; // Content count including possible separators
+ int sepCnt = sLen > 76 ? (s.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
+
+ // Decode all but the last 0 - 2 bytes.
+ int d = 0;
+ for (int cc = 0, eLen = (len / 3) * 3; d < eLen; ) {
+ // Assemble three bytes into an int from four "valid" characters.
+ int i = IALPHABET[s.charAt(sIx++)] << 18 | IALPHABET[s.charAt(sIx++)] << 12 | IALPHABET[s.charAt(sIx++)] << 6 | IALPHABET[s.charAt(sIx++)];
+
+ // Add the bytes
+ dArr[d++] = (byte) (i >> 16);
+ dArr[d++] = (byte) (i >> 8);
+ dArr[d++] = (byte) i;
+
+ // If line separator, jump over it.
+ if (sepCnt > 0 && ++cc == 19) {
+ sIx += 2;
+ cc = 0;
+ }
+ }
+
+ if (d < len) {
+ // Decode last 1-3 bytes (incl '=') into 1-3 bytes
+ int i = 0;
+ for (int j = 0; sIx <= eIx - pad; j++) {
+ i |= IALPHABET[s.charAt(sIx++)] << (18 - j * 6);
+ }
+
+ for (int r = 16; d < len; r -= 8) {
+ dArr[d++] = (byte) (i >> r);
+ }
+ }
+
+ return dArr;
+ }
+ */
+}
diff --git a/src/main/java/io/jsonwebtoken/codec/impl/Base64Decoder.java b/src/main/java/io/jsonwebtoken/codec/impl/Base64Decoder.java
new file mode 100644
index 000000000..66175d5c4
--- /dev/null
+++ b/src/main/java/io/jsonwebtoken/codec/impl/Base64Decoder.java
@@ -0,0 +1,22 @@
+package io.jsonwebtoken.codec.impl;
+
+import io.jsonwebtoken.codec.Decoder;
+import io.jsonwebtoken.codec.DecodingException;
+import io.jsonwebtoken.lang.Assert;
+
+public class Base64Decoder extends Base64Support implements Decoder {
+
+ public Base64Decoder() {
+ super(Base64.DEFAULT);
+ }
+
+ Base64Decoder(Base64 base64) {
+ super(base64);
+ }
+
+ @Override
+ public byte[] decode(String s) throws DecodingException {
+ Assert.notNull(s, "String argument cannot be null");
+ return this.base64.decodeFast(s.toCharArray());
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/io/jsonwebtoken/codec/impl/Base64Encoder.java b/src/main/java/io/jsonwebtoken/codec/impl/Base64Encoder.java
new file mode 100644
index 000000000..f6ff16767
--- /dev/null
+++ b/src/main/java/io/jsonwebtoken/codec/impl/Base64Encoder.java
@@ -0,0 +1,22 @@
+package io.jsonwebtoken.codec.impl;
+
+import io.jsonwebtoken.codec.Encoder;
+import io.jsonwebtoken.codec.EncodingException;
+import io.jsonwebtoken.lang.Assert;
+
+public class Base64Encoder extends Base64Support implements Encoder {
+
+ public Base64Encoder() {
+ super(Base64.DEFAULT);
+ }
+
+ Base64Encoder(Base64 base64) {
+ super(base64);
+ }
+
+ @Override
+ public String encode(byte[] bytes) throws EncodingException {
+ Assert.notNull(bytes, "byte array argument cannot be null");
+ return this.base64.encodeToString(bytes, false);
+ }
+}
diff --git a/src/main/java/io/jsonwebtoken/codec/impl/Base64Support.java b/src/main/java/io/jsonwebtoken/codec/impl/Base64Support.java
new file mode 100644
index 000000000..77bdd044f
--- /dev/null
+++ b/src/main/java/io/jsonwebtoken/codec/impl/Base64Support.java
@@ -0,0 +1,13 @@
+package io.jsonwebtoken.codec.impl;
+
+import io.jsonwebtoken.lang.Assert;
+
+public class Base64Support {
+
+ protected final Base64 base64;
+
+ Base64Support(Base64 base64) {
+ Assert.notNull(base64, "Base64 argument cannot be null");
+ this.base64 = base64;
+ }
+}
diff --git a/src/main/java/io/jsonwebtoken/codec/impl/Base64UrlDecoder.java b/src/main/java/io/jsonwebtoken/codec/impl/Base64UrlDecoder.java
new file mode 100644
index 000000000..1a3f70421
--- /dev/null
+++ b/src/main/java/io/jsonwebtoken/codec/impl/Base64UrlDecoder.java
@@ -0,0 +1,11 @@
+package io.jsonwebtoken.codec.impl;
+
+/**
+ * @since 0.10.0
+ */
+public class Base64UrlDecoder extends Base64Decoder {
+
+ public Base64UrlDecoder() {
+ super(Base64.URL_SAFE);
+ }
+}
diff --git a/src/main/java/io/jsonwebtoken/codec/impl/Base64UrlEncoder.java b/src/main/java/io/jsonwebtoken/codec/impl/Base64UrlEncoder.java
new file mode 100644
index 000000000..1859cc7ed
--- /dev/null
+++ b/src/main/java/io/jsonwebtoken/codec/impl/Base64UrlEncoder.java
@@ -0,0 +1,11 @@
+package io.jsonwebtoken.codec.impl;
+
+/**
+ * @since 0.10.0
+ */
+public class Base64UrlEncoder extends Base64Encoder {
+
+ public Base64UrlEncoder() {
+ super(Base64.URL_SAFE);
+ }
+}
diff --git a/src/main/java/io/jsonwebtoken/codec/impl/ExceptionPropagatingDecoder.java b/src/main/java/io/jsonwebtoken/codec/impl/ExceptionPropagatingDecoder.java
new file mode 100644
index 000000000..1e186e00b
--- /dev/null
+++ b/src/main/java/io/jsonwebtoken/codec/impl/ExceptionPropagatingDecoder.java
@@ -0,0 +1,28 @@
+package io.jsonwebtoken.codec.impl;
+
+import io.jsonwebtoken.codec.Decoder;
+import io.jsonwebtoken.codec.DecodingException;
+import io.jsonwebtoken.lang.Assert;
+
+public class ExceptionPropagatingDecoder implements Decoder {
+
+ private final Decoder decoder;
+
+ public ExceptionPropagatingDecoder(Decoder decoder) {
+ Assert.notNull(decoder, "Decoder cannot be null.");
+ this.decoder = decoder;
+ }
+
+ @Override
+ public R decode(T t) throws DecodingException {
+ Assert.notNull(t, "Decode argument cannot be null.");
+ try {
+ return decoder.decode(t);
+ } catch (DecodingException e) {
+ throw e; //propagate
+ } catch (Exception e) {
+ String msg = "Unable to decode input: " + e.getMessage();
+ throw new DecodingException(msg, e);
+ }
+ }
+}
diff --git a/src/main/java/io/jsonwebtoken/codec/impl/ExceptionPropagatingEncoder.java b/src/main/java/io/jsonwebtoken/codec/impl/ExceptionPropagatingEncoder.java
new file mode 100644
index 000000000..ab21278ea
--- /dev/null
+++ b/src/main/java/io/jsonwebtoken/codec/impl/ExceptionPropagatingEncoder.java
@@ -0,0 +1,28 @@
+package io.jsonwebtoken.codec.impl;
+
+import io.jsonwebtoken.codec.Encoder;
+import io.jsonwebtoken.codec.EncodingException;
+import io.jsonwebtoken.lang.Assert;
+
+public class ExceptionPropagatingEncoder implements Encoder {
+
+ private final Encoder encoder;
+
+ public ExceptionPropagatingEncoder(Encoder encoder) {
+ Assert.notNull(encoder, "Encoder cannot be null.");
+ this.encoder = encoder;
+ }
+
+ @Override
+ public R encode(T t) throws EncodingException {
+ Assert.notNull(t, "Encode argument cannot be null.");
+ try {
+ return this.encoder.encode(t);
+ } catch (EncodingException e) {
+ throw e; //propagate
+ } catch (Exception e) {
+ String msg = "Unable to encode input: " + e.getMessage();
+ throw new EncodingException(msg, e);
+ }
+ }
+}
diff --git a/src/main/java/io/jsonwebtoken/impl/AbstractTextCodec.java b/src/main/java/io/jsonwebtoken/impl/AbstractTextCodec.java
index 3ba757a62..1d3ddcdaa 100644
--- a/src/main/java/io/jsonwebtoken/impl/AbstractTextCodec.java
+++ b/src/main/java/io/jsonwebtoken/impl/AbstractTextCodec.java
@@ -15,13 +15,19 @@
*/
package io.jsonwebtoken.impl;
+import io.jsonwebtoken.codec.Decoder;
+import io.jsonwebtoken.codec.Encoder;
import io.jsonwebtoken.lang.Assert;
import java.nio.charset.Charset;
+/**
+ * @deprecated since 0.10.0 - will be removed before 1.0.0. Use {@link Encoder} orr {@link Decoder} instead.
+ */
+@Deprecated
public abstract class AbstractTextCodec implements TextCodec {
- protected static final Charset UTF8 = Charset.forName("UTF-8");
+ protected static final Charset UTF8 = Charset.forName("UTF-8");
protected static final Charset US_ASCII = Charset.forName("US-ASCII");
@Override
diff --git a/src/main/java/io/jsonwebtoken/impl/AndroidBase64Codec.java b/src/main/java/io/jsonwebtoken/impl/AndroidBase64Codec.java
index 8ad32c87f..96586111d 100644
--- a/src/main/java/io/jsonwebtoken/impl/AndroidBase64Codec.java
+++ b/src/main/java/io/jsonwebtoken/impl/AndroidBase64Codec.java
@@ -15,6 +15,14 @@
*/
package io.jsonwebtoken.impl;
+import io.jsonwebtoken.codec.Decoder;
+import io.jsonwebtoken.codec.Encoder;
+
+/**
+ * @deprecated since 0.10.0 - will be removed before 1.0.0. Use {@link Encoder#BASE64 Encoder.BASE64}
+ * or {@link Decoder#BASE64 Decoder.BASE64} instead.
+ */
+@Deprecated
public class AndroidBase64Codec extends AbstractTextCodec {
@Override
diff --git a/src/main/java/io/jsonwebtoken/impl/Base64Codec.java b/src/main/java/io/jsonwebtoken/impl/Base64Codec.java
index d2faf5c07..5867d4725 100644
--- a/src/main/java/io/jsonwebtoken/impl/Base64Codec.java
+++ b/src/main/java/io/jsonwebtoken/impl/Base64Codec.java
@@ -15,14 +15,22 @@
*/
package io.jsonwebtoken.impl;
+import io.jsonwebtoken.codec.Decoder;
+import io.jsonwebtoken.codec.Encoder;
+
+/**
+ * @deprecated since 0.10.0 - will be removed before 1.0.0. Use {@link Encoder#BASE64 Encoder.BASE64}
+ * or {@link Decoder#BASE64 Decoder.BASE64} instead.
+ */
+@Deprecated
public class Base64Codec extends AbstractTextCodec {
public String encode(byte[] data) {
- return javax.xml.bind.DatatypeConverter.printBase64Binary(data);
+ return Encoder.BASE64.encode(data);
}
@Override
public byte[] decode(String encoded) {
- return javax.xml.bind.DatatypeConverter.parseBase64Binary(encoded);
+ return Decoder.BASE64.decode(encoded);
}
}
diff --git a/src/main/java/io/jsonwebtoken/impl/Base64UrlCodec.java b/src/main/java/io/jsonwebtoken/impl/Base64UrlCodec.java
index ea87f514b..68d584a65 100644
--- a/src/main/java/io/jsonwebtoken/impl/Base64UrlCodec.java
+++ b/src/main/java/io/jsonwebtoken/impl/Base64UrlCodec.java
@@ -15,90 +15,23 @@
*/
package io.jsonwebtoken.impl;
+import io.jsonwebtoken.codec.Decoder;
+import io.jsonwebtoken.codec.Encoder;
+
+/**
+ * @deprecated since 0.10.0 - will be removed before 1.0.0. Use {@link Encoder#BASE64URL Encoder.BASE64URL}
+ * or {@link Decoder#BASE64URL Decoder.BASE64URL} instead.
+ */
+@Deprecated
public class Base64UrlCodec extends AbstractTextCodec {
@Override
public String encode(byte[] data) {
- String base64Text = TextCodec.BASE64.encode(data);
- byte[] bytes = base64Text.getBytes(US_ASCII);
-
- //base64url encoding doesn't use padding chars:
- bytes = removePadding(bytes);
-
- //replace URL-unfriendly Base64 chars to url-friendly ones:
- for (int i = 0; i < bytes.length; i++) {
- if (bytes[i] == '+') {
- bytes[i] = '-';
- } else if (bytes[i] == '/') {
- bytes[i] = '_';
- }
- }
-
- return new String(bytes, US_ASCII);
- }
-
- protected byte[] removePadding(byte[] bytes) {
-
- byte[] result = bytes;
-
- int paddingCount = 0;
- for (int i = bytes.length - 1; i > 0; i--) {
- if (bytes[i] == '=') {
- paddingCount++;
- } else {
- break;
- }
- }
- if (paddingCount > 0) {
- result = new byte[bytes.length - paddingCount];
- System.arraycopy(bytes, 0, result, 0, bytes.length - paddingCount);
- }
-
- return result;
+ return Encoder.BASE64URL.encode(data);
}
@Override
public byte[] decode(String encoded) {
- char[] chars = encoded.toCharArray(); //always ASCII - one char == 1 byte
-
- //Base64 requires padding to be in place before decoding, so add it if necessary:
- chars = ensurePadding(chars);
-
- //Replace url-friendly chars back to normal Base64 chars:
- for (int i = 0; i < chars.length; i++) {
- if (chars[i] == '-') {
- chars[i] = '+';
- } else if (chars[i] == '_') {
- chars[i] = '/';
- }
- }
-
- String base64Text = new String(chars);
-
- return TextCodec.BASE64.decode(base64Text);
+ return Decoder.BASE64URL.decode(encoded);
}
-
- protected char[] ensurePadding(char[] chars) {
-
- char[] result = chars; //assume argument in case no padding is necessary
-
- int paddingCount = 0;
-
- //fix for https://github.com/jwtk/jjwt/issues/31
- int remainder = chars.length % 4;
- if (remainder == 2 || remainder == 3) {
- paddingCount = 4 - remainder;
- }
-
- if (paddingCount > 0) {
- result = new char[chars.length + paddingCount];
- System.arraycopy(chars, 0, result, 0, chars.length);
- for (int i = 0; i < paddingCount; i++) {
- result[chars.length + i] = '=';
- }
- }
-
- return result;
- }
-
}
diff --git a/src/main/java/io/jsonwebtoken/impl/DefaultJwtBuilder.java b/src/main/java/io/jsonwebtoken/impl/DefaultJwtBuilder.java
index 25b48d62f..bb2865868 100644
--- a/src/main/java/io/jsonwebtoken/impl/DefaultJwtBuilder.java
+++ b/src/main/java/io/jsonwebtoken/impl/DefaultJwtBuilder.java
@@ -17,12 +17,20 @@
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
-import io.jsonwebtoken.*;
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.CompressionCodec;
+import io.jsonwebtoken.Header;
+import io.jsonwebtoken.JwsHeader;
+import io.jsonwebtoken.JwtBuilder;
+import io.jsonwebtoken.JwtParser;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+import io.jsonwebtoken.codec.Decoder;
+import io.jsonwebtoken.codec.Encoder;
import io.jsonwebtoken.impl.crypto.DefaultJwtSigner;
import io.jsonwebtoken.impl.crypto.JwtSigner;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Collections;
-import io.jsonwebtoken.lang.Objects;
import io.jsonwebtoken.lang.Strings;
import javax.crypto.spec.SecretKeySpec;
@@ -39,11 +47,19 @@ public class DefaultJwtBuilder implements JwtBuilder {
private String payload;
private SignatureAlgorithm algorithm;
- private Key key;
- private byte[] keyBytes;
+ private Key key;
+
+ private Encoder base64UrlEncoder = Encoder.BASE64URL;
private CompressionCodec compressionCodec;
+ @Override
+ public JwtBuilder base64UrlEncodeWith(Encoder base64UrlEncoder) {
+ Assert.notNull(base64UrlEncoder, "base64UrlEncoder cannot be null.");
+ this.base64UrlEncoder = base64UrlEncoder;
+ return this;
+ }
+
@Override
public JwtBuilder setHeader(Header header) {
this.header = header;
@@ -88,7 +104,7 @@ public JwtBuilder signWith(SignatureAlgorithm alg, byte[] secretKey) {
Assert.notEmpty(secretKey, "secret key byte array cannot be null or empty.");
Assert.isTrue(alg.isHmac(), "Key bytes may only be specified for HMAC signatures. If using RSA or Elliptic Curve, use the signWith(SignatureAlgorithm, Key) method instead.");
this.algorithm = alg;
- this.keyBytes = secretKey;
+ this.key = new SecretKeySpec(secretKey, alg.getJcaName());
return this;
}
@@ -96,7 +112,7 @@ public JwtBuilder signWith(SignatureAlgorithm alg, byte[] secretKey) {
public JwtBuilder signWith(SignatureAlgorithm alg, String base64EncodedSecretKey) {
Assert.hasText(base64EncodedSecretKey, "base64-encoded secret key cannot be null or empty.");
Assert.isTrue(alg.isHmac(), "Base64-encoded key bytes may only be specified for HMAC signatures. If using RSA or Elliptic Curve, use the signWith(SignatureAlgorithm, Key) method instead.");
- byte[] bytes = TextCodec.BASE64.decode(base64EncodedSecretKey);
+ byte[] bytes = Decoder.BASE64.decode(base64EncodedSecretKey);
return signWith(alg, bytes);
}
@@ -262,22 +278,13 @@ public String compact() {
throw new IllegalStateException("Both 'payload' and 'claims' cannot both be specified. Choose either one.");
}
- if (key != null && keyBytes != null) {
- throw new IllegalStateException("A key object and key bytes cannot both be specified. Choose either one.");
- }
-
Header header = ensureHeader();
- Key key = this.key;
- if (key == null && !Objects.isEmpty(keyBytes)) {
- key = new SecretKeySpec(keyBytes, algorithm.getJcaName());
- }
-
JwsHeader jwsHeader;
-
if (header instanceof JwsHeader) {
- jwsHeader = (JwsHeader)header;
+ jwsHeader = (JwsHeader) header;
} else {
+ //noinspection unchecked
jwsHeader = new DefaultJwsHeader(header);
}
@@ -294,25 +301,19 @@ public String compact() {
String base64UrlEncodedHeader = base64UrlEncode(jwsHeader, "Unable to serialize header to json.");
- String base64UrlEncodedBody;
+ byte[] bytes;
+ try {
+ bytes = this.payload != null ? payload.getBytes(Strings.UTF_8) : toJson(claims);
+ } catch (JsonProcessingException e) {
+ throw new IllegalArgumentException("Unable to serialize claims object to json.");
+ }
if (compressionCodec != null) {
-
- byte[] bytes;
- try {
- bytes = this.payload != null ? payload.getBytes(Strings.UTF_8) : toJson(claims);
- } catch (JsonProcessingException e) {
- throw new IllegalArgumentException("Unable to serialize claims object to json.");
- }
-
- base64UrlEncodedBody = TextCodec.BASE64URL.encode(compressionCodec.compress(bytes));
-
- } else {
- base64UrlEncodedBody = this.payload != null ?
- TextCodec.BASE64URL.encode(this.payload) :
- base64UrlEncode(claims, "Unable to serialize claims object to json.");
+ bytes = compressionCodec.compress(bytes);
}
+ String base64UrlEncodedBody = base64UrlEncoder.encode(bytes);
+
String jwt = base64UrlEncodedHeader + JwtParser.SEPARATOR_CHAR + base64UrlEncodedBody;
if (key != null) { //jwt must be signed:
@@ -335,7 +336,7 @@ public String compact() {
* @since 0.5 mostly to allow testing overrides
*/
protected JwtSigner createSigner(SignatureAlgorithm alg, Key key) {
- return new DefaultJwtSigner(alg, key);
+ return new DefaultJwtSigner(alg, key, base64UrlEncoder);
}
protected String base64UrlEncode(Object o, String errMsg) {
@@ -346,11 +347,10 @@ protected String base64UrlEncode(Object o, String errMsg) {
throw new IllegalStateException(errMsg, e);
}
- return TextCodec.BASE64URL.encode(bytes);
+ return base64UrlEncoder.encode(bytes);
}
-
- protected byte[] toJson(Object object) throws JsonProcessingException {
+ protected byte[] toJson(Object object) throws JsonProcessingException {
return OBJECT_MAPPER.writeValueAsBytes(object);
}
}
diff --git a/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java b/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java
index 4e4b9c79c..46fd3fd68 100644
--- a/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java
+++ b/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java
@@ -38,6 +38,7 @@
import io.jsonwebtoken.SignatureException;
import io.jsonwebtoken.SigningKeyResolver;
import io.jsonwebtoken.UnsupportedJwtException;
+import io.jsonwebtoken.codec.Decoder;
import io.jsonwebtoken.impl.compression.DefaultCompressionCodecResolver;
import io.jsonwebtoken.impl.crypto.DefaultJwtSignatureValidator;
import io.jsonwebtoken.impl.crypto.JwtSignatureValidator;
@@ -69,12 +70,21 @@ public class DefaultJwtParser implements JwtParser {
private CompressionCodecResolver compressionCodecResolver = new DefaultCompressionCodecResolver();
- Claims expectedClaims = new DefaultClaims();
+ private Decoder base64UrlDecoder = Decoder.BASE64URL;
+
+ private Claims expectedClaims = new DefaultClaims();
private Clock clock = DefaultClock.INSTANCE;
private long allowedClockSkewMillis = 0;
+ @Override
+ public JwtParser base64UrlDecodeWith(Decoder base64UrlDecoder) {
+ Assert.notNull(base64UrlDecoder, "base64UrlDecoder cannot be null.");
+ this.base64UrlDecoder = base64UrlDecoder;
+ return this;
+ }
+
@Override
public JwtParser requireIssuedAt(Date issuedAt) {
expectedClaims.setIssuedAt(issuedAt);
@@ -146,9 +156,9 @@ public JwtParser setSigningKey(byte[] key) {
}
@Override
- public JwtParser setSigningKey(String base64EncodedKeyBytes) {
- Assert.hasText(base64EncodedKeyBytes, "signing key cannot be null or empty.");
- this.keyBytes = TextCodec.BASE64.decode(base64EncodedKeyBytes);
+ public JwtParser setSigningKey(String base64EncodedSecretKey) {
+ Assert.hasText(base64EncodedSecretKey, "signing key cannot be null or empty.");
+ this.keyBytes = Decoder.BASE64.decode(base64EncodedSecretKey);
return this;
}
@@ -215,7 +225,7 @@ public Jwt parse(String jwt) throws ExpiredJwtException, MalformedJwtException,
if (c == SEPARATOR_CHAR) {
CharSequence tokenSeq = Strings.clean(sb);
- String token = tokenSeq!=null?tokenSeq.toString():null;
+ String token = tokenSeq != null ? tokenSeq.toString() : null;
if (delimiterCount == 0) {
base64UrlEncodedHeader = token;
@@ -248,7 +258,8 @@ public Jwt parse(String jwt) throws ExpiredJwtException, MalformedJwtException,
CompressionCodec compressionCodec = null;
if (base64UrlEncodedHeader != null) {
- String origValue = TextCodec.BASE64URL.decodeToString(base64UrlEncodedHeader);
+ byte[] bytes = base64UrlDecoder.decode(base64UrlEncodedHeader);
+ String origValue = new String(bytes, Strings.UTF_8);
Map m = readValue(origValue);
if (base64UrlEncodedDigest != null) {
@@ -261,13 +272,11 @@ public Jwt parse(String jwt) throws ExpiredJwtException, MalformedJwtException,
}
// =============== Body =================
- String payload;
+ byte[] bytes = base64UrlDecoder.decode(base64UrlEncodedPayload);
if (compressionCodec != null) {
- byte[] decompressed = compressionCodec.decompress(TextCodec.BASE64URL.decode(base64UrlEncodedPayload));
- payload = new String(decompressed, Strings.UTF_8);
- } else {
- payload = TextCodec.BASE64URL.decodeToString(base64UrlEncodedPayload);
+ bytes = compressionCodec.decompress(bytes);
}
+ String payload = new String(bytes, Strings.UTF_8);
Claims claims = null;
@@ -293,7 +302,7 @@ public Jwt parse(String jwt) throws ExpiredJwtException, MalformedJwtException,
if (algorithm == null || algorithm == SignatureAlgorithm.NONE) {
//it is plaintext, but it has a signature. This is invalid:
String msg = "JWT string has a digest/signature, but the header does not reference a valid signature " +
- "algorithm.";
+ "algorithm.";
throw new MalformedJwtException(msg);
}
@@ -322,7 +331,7 @@ public Jwt parse(String jwt) throws ExpiredJwtException, MalformedJwtException,
if (!Objects.isEmpty(keyBytes)) {
Assert.isTrue(algorithm.isHmac(),
- "Key bytes can only be specified for HMAC signatures. Please specify a PublicKey or PrivateKey instance.");
+ "Key bytes can only be specified for HMAC signatures. Please specify a PublicKey or PrivateKey instance.");
key = new SecretKeySpec(keyBytes, algorithm.getJcaName());
}
@@ -338,19 +347,19 @@ public Jwt parse(String jwt) throws ExpiredJwtException, MalformedJwtException,
validator = createSignatureValidator(algorithm, key);
} catch (IllegalArgumentException e) {
String algName = algorithm.getValue();
- String msg = "The parsed JWT indicates it was signed with the " + algName + " signature " +
- "algorithm, but the specified signing key of type " + key.getClass().getName() +
- " may not be used to validate " + algName + " signatures. Because the specified " +
- "signing key reflects a specific and expected algorithm, and the JWT does not reflect " +
- "this algorithm, it is likely that the JWT was not expected and therefore should not be " +
- "trusted. Another possibility is that the parser was configured with the incorrect " +
- "signing key, but this cannot be assumed for security reasons.";
+ String msg = "The parsed JWT indicates it was signed with the " + algName + " signature " +
+ "algorithm, but the specified signing key of type " + key.getClass().getName() +
+ " may not be used to validate " + algName + " signatures. Because the specified " +
+ "signing key reflects a specific and expected algorithm, and the JWT does not reflect " +
+ "this algorithm, it is likely that the JWT was not expected and therefore should not be " +
+ "trusted. Another possibility is that the parser was configured with the incorrect " +
+ "signing key, but this cannot be assumed for security reasons.";
throw new UnsupportedJwtException(msg, e);
}
if (!validator.isValid(jwtWithoutSignature, base64UrlEncodedDigest)) {
String msg = "JWT signature does not match locally computed signature. JWT validity cannot be " +
- "asserted and should not be trusted.";
+ "asserted and should not be trusted.";
throw new SignatureException(msg);
}
}
@@ -426,34 +435,31 @@ private void validateExpectedClaims(Header header, Claims claims) {
Object expectedClaimValue = expectedClaims.get(expectedClaimName);
Object actualClaimValue = claims.get(expectedClaimName);
- if (
- Claims.ISSUED_AT.equals(expectedClaimName) ||
- Claims.EXPIRATION.equals(expectedClaimName) ||
- Claims.NOT_BEFORE.equals(expectedClaimName)
- ) {
+ if (Claims.ISSUED_AT.equals(expectedClaimName) || Claims.EXPIRATION.equals(expectedClaimName) ||
+ Claims.NOT_BEFORE.equals(expectedClaimName)) {
+
expectedClaimValue = expectedClaims.get(expectedClaimName, Date.class);
actualClaimValue = claims.get(expectedClaimName, Date.class);
- } else if (
- expectedClaimValue instanceof Date &&
- actualClaimValue != null &&
- actualClaimValue instanceof Long
- ) {
- actualClaimValue = new Date((Long)actualClaimValue);
+
+ } else if (expectedClaimValue instanceof Date && actualClaimValue instanceof Long) {
+
+ actualClaimValue = new Date((Long) actualClaimValue);
}
InvalidClaimException invalidClaimException = null;
if (actualClaimValue == null) {
- String msg = String.format(
- ClaimJwtException.MISSING_EXPECTED_CLAIM_MESSAGE_TEMPLATE,
- expectedClaimName, expectedClaimValue
- );
+
+ String msg = String.format(ClaimJwtException.MISSING_EXPECTED_CLAIM_MESSAGE_TEMPLATE,
+ expectedClaimName, expectedClaimValue);
+
invalidClaimException = new MissingClaimException(header, claims, msg);
+
} else if (!expectedClaimValue.equals(actualClaimValue)) {
- String msg = String.format(
- ClaimJwtException.INCORRECT_EXPECTED_CLAIM_MESSAGE_TEMPLATE,
- expectedClaimName, expectedClaimValue, actualClaimValue
- );
+
+ String msg = String.format(ClaimJwtException.INCORRECT_EXPECTED_CLAIM_MESSAGE_TEMPLATE,
+ expectedClaimName, expectedClaimValue, actualClaimValue);
+
invalidClaimException = new IncorrectClaimException(header, claims, msg);
}
@@ -469,7 +475,7 @@ private void validateExpectedClaims(Header header, Claims claims) {
* @since 0.5 mostly to allow testing overrides
*/
protected JwtSignatureValidator createSignatureValidator(SignatureAlgorithm alg, Key key) {
- return new DefaultJwtSignatureValidator(alg, key);
+ return new DefaultJwtSignatureValidator(alg, key, base64UrlDecoder);
}
@Override
diff --git a/src/main/java/io/jsonwebtoken/impl/DefaultTextCodecFactory.java b/src/main/java/io/jsonwebtoken/impl/DefaultTextCodecFactory.java
index 9b3d9f6f2..c148c4f6b 100644
--- a/src/main/java/io/jsonwebtoken/impl/DefaultTextCodecFactory.java
+++ b/src/main/java/io/jsonwebtoken/impl/DefaultTextCodecFactory.java
@@ -15,6 +15,10 @@
*/
package io.jsonwebtoken.impl;
+/**
+ * @deprecated since 0.10.0
+ */
+@Deprecated
public class DefaultTextCodecFactory implements TextCodecFactory {
protected String getSystemProperty(String key) {
diff --git a/src/main/java/io/jsonwebtoken/impl/TextCodec.java b/src/main/java/io/jsonwebtoken/impl/TextCodec.java
index 44e8b931a..2a51ed5cf 100644
--- a/src/main/java/io/jsonwebtoken/impl/TextCodec.java
+++ b/src/main/java/io/jsonwebtoken/impl/TextCodec.java
@@ -15,10 +15,26 @@
*/
package io.jsonwebtoken.impl;
+/**
+ * @deprecated since 0.10.0. Use an {@link io.jsonwebtoken.codec.Encoder} or {@link io.jsonwebtoken.codec.Decoder}
+ * as needed. This class will be removed before 1.0.0
+ */
+@Deprecated
public interface TextCodec {
- public static final TextCodec BASE64 = new DefaultTextCodecFactory().getTextCodec();
- public static final TextCodec BASE64URL = new Base64UrlCodec();
+ /**
+ * @deprecated since 0.10.0. Use {@link io.jsonwebtoken.codec.Encoder#BASE64 Encoder.BASE64} or
+ * {@link io.jsonwebtoken.codec.Decoder#BASE64 Decoder.BASE64} instead. This class will be removed before 1.0.0
+ */
+ @Deprecated
+ TextCodec BASE64 = new Base64Codec();
+
+ /**
+ * @deprecated since 0.10.0. Use {@link io.jsonwebtoken.codec.Encoder#BASE64URL Encoder.BASE64URL} or
+ * {@link io.jsonwebtoken.codec.Decoder#BASE64URL Decoder.BASE64URL} instead. This class will be removed before 1.0.0
+ */
+ @Deprecated
+ TextCodec BASE64URL = new Base64UrlCodec();
String encode(String data);
diff --git a/src/main/java/io/jsonwebtoken/impl/TextCodecFactory.java b/src/main/java/io/jsonwebtoken/impl/TextCodecFactory.java
index 43fc429c5..a318060f1 100644
--- a/src/main/java/io/jsonwebtoken/impl/TextCodecFactory.java
+++ b/src/main/java/io/jsonwebtoken/impl/TextCodecFactory.java
@@ -15,6 +15,10 @@
*/
package io.jsonwebtoken.impl;
+/**
+ * @deprecated since 0.10.0
+ */
+@Deprecated
public interface TextCodecFactory {
TextCodec getTextCodec();
diff --git a/src/main/java/io/jsonwebtoken/impl/compression/DefaultCompressionCodecResolver.java b/src/main/java/io/jsonwebtoken/impl/compression/DefaultCompressionCodecResolver.java
index fb32c1c62..64de55fd7 100644
--- a/src/main/java/io/jsonwebtoken/impl/compression/DefaultCompressionCodecResolver.java
+++ b/src/main/java/io/jsonwebtoken/impl/compression/DefaultCompressionCodecResolver.java
@@ -26,11 +26,11 @@
* Default implementation of {@link CompressionCodecResolver} that supports the following:
*
*
- * - If the specified JWT {@link Header} does not have a {@code calg} header, this implementation does
+ *
- If the specified JWT {@link Header} does not have a {@code zip} header, this implementation does
* nothing and returns {@code null} to the caller, indicating no compression was used.
- * - If the header has a {@code calg} value of {@code DEF}, a {@link DeflateCompressionCodec} will be returned.
- * - If the header has a {@code calg} value of {@code GZIP}, a {@link GzipCompressionCodec} will be returned.
- * - If the header has any other {@code calg} value, a {@link CompressionException} is thrown to reflect an
+ *
- If the header has a {@code zip} value of {@code DEF}, a {@link DeflateCompressionCodec} will be returned.
+ * - If the header has a {@code zip} value of {@code GZIP}, a {@link GzipCompressionCodec} will be returned.
+ * - If the header has any other {@code zip} value, a {@link CompressionException} is thrown to reflect an
* unrecognized algorithm.
*
*
diff --git a/src/main/java/io/jsonwebtoken/impl/crypto/DefaultJwtSignatureValidator.java b/src/main/java/io/jsonwebtoken/impl/crypto/DefaultJwtSignatureValidator.java
index 245c0d551..f9119878b 100644
--- a/src/main/java/io/jsonwebtoken/impl/crypto/DefaultJwtSignatureValidator.java
+++ b/src/main/java/io/jsonwebtoken/impl/crypto/DefaultJwtSignatureValidator.java
@@ -16,7 +16,7 @@
package io.jsonwebtoken.impl.crypto;
import io.jsonwebtoken.SignatureAlgorithm;
-import io.jsonwebtoken.impl.TextCodec;
+import io.jsonwebtoken.codec.Decoder;
import io.jsonwebtoken.lang.Assert;
import java.nio.charset.Charset;
@@ -27,14 +27,27 @@ public class DefaultJwtSignatureValidator implements JwtSignatureValidator {
private static final Charset US_ASCII = Charset.forName("US-ASCII");
private final SignatureValidator signatureValidator;
+ private final Decoder base64UrlDecoder;
+ @Deprecated
public DefaultJwtSignatureValidator(SignatureAlgorithm alg, Key key) {
- this(DefaultSignatureValidatorFactory.INSTANCE, alg, key);
+ this(DefaultSignatureValidatorFactory.INSTANCE, alg, key, Decoder.BASE64URL);
}
+ public DefaultJwtSignatureValidator(SignatureAlgorithm alg, Key key, Decoder base64UrlDecoder) {
+ this(DefaultSignatureValidatorFactory.INSTANCE, alg, key, base64UrlDecoder);
+ }
+
+ @Deprecated
public DefaultJwtSignatureValidator(SignatureValidatorFactory factory, SignatureAlgorithm alg, Key key) {
+ this(factory, alg, key, Decoder.BASE64URL);
+ }
+
+ public DefaultJwtSignatureValidator(SignatureValidatorFactory factory, SignatureAlgorithm alg, Key key, Decoder base64UrlDecoder) {
Assert.notNull(factory, "SignerFactory argument cannot be null.");
+ Assert.notNull(base64UrlDecoder, "Base64Url decoder argument cannot be null.");
this.signatureValidator = factory.createSignatureValidator(alg, key);
+ this.base64UrlDecoder = base64UrlDecoder;
}
@Override
@@ -42,7 +55,7 @@ public boolean isValid(String jwtWithoutSignature, String base64UrlEncodedSignat
byte[] data = jwtWithoutSignature.getBytes(US_ASCII);
- byte[] signature = TextCodec.BASE64URL.decode(base64UrlEncodedSignature);
+ byte[] signature = base64UrlDecoder.decode(base64UrlEncodedSignature);
return this.signatureValidator.isValid(data, signature);
}
diff --git a/src/main/java/io/jsonwebtoken/impl/crypto/DefaultJwtSigner.java b/src/main/java/io/jsonwebtoken/impl/crypto/DefaultJwtSigner.java
index e55d59332..de761a881 100644
--- a/src/main/java/io/jsonwebtoken/impl/crypto/DefaultJwtSigner.java
+++ b/src/main/java/io/jsonwebtoken/impl/crypto/DefaultJwtSigner.java
@@ -16,7 +16,8 @@
package io.jsonwebtoken.impl.crypto;
import io.jsonwebtoken.SignatureAlgorithm;
-import io.jsonwebtoken.impl.TextCodec;
+import io.jsonwebtoken.codec.Encoder;
+import io.jsonwebtoken.codec.impl.Base64UrlEncoder;
import io.jsonwebtoken.lang.Assert;
import java.nio.charset.Charset;
@@ -27,13 +28,26 @@ public class DefaultJwtSigner implements JwtSigner {
private static final Charset US_ASCII = Charset.forName("US-ASCII");
private final Signer signer;
+ private final Encoder base64UrlEncoder;
+ @Deprecated
public DefaultJwtSigner(SignatureAlgorithm alg, Key key) {
- this(DefaultSignerFactory.INSTANCE, alg, key);
+ this(DefaultSignerFactory.INSTANCE, alg, key, Encoder.BASE64URL);
}
+ public DefaultJwtSigner(SignatureAlgorithm alg, Key key, Encoder base64UrlEncoder) {
+ this(DefaultSignerFactory.INSTANCE, alg, key, base64UrlEncoder);
+ }
+
+ @Deprecated
public DefaultJwtSigner(SignerFactory factory, SignatureAlgorithm alg, Key key) {
+ this(factory, alg, key, Encoder.BASE64URL);
+ }
+
+ public DefaultJwtSigner(SignerFactory factory, SignatureAlgorithm alg, Key key, Encoder base64UrlEncoder) {
Assert.notNull(factory, "SignerFactory argument cannot be null.");
+ Assert.notNull(base64UrlEncoder, "Base64Url Encoder cannot be null.");
+ this.base64UrlEncoder = base64UrlEncoder;
this.signer = factory.createSigner(alg, key);
}
@@ -44,6 +58,6 @@ public String sign(String jwtWithoutSignature) {
byte[] signature = signer.sign(bytesToSign);
- return TextCodec.BASE64URL.encode(signature);
+ return base64UrlEncoder.encode(signature);
}
}
diff --git a/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy b/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy
index 00dc67f23..3140a0153 100644
--- a/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy
+++ b/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy
@@ -15,9 +15,10 @@
*/
package io.jsonwebtoken
+import io.jsonwebtoken.codec.Encoder
import io.jsonwebtoken.impl.DefaultClock
import io.jsonwebtoken.impl.FixedClock
-import io.jsonwebtoken.impl.TextCodec
+import io.jsonwebtoken.lang.Strings
import org.junit.Test
import javax.crypto.spec.SecretKeySpec
@@ -38,6 +39,11 @@ class JwtParserTest {
return key
}
+ protected static String base64Url(String s) {
+ byte[] bytes = s.getBytes(Strings.UTF_8)
+ return Encoder.BASE64URL.encode(bytes)
+ }
+
@Test
void testSetDuplicateSigningKeys() {
@@ -70,8 +76,7 @@ class JwtParserTest {
String junkPayload = '{;aklsjd;fkajsd;fkjasd;lfkj}'
- String bad = TextCodec.BASE64.encode('{"alg":"none"}') + '.' +
- TextCodec.BASE64.encode(junkPayload) + '.'
+ String bad = base64Url('{"alg":"none"}') + '.' + base64Url(junkPayload) + '.'
try {
Jwts.parser().parse(bad)
@@ -92,9 +97,7 @@ class JwtParserTest {
String badSig = ";aklsjdf;kajsd;fkjas;dklfj"
- String bad = TextCodec.BASE64.encode(header) + '.' +
- TextCodec.BASE64.encode(payload) + '.' +
- TextCodec.BASE64.encode(badSig)
+ String bad = base64Url(header) + '.' + base64Url(payload) + '.' + base64Url(badSig)
try {
Jwts.parser().setSigningKey(randomKey()).parse(bad)
@@ -113,9 +116,7 @@ class JwtParserTest {
String badSig = ";aklsjdf;kajsd;fkjas;dklfj"
- String bad = TextCodec.BASE64.encode(header) + '.' +
- TextCodec.BASE64.encode(payload) + '.' +
- TextCodec.BASE64.encode(badSig)
+ String bad = base64Url(header) + '.' + base64Url(payload) + '.' + base64Url(badSig)
try {
Jwts.parser().setSigningKey(randomKey()).parse(bad)
@@ -129,15 +130,13 @@ class JwtParserTest {
@Test
void testParsePlaintextJwsWithIncorrectAlg() {
- String header = '{"alg":"none"}'
+ def header = '{"alg":"none"}'
- String payload = '{"subject":"Joe"}'
+ def payload = '{"subject":"Joe"}'
- String badSig = ";aklsjdf;kajsd;fkjas;dklfj"
+ def badSig = ";aklsjdf;kajsd;fkjas;dklfj"
- String bad = TextCodec.BASE64.encode(header) + '.' +
- TextCodec.BASE64.encode(payload) + '.' +
- TextCodec.BASE64.encode(badSig)
+ String bad = base64Url(header) + '.' + base64Url(payload) + '.' + base64Url(badSig)
try {
Jwts.parser().setSigningKey(randomKey()).parse(bad)
@@ -150,8 +149,11 @@ class JwtParserTest {
@Test
void testParseWithBase64EncodedSigningKey() {
+
byte[] key = randomKey()
- String base64Encodedkey = TextCodec.BASE64.encode(key)
+
+ String base64Encodedkey = Encoder.BASE64.encode(key)
+
String payload = 'Hello world!'
String compact = Jwts.builder().setPayload(payload).signWith(SignatureAlgorithm.HS256, base64Encodedkey).compact()
@@ -1530,10 +1532,7 @@ class JwtParserTest {
String bogus = 'bogus'
- String bad = TextCodec.BASE64.encode(header) + '.' +
- TextCodec.BASE64.encode(payload) + '.' +
- TextCodec.BASE64.encode(badSig) + '.' +
- TextCodec.BASE64.encode(bogus)
+ String bad = base64Url(header) + '.' + base64Url(payload) + '.' + base64Url(badSig) + '.' + base64Url(bogus)
try {
@@ -1549,7 +1548,7 @@ class JwtParserTest {
void testNoHeaderNoSig() {
String payload = '{"subject":"Joe"}'
- String jwtStr = '.' + TextCodec.BASE64.encode(payload) + '.'
+ String jwtStr = '.' + base64Url(payload) + '.'
Jwt jwt = Jwts.parser().parse(jwtStr)
@@ -1559,14 +1558,15 @@ class JwtParserTest {
@Test
void testNoHeaderSig() {
+
String payload = '{"subject":"Joe"}'
String sig = ";aklsjdf;kajsd;fkjas;dklfj"
- String jwtStr = '.' + TextCodec.BASE64.encode(payload) + '.' + TextCodec.BASE64.encode(sig)
+ String jwtStr = '.' + base64Url(payload) + '.' + base64Url(sig)
try {
- Jwt jwt = Jwts.parser().parse(jwtStr)
+ Jwts.parser().parse(jwtStr)
fail()
} catch (MalformedJwtException se) {
assertEquals 'JWT string has a digest/signature, but the header does not reference a valid signature algorithm.', se.message
@@ -1581,7 +1581,7 @@ class JwtParserTest {
String sig = ";aklsjdf;kajsd;fkjas;dklfj"
- String jwtStr = TextCodec.BASE64.encode(payload) + '.' + TextCodec.BASE64.encode(payload) + '.' + TextCodec.BASE64.encode(sig)
+ String jwtStr = base64Url(payload) + '.' + base64Url(payload) + '.' + base64Url(sig)
try {
Jwt jwt = Jwts.parser().parse(jwtStr)
diff --git a/src/test/groovy/io/jsonwebtoken/JwtsTest.groovy b/src/test/groovy/io/jsonwebtoken/JwtsTest.groovy
index 9a344f0b9..42a597971 100644
--- a/src/test/groovy/io/jsonwebtoken/JwtsTest.groovy
+++ b/src/test/groovy/io/jsonwebtoken/JwtsTest.groovy
@@ -16,9 +16,9 @@
package io.jsonwebtoken
import com.fasterxml.jackson.databind.ObjectMapper
+import io.jsonwebtoken.codec.Encoder
import io.jsonwebtoken.impl.DefaultHeader
import io.jsonwebtoken.impl.DefaultJwsHeader
-import io.jsonwebtoken.impl.TextCodec
import io.jsonwebtoken.impl.compression.CompressionCodecs
import io.jsonwebtoken.impl.compression.DefaultCompressionCodecResolver
import io.jsonwebtoken.impl.compression.GzipCompressionCodec
@@ -39,6 +39,11 @@ import static org.junit.Assert.*
class JwtsTest {
+ protected static String base64Url(String s) {
+ byte[] bytes = s.getBytes(Strings.UTF_8)
+ return Encoder.BASE64URL.encode(bytes)
+ }
+
@Test
void testSubclass() {
new Jwts()
@@ -613,8 +618,8 @@ class JwtsTest {
PrivateKey privateKey = kp.getPrivate();
ObjectMapper om = new ObjectMapper()
- String header = TextCodec.BASE64URL.encode(om.writeValueAsString(['alg': 'HS256']))
- String body = TextCodec.BASE64URL.encode(om.writeValueAsString('foo'))
+ String header = base64Url(om.writeValueAsString(['alg': 'HS256']))
+ String body = base64Url(om.writeValueAsString('foo'))
String compact = header + '.' + body + '.'
// Now for the forgery: simulate an attacker using the RSA public key to sign a token, but
@@ -622,7 +627,7 @@ class JwtsTest {
Mac mac = Mac.getInstance('HmacSHA256');
mac.init(new SecretKeySpec(publicKey.getEncoded(), 'HmacSHA256'));
byte[] signatureBytes = mac.doFinal(compact.getBytes(Charset.forName('US-ASCII')))
- String encodedSignature = TextCodec.BASE64URL.encode(signatureBytes);
+ String encodedSignature = Encoder.BASE64URL.encode(signatureBytes)
//Finally, the forged token is the header + body + forged signature:
String forged = compact + encodedSignature;
@@ -646,8 +651,8 @@ class JwtsTest {
//PrivateKey privateKey = kp.getPrivate();
ObjectMapper om = new ObjectMapper()
- String header = TextCodec.BASE64URL.encode(om.writeValueAsString(['alg': 'HS256']))
- String body = TextCodec.BASE64URL.encode(om.writeValueAsString('foo'))
+ String header = base64Url(om.writeValueAsString(['alg': 'HS256']))
+ String body = base64Url(om.writeValueAsString('foo'))
String compact = header + '.' + body + '.'
// Now for the forgery: simulate an attacker using the RSA public key to sign a token, but
@@ -655,7 +660,7 @@ class JwtsTest {
Mac mac = Mac.getInstance('HmacSHA256');
mac.init(new SecretKeySpec(publicKey.getEncoded(), 'HmacSHA256'));
byte[] signatureBytes = mac.doFinal(compact.getBytes(Charset.forName('US-ASCII')))
- String encodedSignature = TextCodec.BASE64URL.encode(signatureBytes);
+ String encodedSignature = Encoder.BASE64URL.encode(signatureBytes);
//Finally, the forged token is the header + body + forged signature:
String forged = compact + encodedSignature;
@@ -679,8 +684,8 @@ class JwtsTest {
//PrivateKey privateKey = kp.getPrivate();
ObjectMapper om = new ObjectMapper()
- String header = TextCodec.BASE64URL.encode(om.writeValueAsString(['alg': 'HS256']))
- String body = TextCodec.BASE64URL.encode(om.writeValueAsString('foo'))
+ String header = base64Url(om.writeValueAsString(['alg': 'HS256']))
+ String body = base64Url(om.writeValueAsString('foo'))
String compact = header + '.' + body + '.'
// Now for the forgery: simulate an attacker using the Elliptic Curve public key to sign a token, but
@@ -688,7 +693,7 @@ class JwtsTest {
Mac mac = Mac.getInstance('HmacSHA256');
mac.init(new SecretKeySpec(publicKey.getEncoded(), 'HmacSHA256'));
byte[] signatureBytes = mac.doFinal(compact.getBytes(Charset.forName('US-ASCII')))
- String encodedSignature = TextCodec.BASE64URL.encode(signatureBytes);
+ String encodedSignature = Encoder.BASE64URL.encode(signatureBytes);
//Finally, the forged token is the header + body + forged signature:
String forged = compact + encodedSignature;
diff --git a/src/test/groovy/io/jsonwebtoken/codec/CodecExceptionTest.groovy b/src/test/groovy/io/jsonwebtoken/codec/CodecExceptionTest.groovy
new file mode 100644
index 000000000..07151036e
--- /dev/null
+++ b/src/test/groovy/io/jsonwebtoken/codec/CodecExceptionTest.groovy
@@ -0,0 +1,16 @@
+package io.jsonwebtoken.codec
+
+import org.junit.Test
+
+import static org.junit.Assert.assertEquals
+
+class CodecExceptionTest {
+
+ @Test
+ void testConstructorWithCause() {
+ def ioException = new IOException("root error")
+ def exception = new CodecException("wrapping", ioException)
+ assertEquals "wrapping", exception.getMessage()
+ assertEquals ioException, exception.getCause()
+ }
+}
diff --git a/src/test/groovy/io/jsonwebtoken/codec/DecodingExceptionTest.groovy b/src/test/groovy/io/jsonwebtoken/codec/DecodingExceptionTest.groovy
new file mode 100644
index 000000000..99c218623
--- /dev/null
+++ b/src/test/groovy/io/jsonwebtoken/codec/DecodingExceptionTest.groovy
@@ -0,0 +1,16 @@
+package io.jsonwebtoken.codec
+
+import org.junit.Test
+
+import static org.junit.Assert.assertEquals
+
+class DecodingExceptionTest {
+
+ @Test
+ void testConstructorWithCause() {
+ def ioException = new IOException("root error")
+ def exception = new DecodingException("wrapping", ioException)
+ assertEquals "wrapping", exception.getMessage()
+ assertEquals ioException, exception.getCause()
+ }
+}
diff --git a/src/test/groovy/io/jsonwebtoken/codec/EncodingExceptionTest.groovy b/src/test/groovy/io/jsonwebtoken/codec/EncodingExceptionTest.groovy
new file mode 100644
index 000000000..8d68928c2
--- /dev/null
+++ b/src/test/groovy/io/jsonwebtoken/codec/EncodingExceptionTest.groovy
@@ -0,0 +1,16 @@
+package io.jsonwebtoken.codec
+
+import org.junit.Test
+
+import static org.junit.Assert.assertEquals
+
+class EncodingExceptionTest {
+
+ @Test
+ void testConstructorWithCause() {
+ def ioException = new IOException("root error")
+ def exception = new EncodingException("wrapping", ioException)
+ assertEquals "wrapping", exception.getMessage()
+ assertEquals ioException, exception.getCause()
+ }
+}
diff --git a/src/test/groovy/io/jsonwebtoken/codec/impl/Base64DecoderTest.groovy b/src/test/groovy/io/jsonwebtoken/codec/impl/Base64DecoderTest.groovy
new file mode 100644
index 000000000..307a8e370
--- /dev/null
+++ b/src/test/groovy/io/jsonwebtoken/codec/impl/Base64DecoderTest.groovy
@@ -0,0 +1,22 @@
+package io.jsonwebtoken.codec.impl
+
+import io.jsonwebtoken.lang.Strings
+import org.junit.Test
+
+import static org.junit.Assert.assertEquals
+
+class Base64DecoderTest {
+
+ @Test(expected = IllegalArgumentException)
+ void testDecodeWithNullArgument() {
+ new Base64Decoder().decode(null)
+ }
+
+ @Test
+ void testDecode() {
+ String encoded = 'SGVsbG8g5LiW55WM' // Hello 世界
+ byte[] bytes = new Base64Decoder().decode(encoded)
+ String result = new String(bytes, Strings.UTF_8)
+ assertEquals 'Hello 世界', result
+ }
+}
diff --git a/src/test/groovy/io/jsonwebtoken/codec/impl/Base64EncoderTest.groovy b/src/test/groovy/io/jsonwebtoken/codec/impl/Base64EncoderTest.groovy
new file mode 100644
index 000000000..b1b544859
--- /dev/null
+++ b/src/test/groovy/io/jsonwebtoken/codec/impl/Base64EncoderTest.groovy
@@ -0,0 +1,22 @@
+package io.jsonwebtoken.codec.impl
+
+import io.jsonwebtoken.lang.Strings
+import org.junit.Test
+
+import static org.junit.Assert.assertEquals
+
+class Base64EncoderTest {
+
+ @Test(expected = IllegalArgumentException)
+ void testEncodeWithNullArgument() {
+ new Base64Encoder().encode(null)
+ }
+
+ @Test
+ void testDecode() {
+ String input = 'Hello 世界'
+ byte[] bytes = input.getBytes(Strings.UTF_8)
+ String encoded = new Base64Encoder().encode(bytes)
+ assertEquals 'SGVsbG8g5LiW55WM', encoded
+ }
+}
diff --git a/src/test/groovy/io/jsonwebtoken/codec/impl/Base64Test.groovy b/src/test/groovy/io/jsonwebtoken/codec/impl/Base64Test.groovy
new file mode 100644
index 000000000..2c6b3bceb
--- /dev/null
+++ b/src/test/groovy/io/jsonwebtoken/codec/impl/Base64Test.groovy
@@ -0,0 +1,131 @@
+package io.jsonwebtoken.codec.impl
+
+import io.jsonwebtoken.lang.Strings
+import org.junit.Test
+
+import static org.junit.Assert.*
+
+class Base64Test {
+
+ private static final String PLAINTEXT =
+ '''Bacon ipsum dolor amet venison beef pork chop, doner jowl pastrami ground round alcatra.
+ Beef leberkas filet mignon ball tip pork spare ribs kevin short loin ribeye ground round
+ biltong jerky short ribs corned beef. Strip steak turducken meatball porchetta beef ribs
+ shoulder pork belly doner salami corned beef kielbasa cow filet mignon drumstick. Bacon
+ tenderloin pancetta flank frankfurter ham kevin leberkas meatball turducken beef ribs.
+ Cupim short loin short ribs shankle tenderloin. Ham ribeye hamburger flank tenderloin
+ cupim t-bone, shank tri-tip venison salami sausage pancetta. Pork belly chuck salami
+ alcatra sirloin.
+
+ 以ケ ホゥ婧詃 橎ちゅぬ蛣埣 禧ざしゃ蟨廩 椥䤥グ曣わ 基覧 滯っ䶧きょメ Ủ䧞以ケ妣 择禤槜谣お 姨のドゥ,
+ らボみょば䪩 苯礊觊ツュ婃 䩦ディふげセ げセりょ 禤槜 Ủ䧞以ケ妣 せがみゅちょ䰯 择禤槜谣お 難ゞ滧 蝥ちゃ,
+ 滯っ䶧きょメ らボみょば䪩 礯みゃ楦と饥 椥䤥グ ウァ槚 訤をりゃしゑ びゃ驨も氩簥 栨キョ奎婨榞 ヌに楃 以ケ,
+ 姚奊べ 椥䤥グ曣わ 栨キョ奎婨榞 ちょ䰯 Ủ䧞以ケ妣 誧姨のドゥろ よ苯礊 く涥, りゅぽ槞 馣ぢゃ尦䦎ぎ
+ 大た䏩䰥ぐ 郎きや楺橯 䧎キェ, 難ゞ滧 栧择 谯䧟簨訧ぎょ 椥䤥グ曣わ'''
+
+ @Test
+ void testEncodeToStringWithNullArgument() {
+ String s = Base64.DEFAULT.encodeToString(null, false)
+ assertEquals 0, s.toCharArray().length
+ }
+
+ @Test
+ void testEncodeToStringWithEmptyByteArray() {
+ byte[] bytes = new byte[0]
+ String s = Base64.DEFAULT.encodeToString(bytes, false)
+ assertEquals 0, s.toCharArray().length
+ }
+
+ @Test
+ void testLineSeparators() {
+ byte[] bytes = PLAINTEXT.getBytes(Strings.UTF_8)
+ String encoded = Base64.DEFAULT.encodeToString(bytes, true)
+
+ def r = new StringReader(encoded)
+ String line = ''
+
+ while ((line = r.readLine()) != null) {
+ assertTrue line.length() <= 76
+ }
+ }
+
+ @Test
+ void testDecodeFastWithNullArgument() {
+ byte[] bytes = Base64.DEFAULT.decodeFast(null)
+ assertEquals 0, bytes.length
+ }
+
+ @Test
+ void testDecodeFastWithEmptyCharArray() {
+ byte[] bytes = Base64.DEFAULT.decodeFast(new char[0])
+ assertEquals 0, bytes.length
+ }
+
+ @Test
+ void testDecodeFastWithSurroundingIllegalCharacters() {
+ String expected = 'Hello 世界'
+ def encoded = '***SGVsbG8g5LiW55WM!!!'
+ byte[] bytes = Base64.DEFAULT.decodeFast(encoded.toCharArray())
+ String result = new String(bytes, Strings.UTF_8)
+ assertEquals expected, result
+ }
+
+ @Test
+ void testDecodeFastWithLineSeparators() {
+
+ byte[] bytes = PLAINTEXT.getBytes(Strings.UTF_8)
+ String encoded = Base64.DEFAULT.encodeToString(bytes, true)
+
+ byte[] resultBytes = Base64.DEFAULT.decodeFast(encoded.toCharArray())
+
+ assertTrue Arrays.equals(bytes, resultBytes)
+ assertEquals PLAINTEXT, new String(resultBytes, Strings.UTF_8)
+ }
+
+ private static String BASE64(String s) {
+ byte[] bytes = s.getBytes(Strings.UTF_8);
+ return Base64.DEFAULT.encodeToString(bytes, false)
+ }
+
+ @Test // https://tools.ietf.org/html/rfc4648#page-12
+ void testRfc4648Base64TestVectors() {
+
+ assertEquals "", BASE64("")
+
+ assertEquals "Zg==", BASE64("f")
+
+ assertEquals "Zm8=", BASE64("fo")
+
+ assertEquals "Zm9v", BASE64("foo")
+
+ assertEquals "Zm9vYg==", BASE64("foob")
+
+ assertEquals "Zm9vYmE=", BASE64("fooba")
+
+ assertEquals "Zm9vYmFy", BASE64("foobar")
+ }
+
+ private static String BASE64URL(String s) {
+ byte[] bytes = s.getBytes(Strings.UTF_8);
+ return Base64.URL_SAFE.encodeToString(bytes, false)
+ }
+
+ @Test //same test vectors above, but with padding removed
+ void testRfc4648Base64UrlTestVectors() {
+
+ assertEquals "", BASE64URL("")
+
+ assertEquals "Zg", BASE64URL("f") //base64 = 2 padding chars, base64url = no padding needed
+
+ assertEquals "Zm8", BASE64URL("fo") //base64 = 1 padding char, base64url = no padding needed
+
+ assertEquals "Zm9v", BASE64URL("foo")
+
+ assertEquals "Zm9vYg", BASE64URL("foob") //base64 = 2 padding chars, base64url = no padding needed
+
+ assertEquals "Zm9vYmE", BASE64URL("fooba") //base64 = 1 padding char, base64url = no padding needed
+
+ assertEquals "Zm9vYmFy", BASE64URL("foobar")
+
+ }
+}
diff --git a/src/test/groovy/io/jsonwebtoken/codec/impl/ExceptionPropagatingDecoderTest.groovy b/src/test/groovy/io/jsonwebtoken/codec/impl/ExceptionPropagatingDecoderTest.groovy
new file mode 100644
index 000000000..3d3a0da9d
--- /dev/null
+++ b/src/test/groovy/io/jsonwebtoken/codec/impl/ExceptionPropagatingDecoderTest.groovy
@@ -0,0 +1,58 @@
+package io.jsonwebtoken.codec.impl
+
+import io.jsonwebtoken.codec.Decoder
+import io.jsonwebtoken.codec.DecodingException
+import io.jsonwebtoken.codec.EncodingException
+import org.junit.Test
+
+import static org.junit.Assert.*
+
+class ExceptionPropagatingDecoderTest {
+
+ @Test(expected = IllegalArgumentException)
+ void testWithNullConstructorArgument() {
+ new ExceptionPropagatingDecoder(null)
+ }
+
+ @Test(expected = IllegalArgumentException)
+ void testEncodeWithNullArgument() {
+ def decoder = new ExceptionPropagatingDecoder<>(new Base64UrlDecoder())
+ decoder.decode(null)
+ }
+
+ @Test
+ void testEncodePropagatesDecodingException() {
+ def decoder = new ExceptionPropagatingDecoder(new Decoder() {
+ @Override
+ Object decode(Object o) throws DecodingException {
+ throw new DecodingException("problem", new IOException("dummy"))
+ }
+ })
+ try {
+ decoder.decode("hello")
+ fail()
+ } catch (DecodingException ex) {
+ assertEquals "problem", ex.getMessage()
+ }
+ }
+
+ @Test
+ void testEncodeWithNonEncodingExceptionIsWrappedAsEncodingException() {
+
+ def causeEx = new RuntimeException("whatevs")
+
+ def decoder = new ExceptionPropagatingDecoder(new Decoder() {
+ @Override
+ Object decode(Object o) throws EncodingException {
+ throw causeEx
+ }
+ })
+ try {
+ decoder.decode("hello")
+ fail()
+ } catch (DecodingException ex) {
+ assertEquals "Unable to decode input: whatevs", ex.getMessage()
+ assertSame causeEx, ex.getCause()
+ }
+ }
+}
diff --git a/src/test/groovy/io/jsonwebtoken/codec/impl/ExceptionPropagatingEncoderTest.groovy b/src/test/groovy/io/jsonwebtoken/codec/impl/ExceptionPropagatingEncoderTest.groovy
new file mode 100644
index 000000000..625a5e9ca
--- /dev/null
+++ b/src/test/groovy/io/jsonwebtoken/codec/impl/ExceptionPropagatingEncoderTest.groovy
@@ -0,0 +1,58 @@
+package io.jsonwebtoken.codec.impl
+
+import io.jsonwebtoken.codec.Encoder
+import io.jsonwebtoken.codec.EncodingException
+import org.junit.Test
+
+import static org.junit.Assert.*
+
+class ExceptionPropagatingEncoderTest {
+
+
+ @Test(expected = IllegalArgumentException)
+ void testWithNullConstructorArgument() {
+ new ExceptionPropagatingEncoder(null)
+ }
+
+ @Test(expected = IllegalArgumentException)
+ void testEncodeWithNullArgument() {
+ def encoder = new ExceptionPropagatingEncoder<>(new Base64UrlEncoder())
+ encoder.encode(null)
+ }
+
+ @Test
+ void testEncodePropagatesEncodingException() {
+ def encoder = new ExceptionPropagatingEncoder(new Encoder() {
+ @Override
+ Object encode(Object o) throws EncodingException {
+ throw new EncodingException("problem", new IOException("dummy"))
+ }
+ })
+ try {
+ encoder.encode("hello")
+ fail()
+ } catch (EncodingException ex) {
+ assertEquals "problem", ex.getMessage()
+ }
+ }
+
+ @Test
+ void testEncodeWithNonEncodingExceptionIsWrappedAsEncodingException() {
+
+ def causeEx = new RuntimeException("whatevs")
+
+ def encoder = new ExceptionPropagatingEncoder(new Encoder() {
+ @Override
+ Object encode(Object o) throws EncodingException {
+ throw causeEx;
+ }
+ })
+ try {
+ encoder.encode("hello")
+ fail()
+ } catch (EncodingException ex) {
+ assertEquals "Unable to encode input: whatevs", ex.getMessage()
+ assertSame causeEx, ex.getCause()
+ }
+ }
+}
diff --git a/src/test/groovy/io/jsonwebtoken/impl/AndroidBase64CodecTest.groovy b/src/test/groovy/io/jsonwebtoken/impl/AndroidBase64CodecTest.groovy
index 54d53a238..d097dc559 100644
--- a/src/test/groovy/io/jsonwebtoken/impl/AndroidBase64CodecTest.groovy
+++ b/src/test/groovy/io/jsonwebtoken/impl/AndroidBase64CodecTest.groovy
@@ -27,6 +27,7 @@ import static org.powermock.api.easymock.PowerMock.mockStatic
import static org.powermock.api.easymock.PowerMock.replayAll
import static org.powermock.api.easymock.PowerMock.verifyAll
+@Deprecated //remove just before 1.0.0 release
@RunWith(PowerMockRunner.class)
@PrepareForTest([Base64.class])
class AndroidBase64CodecTest {
diff --git a/src/test/groovy/io/jsonwebtoken/impl/Base64CodecTest.groovy b/src/test/groovy/io/jsonwebtoken/impl/Base64CodecTest.groovy
new file mode 100644
index 000000000..d9f0deddb
--- /dev/null
+++ b/src/test/groovy/io/jsonwebtoken/impl/Base64CodecTest.groovy
@@ -0,0 +1,22 @@
+package io.jsonwebtoken.impl
+
+import org.junit.Test
+
+import static org.junit.Assert.assertEquals
+
+@Deprecated //remove just before 1.0.0 release
+class Base64CodecTest {
+
+ @Test
+ void testEncodeDecode() {
+
+ String s = "Hello 世界"
+
+ def codec = new Base64Codec()
+
+ String encoded = codec.encode(s)
+
+ assertEquals s, codec.decodeToString(encoded)
+ }
+
+}
diff --git a/src/test/groovy/io/jsonwebtoken/impl/Base64UrlCodecTest.groovy b/src/test/groovy/io/jsonwebtoken/impl/Base64UrlCodecTest.groovy
index 45969d862..b1f4971b1 100644
--- a/src/test/groovy/io/jsonwebtoken/impl/Base64UrlCodecTest.groovy
+++ b/src/test/groovy/io/jsonwebtoken/impl/Base64UrlCodecTest.groovy
@@ -1,19 +1,25 @@
package io.jsonwebtoken.impl
+import io.jsonwebtoken.lang.Strings
import org.junit.Test
import static org.junit.Assert.*
+@Deprecated //remove just before 1.0.0 release
class Base64UrlCodecTest {
@Test
- void testRemovePaddingWithEmptyByteArray() {
+ void testEncodeDecode() {
+
+ String s = "Hello 世界"
def codec = new Base64UrlCodec()
- byte[] empty = new byte[0];
+ String base64url = codec.encode(s.getBytes(Strings.UTF_8))
+
+ byte[] decoded = codec.decode(base64url)
- def result = codec.removePadding(empty)
+ String result = new String(decoded, Strings.UTF_8)
- assertSame empty, result
+ assertEquals s, result
}
}
diff --git a/src/test/groovy/io/jsonwebtoken/impl/DefaultJwtBuilderTest.groovy b/src/test/groovy/io/jsonwebtoken/impl/DefaultJwtBuilderTest.groovy
index f4a87fc37..737067f93 100644
--- a/src/test/groovy/io/jsonwebtoken/impl/DefaultJwtBuilderTest.groovy
+++ b/src/test/groovy/io/jsonwebtoken/impl/DefaultJwtBuilderTest.groovy
@@ -19,6 +19,8 @@ import com.fasterxml.jackson.core.JsonProcessingException
import com.fasterxml.jackson.databind.JsonMappingException
import io.jsonwebtoken.Jwts
import io.jsonwebtoken.SignatureAlgorithm
+import io.jsonwebtoken.codec.Encoder
+import io.jsonwebtoken.codec.EncodingException
import io.jsonwebtoken.impl.compression.CompressionCodecs
import io.jsonwebtoken.impl.crypto.MacProvider
import org.junit.Test
@@ -164,21 +166,6 @@ class DefaultJwtBuilderTest {
}
}
- @Test
- void testCompactWithBothKeyAndKeyBytes() {
- def b = new DefaultJwtBuilder()
- b.setPayload('foo')
- def key = MacProvider.generateKey()
- b.signWith(SignatureAlgorithm.HS256, key)
- b.signWith(SignatureAlgorithm.HS256, key.encoded)
- try {
- b.compact()
- fail()
- } catch (IllegalStateException ise) {
- assertEquals ise.message, "A key object and key bytes cannot both be specified. Choose either one."
- }
- }
-
@Test
void testCompactWithJwsHeader() {
def b = new DefaultJwtBuilder()
@@ -320,4 +307,21 @@ class DefaultJwtBuilderTest {
assertNull b.claims
}
+ @Test(expected = IllegalArgumentException)
+ void testBase64UrlEncodeWithNullArgument() {
+ new DefaultJwtBuilder().base64UrlEncodeWith(null)
+ }
+
+ @Test
+ void testBase64UrlEncodeWithCustomEncoder() {
+ def encoder = new Encoder() {
+ @Override
+ Object encode(Object o) throws EncodingException {
+ return null
+ }
+ }
+ def b = new DefaultJwtBuilder().base64UrlEncodeWith(encoder)
+ assertSame encoder, b.base64UrlEncoder
+ }
+
}
diff --git a/src/test/groovy/io/jsonwebtoken/impl/DefaultJwtParserTest.groovy b/src/test/groovy/io/jsonwebtoken/impl/DefaultJwtParserTest.groovy
new file mode 100644
index 000000000..26c4e22a2
--- /dev/null
+++ b/src/test/groovy/io/jsonwebtoken/impl/DefaultJwtParserTest.groovy
@@ -0,0 +1,30 @@
+package io.jsonwebtoken.impl
+
+import io.jsonwebtoken.codec.Decoder
+import io.jsonwebtoken.codec.DecodingException
+import org.junit.Test
+import static org.junit.Assert.*
+
+// NOTE to the casual reader: even though this test class appears mostly empty, the DefaultJwtParser
+// implementation is tested to 100% coverage. The vast majority of its tests are in the JwtsTest class. This class
+// just fills in any remaining test gaps.
+
+class DefaultJwtParserTest {
+
+ @Test(expected = IllegalArgumentException)
+ void testBase64UrlDecodeWithNullArgument() {
+ new DefaultJwtBuilder().base64UrlEncodeWith(null)
+ }
+
+ @Test
+ void testBase64UrlEncodeWithCustomEncoder() {
+ def decoder = new Decoder() {
+ @Override
+ Object decode(Object o) throws DecodingException {
+ return null
+ }
+ }
+ def b = new DefaultJwtParser().base64UrlDecodeWith(decoder)
+ assertSame decoder, b.base64UrlDecoder
+ }
+}
diff --git a/src/test/groovy/io/jsonwebtoken/impl/DefaultTextCodecFactoryTest.groovy b/src/test/groovy/io/jsonwebtoken/impl/DefaultTextCodecFactoryTest.groovy
index cb4b746f4..95ff6211e 100644
--- a/src/test/groovy/io/jsonwebtoken/impl/DefaultTextCodecFactoryTest.groovy
+++ b/src/test/groovy/io/jsonwebtoken/impl/DefaultTextCodecFactoryTest.groovy
@@ -26,6 +26,12 @@ import static org.junit.Assert.*
@PrepareForTest([System.class])
class DefaultTextCodecFactoryTest {
+ @Test
+ void testGetSystemProperty() {
+ def factory = new DefaultTextCodecFactory()
+ assertNotNull factory.getSystemProperty("java.version")
+ }
+
@Test
void testIsAndroidByVmName() {
diff --git a/src/test/groovy/io/jsonwebtoken/impl/crypto/DefaultJwtSignatureValidatorTest.groovy b/src/test/groovy/io/jsonwebtoken/impl/crypto/DefaultJwtSignatureValidatorTest.groovy
new file mode 100644
index 000000000..a86178f72
--- /dev/null
+++ b/src/test/groovy/io/jsonwebtoken/impl/crypto/DefaultJwtSignatureValidatorTest.groovy
@@ -0,0 +1,33 @@
+package io.jsonwebtoken.impl.crypto
+
+import io.jsonwebtoken.SignatureAlgorithm
+import io.jsonwebtoken.codec.Decoder
+import org.junit.Test
+import static org.junit.Assert.*
+
+class DefaultJwtSignatureValidatorTest {
+
+ @Test //TODO: remove this before 1.0 since it tests a deprecated method
+ @Deprecated
+ void testDeprecatedTwoArgCtor() {
+
+ def alg = SignatureAlgorithm.HS256
+ def key = MacProvider.generateKey(alg)
+ def validator = new DefaultJwtSignatureValidator(alg, key)
+
+ assertNotNull validator.signatureValidator
+ assertSame Decoder.BASE64URL, validator.base64UrlDecoder
+ }
+
+ @Test //TODO: remove this before 1.0 since it tests a deprecated method
+ @Deprecated
+ void testDeprecatedThreeArgCtor() {
+
+ def alg = SignatureAlgorithm.HS256
+ def key = MacProvider.generateKey(alg)
+ def validator = new DefaultJwtSignatureValidator(DefaultSignatureValidatorFactory.INSTANCE, alg, key)
+
+ assertNotNull validator.signatureValidator
+ assertSame Decoder.BASE64URL, validator.base64UrlDecoder
+ }
+}
diff --git a/src/test/groovy/io/jsonwebtoken/impl/crypto/DefaultJwtSignerTest.groovy b/src/test/groovy/io/jsonwebtoken/impl/crypto/DefaultJwtSignerTest.groovy
new file mode 100644
index 000000000..7dd1e97db
--- /dev/null
+++ b/src/test/groovy/io/jsonwebtoken/impl/crypto/DefaultJwtSignerTest.groovy
@@ -0,0 +1,34 @@
+package io.jsonwebtoken.impl.crypto
+
+import io.jsonwebtoken.SignatureAlgorithm
+import io.jsonwebtoken.codec.Encoder
+import org.junit.Test
+
+import static org.junit.Assert.*
+
+class DefaultJwtSignerTest {
+
+ @Test //TODO: remove this before 1.0 since it tests a deprecated method
+ @Deprecated //remove just before 1.0.0 release
+ void testDeprecatedTwoArgCtor() {
+
+ def alg = SignatureAlgorithm.HS256
+ def key = MacProvider.generateKey(alg)
+ def signer = new DefaultJwtSigner(alg, key)
+
+ assertNotNull signer.signer
+ assertSame Encoder.BASE64URL, signer.base64UrlEncoder
+ }
+
+ @Test //TODO: remove this before 1.0 since it tests a deprecated method
+ @Deprecated //remove just before 1.0.0 release
+ void testDeprecatedThreeArgCtor() {
+
+ def alg = SignatureAlgorithm.HS256
+ def key = MacProvider.generateKey(alg)
+ def signer = new DefaultJwtSigner(DefaultSignerFactory.INSTANCE, alg, key)
+
+ assertNotNull signer.signer
+ assertSame Encoder.BASE64URL, signer.base64UrlEncoder
+ }
+}
diff --git a/src/test/groovy/io/jsonwebtoken/impl/crypto/EllipticCurveSignatureValidatorTest.groovy b/src/test/groovy/io/jsonwebtoken/impl/crypto/EllipticCurveSignatureValidatorTest.groovy
index f23c38529..f9398f374 100644
--- a/src/test/groovy/io/jsonwebtoken/impl/crypto/EllipticCurveSignatureValidatorTest.groovy
+++ b/src/test/groovy/io/jsonwebtoken/impl/crypto/EllipticCurveSignatureValidatorTest.groovy
@@ -18,6 +18,7 @@ package io.jsonwebtoken.impl.crypto
import io.jsonwebtoken.JwtException
import io.jsonwebtoken.SignatureAlgorithm
import io.jsonwebtoken.SignatureException
+import io.jsonwebtoken.codec.Decoder
import io.jsonwebtoken.impl.TextCodec
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.junit.Test
@@ -64,13 +65,13 @@ class EllipticCurveSignatureValidatorTest {
void ecdsaSignatureComplianceTest() {
def fact = KeyFactory.getInstance("ECDSA", "BC");
def publicKey = "MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQASisgweVL1tAtIvfmpoqvdXF8sPKTV9YTKNxBwkdkm+/auh4pR8TbaIfsEzcsGUVv61DFNFXb0ozJfurQ59G2XcgAn3vROlSSnpbIvuhKrzL5jwWDTaYa5tVF1Zjwia/5HUhKBkcPuWGXg05nMjWhZfCuEetzMLoGcHmtvabugFrqsAg="
- def pub = fact.generatePublic(new X509EncodedKeySpec(TextCodec.BASE64.decode(publicKey)))
+ def pub = fact.generatePublic(new X509EncodedKeySpec(Decoder.BASE64.decode(publicKey)))
def v = new EllipticCurveSignatureValidator(SignatureAlgorithm.ES512, pub)
def verifier = { token ->
def signatureStart = token.lastIndexOf('.')
def withoutSignature = token.substring(0, signatureStart)
def signature = token.substring(signatureStart + 1)
- assert v.isValid(withoutSignature.getBytes("US-ASCII"), TextCodec.BASE64URL.decode(signature)), "Signature do not match that of other implementations"
+ assert v.isValid(withoutSignature.getBytes("US-ASCII"), Decoder.BASE64URL.decode(signature)), "Signature do not match that of other implementations"
}
//Test verification for token created using https://github.com/auth0/node-jsonwebtoken/tree/v7.0.1
verifier("eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJ0ZXN0IjoidGVzdCIsImlhdCI6MTQ2NzA2NTgyN30.Aab4x7HNRzetjgZ88AMGdYV2Ml7kzFbl8Ql2zXvBores7iRqm2nK6810ANpVo5okhHa82MQf2Q_Zn4tFyLDR9z4GAcKFdcAtopxq1h8X58qBWgNOc0Bn40SsgUc8wOX4rFohUCzEtnUREePsvc9EfXjjAH78WD2nq4tn-N94vf14SncQ")
@@ -146,7 +147,7 @@ class EllipticCurveSignatureValidatorTest {
@Test
void edgeCaseSignatureToConcatLengthTest() {
try {
- def signature = TextCodec.BASE64.decode("MIEAAGg3OVb/ZeX12cYrhK3c07TsMKo7Kc6SiqW++4CAZWCX72DkZPGTdCv2duqlupsnZL53hiG3rfdOLj8drndCU+KHGrn5EotCATdMSLCXJSMMJoHMM/ZPG+QOHHPlOWnAvpC1v4lJb32WxMFNz1VAIWrl9Aa6RPG1GcjCTScKjvEE")
+ def signature = Decoder.BASE64.decode("MIEAAGg3OVb/ZeX12cYrhK3c07TsMKo7Kc6SiqW++4CAZWCX72DkZPGTdCv2duqlupsnZL53hiG3rfdOLj8drndCU+KHGrn5EotCATdMSLCXJSMMJoHMM/ZPG+QOHHPlOWnAvpC1v4lJb32WxMFNz1VAIWrl9Aa6RPG1GcjCTScKjvEE")
EllipticCurveProvider.transcodeSignatureToConcat(signature, 132)
fail()
} catch (JwtException e) {
@@ -157,7 +158,7 @@ class EllipticCurveSignatureValidatorTest {
@Test
void edgeCaseSignatureToConcatInvalidSignatureTest() {
try {
- def signature = TextCodec.BASE64.decode("MIGBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
+ def signature = Decoder.BASE64.decode("MIGBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
EllipticCurveProvider.transcodeSignatureToConcat(signature, 132)
fail()
} catch (JwtException e) {
@@ -168,7 +169,7 @@ class EllipticCurveSignatureValidatorTest {
@Test
void edgeCaseSignatureToConcatInvalidSignatureBranchTest() {
try {
- def signature = TextCodec.BASE64.decode("MIGBAD4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
+ def signature = Decoder.BASE64.decode("MIGBAD4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
EllipticCurveProvider.transcodeSignatureToConcat(signature, 132)
fail()
} catch (JwtException e) {
@@ -179,7 +180,7 @@ class EllipticCurveSignatureValidatorTest {
@Test
void edgeCaseSignatureToConcatInvalidSignatureBranch2Test() {
try {
- def signature = TextCodec.BASE64.decode("MIGBAj4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
+ def signature = Decoder.BASE64.decode("MIGBAj4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
EllipticCurveProvider.transcodeSignatureToConcat(signature, 132)
fail()
} catch (JwtException e) {