mirror of https://github.com/jwtk/jjwt.git
Issue-52: Refactoring and adding unit tests to cover the compression functionality
This commit is contained in:
parent
19f6fcaa51
commit
806844a89a
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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 <a href="https://en.wikipedia.org/wiki/DEFLATE">deflate</a> compression algorithm
|
||||
*/
|
||||
CompressionCodec DEFLATE = new DeflateCompressionCodec();
|
||||
|
||||
/**
|
||||
* Codec implementing the <a href="https://en.wikipedia.org/wiki/Gzip">gzip</a> compression algorithm
|
||||
*/
|
||||
CompressionCodec GZIP = new GzipCompressionCodec();
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 <a href="https://en.wikipedia.org/wiki/DEFLATE">deflate</a> 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);
|
||||
}
|
||||
|
|
|
@ -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 <a href="https://en.wikipedia.org/wiki/Gzip">gzip</a> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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]);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue