diff --git a/src/main/java/io/jsonwebtoken/CompressionCodec.java b/src/main/java/io/jsonwebtoken/CompressionCodec.java
index e6a24481..84c56a29 100644
--- a/src/main/java/io/jsonwebtoken/CompressionCodec.java
+++ b/src/main/java/io/jsonwebtoken/CompressionCodec.java
@@ -16,15 +16,29 @@
package io.jsonwebtoken;
/**
- * CompressionCodec
+ * Defines how to compress and decompress byte arrays.
*
- * @since 0.59
+ * @since 0.5.2
*/
public interface CompressionCodec {
+ /**
+ * The algorithm name that would appear in the JWT header.
+ * @return the algorithm name that would appear in the JWT header
+ */
String getAlgorithmName();
+ /**
+ * Takes a byte array and returns a compressed version.
+ * @param payload bytes to compress
+ * @return compressed bytes
+ */
byte[] compress(byte[] payload);
+ /**
+ * Takes a compressed byte array and returns a decompressed version.
+ * @param compressed compressed bytes
+ * @return decompressed bytes
+ */
byte[] decompress(byte[] compressed);
}
\ No newline at end of file
diff --git a/src/main/java/io/jsonwebtoken/CompressionCodecResolver.java b/src/main/java/io/jsonwebtoken/CompressionCodecResolver.java
index 76805e06..877ab890 100644
--- a/src/main/java/io/jsonwebtoken/CompressionCodecResolver.java
+++ b/src/main/java/io/jsonwebtoken/CompressionCodecResolver.java
@@ -16,12 +16,16 @@
package io.jsonwebtoken;
/**
- * CompressionCodecResolver
+ * Resolves "calg" header to an implementation of CompressionCodec.
*
* @since 0.5.2
*/
public interface CompressionCodecResolver {
-
+ /**
+ * Examines the header and returns a CompressionCodec if it finds one that it recognizes.
+ * @param header of the JWT
+ * @return CompressionCodec matching the "calg" header, or null if there is no "calg" header.
+ */
CompressionCodec resolveCompressionCodec(Header header);
}
diff --git a/src/main/java/io/jsonwebtoken/impl/compression/BaseCompressionCodec.java b/src/main/java/io/jsonwebtoken/impl/compression/BaseCompressionCodec.java
new file mode 100644
index 00000000..1c86613a
--- /dev/null
+++ b/src/main/java/io/jsonwebtoken/impl/compression/BaseCompressionCodec.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2015 jsonwebtoken.io
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.jsonwebtoken.impl.compression;
+
+import io.jsonwebtoken.CompressionCodec;
+import io.jsonwebtoken.CompressionException;
+import io.jsonwebtoken.lang.Assert;
+
+import java.io.IOException;
+
+/**
+ * Base class that asserts arguments and wraps IOException with CompressionException.
+ *
+ * @since 0.5.2
+ */
+public abstract class BaseCompressionCodec implements CompressionCodec {
+ /**
+ * Implement this method to do the actual work of compressing the payload
+ * @param payload the bytes to compress
+ * @return the compressed bytes
+ * @throws IOException if the compression causes an IOException
+ */
+ protected abstract byte[] doCompress(byte[] payload) throws IOException;
+
+ /**
+ * Asserts that payload is not null and calls doCompress
+ * @param payload bytes to compress
+ * @return compressed bytes
+ * @throws CompressionException if doCompress throws an IOException
+ */
+ @Override
+ public final byte[] compress(byte[] payload) {
+ Assert.notNull(payload, "payload cannot be null.");
+
+ try {
+ return doCompress(payload);
+ } catch (IOException e) {
+ throw new CompressionException("Unable to compress payload.", e);
+ }
+ }
+
+ /**
+ * Asserts the compressed bytes is not null and calls doDecompress
+ * @param compressed compressed bytes
+ * @return decompressed bytes
+ * @throws CompressionException if doCompress throws an IOException
+ */
+ @Override
+ public final byte[] decompress(byte[] compressed) {
+ Assert.notNull(compressed, "compressed bytes cannot be null.");
+
+ try {
+ return doDecompress(compressed);
+ } catch (IOException e) {
+ throw new CompressionException("Unable to decompress bytes.", e);
+ }
+ }
+
+ /**
+ * Implement this method to do the actual work of decompressing the compressed bytes.
+ * @param compressed compressed bytes
+ * @return decompressed bytes
+ * @throws IOException if the decompression runs into an IO problem
+ */
+ protected abstract byte[] doDecompress(byte[] compressed) throws IOException;
+}
diff --git a/src/main/java/io/jsonwebtoken/impl/compression/CompressionCodecs.java b/src/main/java/io/jsonwebtoken/impl/compression/CompressionCodecs.java
index d1d663d3..8a1fd7eb 100644
--- a/src/main/java/io/jsonwebtoken/impl/compression/CompressionCodecs.java
+++ b/src/main/java/io/jsonwebtoken/impl/compression/CompressionCodecs.java
@@ -22,13 +22,16 @@ import io.jsonwebtoken.CompressionCodec;
*
* @since 0.5.2
*/
-public abstract class CompressionCodecs {
+public interface CompressionCodecs {
- private CompressionCodecs(){}
-
- public static final CompressionCodec DEFLATE = new DeflateCompressionCodec();
-
- public static final CompressionCodec GZIP = new GzipCompressionCodec();
+ /**
+ * Codec implementing the deflate compression algorithm
+ */
+ CompressionCodec DEFLATE = new DeflateCompressionCodec();
+ /**
+ * Codec implementing the gzip compression algorithm
+ */
+ CompressionCodec GZIP = new GzipCompressionCodec();
}
diff --git a/src/main/java/io/jsonwebtoken/impl/compression/DefaultCompressionCodecResolver.java b/src/main/java/io/jsonwebtoken/impl/compression/DefaultCompressionCodecResolver.java
index 00b37a88..298c3b57 100644
--- a/src/main/java/io/jsonwebtoken/impl/compression/DefaultCompressionCodecResolver.java
+++ b/src/main/java/io/jsonwebtoken/impl/compression/DefaultCompressionCodecResolver.java
@@ -23,7 +23,8 @@ import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Strings;
/**
- * DefaultCompressionCodecResolver
+ * Default implementation of {@link CompressionCodecResolver}. This implementation will resolve DEF to
+ * {@link DeflateCompressionCodec} and GZIP to {@link GzipCompressionCodec}.
*
* @since 0.5.2
*/
@@ -31,22 +32,25 @@ public class DefaultCompressionCodecResolver implements CompressionCodecResolver
@Override
public CompressionCodec resolveCompressionCodec(Header header) {
- Assert.notNull(header, "header cannot be null.");
+ String cmpAlg = getAlgorithmFromHeader(header);
- String cmpAlg = header.getCompressionAlgorithm();
-
- if (!Strings.hasText(cmpAlg)) {
+ final boolean hasCompressionAlgorithm = Strings.hasText(cmpAlg);
+ if (!hasCompressionAlgorithm) {
return null;
}
-
if (CompressionCodecs.DEFLATE.getAlgorithmName().equalsIgnoreCase(cmpAlg)) {
return CompressionCodecs.DEFLATE;
}
-
if (CompressionCodecs.GZIP.getAlgorithmName().equalsIgnoreCase(cmpAlg)) {
return CompressionCodecs.GZIP;
}
throw new CompressionException("Unsupported compression algorithm '" + cmpAlg + "'");
}
+
+ protected final String getAlgorithmFromHeader(Header header) {
+ Assert.notNull(header, "header cannot be null.");
+
+ return header.getCompressionAlgorithm();
+ }
}
diff --git a/src/main/java/io/jsonwebtoken/impl/compression/DeflateCompressionCodec.java b/src/main/java/io/jsonwebtoken/impl/compression/DeflateCompressionCodec.java
index 83b5725d..e7390fbd 100644
--- a/src/main/java/io/jsonwebtoken/impl/compression/DeflateCompressionCodec.java
+++ b/src/main/java/io/jsonwebtoken/impl/compression/DeflateCompressionCodec.java
@@ -15,9 +15,6 @@
*/
package io.jsonwebtoken.impl.compression;
-import io.jsonwebtoken.CompressionCodec;
-import io.jsonwebtoken.CompressionException;
-import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Objects;
import java.io.ByteArrayOutputStream;
@@ -27,11 +24,10 @@ import java.util.zip.DeflaterOutputStream;
import java.util.zip.InflaterOutputStream;
/**
- * DeflateCompressionCodec
- *
+ * Codec implementing the deflate compression algorithm
* @since 0.5.2
*/
-public class DeflateCompressionCodec implements CompressionCodec {
+public class DeflateCompressionCodec extends BaseCompressionCodec {
private static final String DEFLATE = "DEF";
@@ -41,8 +37,7 @@ public class DeflateCompressionCodec implements CompressionCodec {
}
@Override
- public byte[] compress(byte[] payload) {
- Assert.notNull(payload, "payload cannot be null.");
+ public byte[] doCompress(byte[] payload) throws IOException {
Deflater deflater = new Deflater(Deflater.BEST_COMPRESSION);
@@ -55,17 +50,13 @@ public class DeflateCompressionCodec implements CompressionCodec {
deflaterOutputStream.write(payload, 0, payload.length);
deflaterOutputStream.flush();
return outputStream.toByteArray();
- } catch (IOException e) {
- throw new CompressionException("Unable to compress payload.", e);
} finally {
Objects.nullSafeClose(outputStream, deflaterOutputStream);
}
}
@Override
- public byte[] decompress(byte[] compressed) {
- Assert.notNull(compressed, "compressed cannot be null.");
-
+ public byte[] doDecompress(byte[] compressed) throws IOException {
InflaterOutputStream inflaterOutputStream = null;
ByteArrayOutputStream decompressedOutputStream = null;
@@ -75,8 +66,6 @@ public class DeflateCompressionCodec implements CompressionCodec {
inflaterOutputStream.write(compressed);
inflaterOutputStream.flush();
return decompressedOutputStream.toByteArray();
- } catch (IOException e) {
- throw new CompressionException("Unable to decompress compressed payload.", e);
} finally {
Objects.nullSafeClose(decompressedOutputStream, inflaterOutputStream);
}
diff --git a/src/main/java/io/jsonwebtoken/impl/compression/GzipCompressionCodec.java b/src/main/java/io/jsonwebtoken/impl/compression/GzipCompressionCodec.java
index 1a8a7a53..61fd98de 100644
--- a/src/main/java/io/jsonwebtoken/impl/compression/GzipCompressionCodec.java
+++ b/src/main/java/io/jsonwebtoken/impl/compression/GzipCompressionCodec.java
@@ -16,8 +16,6 @@
package io.jsonwebtoken.impl.compression;
import io.jsonwebtoken.CompressionCodec;
-import io.jsonwebtoken.CompressionException;
-import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Objects;
import java.io.ByteArrayInputStream;
@@ -27,11 +25,11 @@ import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
/**
- * GzipCompressionCodec
+ * Codec implementing the gzip compression algorithm
*
* @since 0.5.2
*/
-public class GzipCompressionCodec implements CompressionCodec {
+public class GzipCompressionCodec extends BaseCompressionCodec implements CompressionCodec {
private static final String GZIP = "GZIP";
@@ -41,29 +39,7 @@ public class GzipCompressionCodec implements CompressionCodec {
}
@Override
- public byte[] compress(byte[] payload) {
- Assert.notNull(payload, "payload cannot be null.");
-
- ByteArrayOutputStream outputStream = null;
- GZIPOutputStream gzipOutputStream = null;
-
- try {
- outputStream = new ByteArrayOutputStream();
- gzipOutputStream = new GZIPOutputStream(outputStream, true);
- gzipOutputStream.write(payload, 0, payload.length);
- gzipOutputStream.finish();
- return outputStream.toByteArray();
- } catch (IOException e) {
- throw new CompressionException("Unable to compress payload.", e);
- } finally {
- Objects.nullSafeClose(outputStream, gzipOutputStream);
- }
- }
-
- @Override
- public byte[] decompress(byte[] compressed) {
- Assert.notNull(compressed, "compressed cannot be null.");
-
+ protected byte[] doDecompress(byte[] compressed) throws IOException {
byte[] buffer = new byte[512];
ByteArrayOutputStream outputStream = null;
@@ -79,10 +55,20 @@ public class GzipCompressionCodec implements CompressionCodec {
outputStream.write(buffer, 0, read);
}
return outputStream.toByteArray();
- } catch (IOException e) {
- throw new CompressionException("Unable to decompress compressed payload.", e);
} finally {
Objects.nullSafeClose(inputStream, gzipInputStream, outputStream);
}
}
+
+ protected byte[] doCompress(byte[] payload) throws IOException {
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ GZIPOutputStream compressorOutputStream = new GZIPOutputStream(outputStream, true);
+ try {
+ compressorOutputStream.write(payload, 0, payload.length);
+ compressorOutputStream.finish();
+ return outputStream.toByteArray();
+ } finally {
+ Objects.nullSafeClose(compressorOutputStream, outputStream);
+ }
+ }
}
diff --git a/src/test/groovy/io/jsonwebtoken/JwtsTest.groovy b/src/test/groovy/io/jsonwebtoken/JwtsTest.groovy
index c20ceda4..c188ca56 100644
--- a/src/test/groovy/io/jsonwebtoken/JwtsTest.groovy
+++ b/src/test/groovy/io/jsonwebtoken/JwtsTest.groovy
@@ -20,9 +20,12 @@ 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
import io.jsonwebtoken.impl.crypto.EllipticCurveProvider
import io.jsonwebtoken.impl.crypto.MacProvider
import io.jsonwebtoken.impl.crypto.RsaProvider
+import io.jsonwebtoken.lang.Strings
import org.junit.Test
import javax.crypto.Mac
@@ -109,6 +112,7 @@ class JwtsTest {
def token = Jwts.parser().parse(jwt);
+ //noinspection GrEqualsBetweenInconvertibleTypes
assert token.body == claims
}
@@ -331,6 +335,27 @@ class JwtsTest {
assertNull claims.getId()
}
+ @Test
+ void testUncompressedJwt() {
+
+ byte[] key = MacProvider.generateKey().getEncoded()
+
+ String id = UUID.randomUUID().toString()
+
+ String compact = Jwts.builder().setId(id).setAudience("an audience").signWith(SignatureAlgorithm.HS256, key)
+ .claim("state", "hello this is an amazing jwt").compact()
+
+ def jws = Jwts.parser().setSigningKey(key).parseClaimsJws(compact)
+
+ Claims claims = jws.body
+
+ assertNull jws.header.getCompressionAlgorithm()
+
+ assertEquals id, claims.getId()
+ assertEquals "an audience", claims.getAudience()
+ assertEquals "hello this is an amazing jwt", claims.state
+ }
+
@Test
void testCompressedJwtWithDeflate() {
@@ -373,6 +398,58 @@ class JwtsTest {
assertEquals "hello this is an amazing jwt", claims.state
}
+ @Test
+ void testCompressedWithCustomResolver() {
+ byte[] key = MacProvider.generateKey().getEncoded()
+
+ String id = UUID.randomUUID().toString()
+
+ String compact = Jwts.builder().setId(id).setAudience("an audience").signWith(SignatureAlgorithm.HS256, key)
+ .claim("state", "hello this is an amazing jwt").compressWith(new GzipCompressionCodec() {
+ @Override
+ String getAlgorithmName() {
+ return "CUSTOM"
+ }
+ }).compact()
+
+ def jws = Jwts.parser().setSigningKey(key).setCompressionCodecResolver(new DefaultCompressionCodecResolver() {
+ @Override
+ CompressionCodec resolveCompressionCodec(Header header) {
+ String algorithm = getAlgorithmFromHeader(header);
+ if ("CUSTOM".equals(algorithm)) {
+ return CompressionCodecs.GZIP
+ } else {
+ return null
+ }
+ }
+ }).parseClaimsJws(compact)
+
+ Claims claims = jws.body
+
+ assertEquals "CUSTOM", jws.header.getCompressionAlgorithm()
+
+ assertEquals id, claims.getId()
+ assertEquals "an audience", claims.getAudience()
+ assertEquals "hello this is an amazing jwt", claims.state
+
+ }
+ @Test(expected = CompressionException.class)
+ void testCompressedJwtWithUnrecognizedHeader() {
+ byte[] key = MacProvider.generateKey().getEncoded()
+
+ String id = UUID.randomUUID().toString()
+
+ String compact = Jwts.builder().setId(id).setAudience("an audience").signWith(SignatureAlgorithm.HS256, key)
+ .claim("state", "hello this is an amazing jwt").compressWith(new GzipCompressionCodec() {
+ @Override
+ String getAlgorithmName() {
+ return "CUSTOM"
+ }
+ }).compact()
+
+ Jwts.parser().setSigningKey(key).parseClaimsJws(compact)
+ }
+
@Test
void testCompressStringPayloadWithDeflate() {
@@ -643,6 +720,7 @@ class JwtsTest {
def token = Jwts.parser().setSigningKey(key).parse(jwt);
assert [alg: alg.name()] == token.header
+ //noinspection GrEqualsBetweenInconvertibleTypes
assert token.body == claims
}
@@ -657,6 +735,7 @@ class JwtsTest {
def token = Jwts.parser().setSigningKey(key).parse(jwt)
assert token.header == [alg: alg.name()]
+ //noinspection GrEqualsBetweenInconvertibleTypes
assert token.body == claims
}
@@ -678,6 +757,7 @@ class JwtsTest {
def token = Jwts.parser().setSigningKey(key).parse(jwt);
assert token.header == [alg: alg.name()]
+ //noinspection GrEqualsBetweenInconvertibleTypes
assert token.body == claims
}
}
diff --git a/src/test/groovy/io/jsonwebtoken/impl/compression/BaseCompressionCodecTest.groovy b/src/test/groovy/io/jsonwebtoken/impl/compression/BaseCompressionCodecTest.groovy
new file mode 100644
index 00000000..7ffc8eca
--- /dev/null
+++ b/src/test/groovy/io/jsonwebtoken/impl/compression/BaseCompressionCodecTest.groovy
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2015 jsonwebtoken.io
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.jsonwebtoken.impl.compression
+
+import io.jsonwebtoken.CompressionCodec
+import io.jsonwebtoken.CompressionException
+import org.junit.Test
+
+/**
+ * @since 0.5.2
+ */
+class BaseCompressionCodecTest {
+ static class ExceptionThrowingCodec extends BaseCompressionCodec {
+
+ @Override
+ protected byte[] doCompress(byte[] payload) throws IOException {
+ throw new IOException("Test Exception")
+ }
+
+ @Override
+ String getAlgorithmName() {
+ return "Test"
+ }
+
+ @Override
+ protected byte[] doDecompress(byte[] payload) throws IOException {
+ throw new IOException("Test Decompress Exception");
+ }
+ }
+
+ @Test(expected = CompressionException.class)
+ void testCompressWithException() {
+ CompressionCodec codecUT = new ExceptionThrowingCodec();
+ codecUT.compress(new byte[0]);
+ }
+
+ @Test(expected = CompressionException.class)
+ void testDecompressWithException() {
+ CompressionCodec codecUT = new ExceptionThrowingCodec();
+ codecUT.decompress(new byte[0]);
+ }
+}