mirror of https://github.com/jwtk/jjwt.git
commit
bb471be0e9
|
@ -1,4 +1,5 @@
|
|||
*.class
|
||||
.DS_Store
|
||||
|
||||
# Mobile Tools for Java (J2ME)
|
||||
.mtj.tmp/
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Defines how to compress and decompress byte arrays.
|
||||
*
|
||||
* @since 0.5.2
|
||||
* @see io.jsonwebtoken.impl.compression.DeflateCompressionCodec
|
||||
* @see io.jsonwebtoken.impl.compression.GzipCompressionCodec
|
||||
*/
|
||||
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);
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* 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,33 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Exception indicating that either compressing or decompressing an JWT body failed.
|
||||
*
|
||||
* @since 0.5.2
|
||||
*/
|
||||
public class CompressionException extends JwtException {
|
||||
|
||||
public CompressionException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public CompressionException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
}
|
|
@ -48,6 +48,9 @@ public interface Header<T extends Header<T>> extends Map<String,Object> {
|
|||
/** JWT {@code Content Type} header parameter name: <code>"cty"</code> */
|
||||
public static final String CONTENT_TYPE = "cty";
|
||||
|
||||
/** JWT {@code Compression Algorithm} header parameter name: <code>"calg"</code> */
|
||||
public static final String COMPRESSION_ALGORITHM = "calg";
|
||||
|
||||
/**
|
||||
* Returns the <a href="https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-25#section-5.1">
|
||||
* <code>typ</code></a> (type) header value or {@code null} if not present.
|
||||
|
@ -100,4 +103,25 @@ public interface Header<T extends Header<T>> extends Map<String,Object> {
|
|||
*/
|
||||
T setContentType(String cty);
|
||||
|
||||
/**
|
||||
* Returns the JWT <code>calg</code> (Compression Algorithm) header value or {@code null} if not present.
|
||||
*
|
||||
* @return the {@code calg} header parameter value or {@code null} if not present.
|
||||
* @since 0.5.2
|
||||
*/
|
||||
String getCompressionAlgorithm();
|
||||
|
||||
/**
|
||||
* Sets the JWT <code>calg</code> (Compression Algorithm) header parameter value. A {@code null} value will remove
|
||||
* the property from the JSON map.
|
||||
* <p>
|
||||
* <p>The compression algorithm is NOT part of the <a href="https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-25>JWT specification</a>
|
||||
* 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. </p>
|
||||
*
|
||||
* @param calg the JWT compression algorithm {@code calg} value or {@code null} to remove the property from the JSON map.
|
||||
* @since 0.5.2
|
||||
*/
|
||||
T setCompressionAlgorithm(String calg);
|
||||
|
||||
}
|
||||
|
|
|
@ -349,6 +349,18 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
|
|||
*/
|
||||
JwtBuilder signWith(SignatureAlgorithm alg, Key key);
|
||||
|
||||
/**
|
||||
* Compresses the JWT body using the {@link CompressionCodec} passed as argument.
|
||||
*
|
||||
* Note: Compression is not part of the Json Web Token specification and is not expected that other libraries (including
|
||||
* older versions of this one) are able to consume a compressed JWT body correctly.
|
||||
*
|
||||
* @param codec implementation of the {@link CompressionCodec} to be used.
|
||||
* @return the builder for method chaining.
|
||||
* @since 0.5.2
|
||||
*/
|
||||
JwtBuilder compressWith(CompressionCodec codec);
|
||||
|
||||
/**
|
||||
* Actually builds the JWT and serializes it to a compact, URL-safe string according to the
|
||||
* <a href="https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-25#section-7">JWT Compact Serialization</a>
|
||||
|
|
|
@ -205,6 +205,16 @@ public interface JwtParser {
|
|||
*/
|
||||
JwtParser setSigningKeyResolver(SigningKeyResolver signingKeyResolver);
|
||||
|
||||
/**
|
||||
* Sets the {@link CompressionCodecResolver} used to acquire the {@link CompressionCodec} that should be used to verify
|
||||
* a decompress the JWT body. If the parsed JWT is not compressed, this resolver si not used.
|
||||
*
|
||||
* @param compressionCodecResolver the compression codec resolver used to decompress the JWT body.
|
||||
* @return the parser for method chaining.
|
||||
* @since 0.5.2
|
||||
*/
|
||||
JwtParser setCompressionCodecResolver(CompressionCodecResolver compressionCodecResolver);
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the specified JWT compact string represents a signed JWT (aka a 'JWS'), {@code false}
|
||||
* otherwise.
|
||||
|
|
|
@ -51,4 +51,16 @@ public class DefaultHeader<T extends Header<T>> extends JwtMap implements Header
|
|||
setValue(CONTENT_TYPE, cty);
|
||||
return (T)this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCompressionAlgorithm() {
|
||||
return getString(COMPRESSION_ALGORITHM);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T setCompressionAlgorithm(String compressionAlgorithm) {
|
||||
setValue(COMPRESSION_ALGORITHM, compressionAlgorithm);
|
||||
return (T) this;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ package io.jsonwebtoken.impl;
|
|||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.CompressionCodec;
|
||||
import io.jsonwebtoken.Header;
|
||||
import io.jsonwebtoken.JwsHeader;
|
||||
import io.jsonwebtoken.JwtBuilder;
|
||||
|
@ -48,6 +49,8 @@ public class DefaultJwtBuilder implements JwtBuilder {
|
|||
private Key key;
|
||||
private byte[] keyBytes;
|
||||
|
||||
private CompressionCodec compressionCodec;
|
||||
|
||||
@Override
|
||||
public JwtBuilder setHeader(Header header) {
|
||||
this.header = header;
|
||||
|
@ -113,6 +116,13 @@ public class DefaultJwtBuilder implements JwtBuilder {
|
|||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JwtBuilder compressWith(CompressionCodec compressionCodec) {
|
||||
Assert.notNull(compressionCodec, "compressionCodec cannot be null");
|
||||
this.compressionCodec = compressionCodec;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JwtBuilder setPayload(String payload) {
|
||||
this.payload = payload;
|
||||
|
@ -279,11 +289,30 @@ public class DefaultJwtBuilder implements JwtBuilder {
|
|||
jwsHeader.setAlgorithm(SignatureAlgorithm.NONE.getValue());
|
||||
}
|
||||
|
||||
if (compressionCodec != null) {
|
||||
jwsHeader.setCompressionAlgorithm(compressionCodec.getAlgorithmName());
|
||||
}
|
||||
|
||||
String base64UrlEncodedHeader = base64UrlEncode(jwsHeader, "Unable to serialize header to json.");
|
||||
|
||||
String base64UrlEncodedBody = this.payload != null ?
|
||||
TextCodec.BASE64URL.encode(this.payload) :
|
||||
base64UrlEncode(claims, "Unable to serialize claims object to json.");
|
||||
String base64UrlEncodedBody;
|
||||
|
||||
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.");
|
||||
}
|
||||
|
||||
String jwt = base64UrlEncodedHeader + JwtParser.SEPARATOR_CHAR + base64UrlEncodedBody;
|
||||
|
||||
|
@ -311,17 +340,18 @@ public class DefaultJwtBuilder implements JwtBuilder {
|
|||
}
|
||||
|
||||
protected String base64UrlEncode(Object o, String errMsg) {
|
||||
String s;
|
||||
byte[] bytes;
|
||||
try {
|
||||
s = toJson(o);
|
||||
bytes = toJson(o);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new IllegalStateException(errMsg, e);
|
||||
}
|
||||
|
||||
return TextCodec.BASE64URL.encode(s);
|
||||
return TextCodec.BASE64URL.encode(bytes);
|
||||
}
|
||||
|
||||
protected String toJson(Object o) throws JsonProcessingException {
|
||||
return OBJECT_MAPPER.writeValueAsString(o);
|
||||
|
||||
protected byte[] toJson(Object object) throws JsonProcessingException {
|
||||
return OBJECT_MAPPER.writeValueAsBytes(object);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,8 @@ package io.jsonwebtoken.impl;
|
|||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.jsonwebtoken.ClaimJwtException;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.CompressionCodec;
|
||||
import io.jsonwebtoken.CompressionCodecResolver;
|
||||
import io.jsonwebtoken.ExpiredJwtException;
|
||||
import io.jsonwebtoken.Header;
|
||||
import io.jsonwebtoken.IncorrectClaimException;
|
||||
|
@ -34,7 +36,9 @@ import io.jsonwebtoken.MalformedJwtException;
|
|||
import io.jsonwebtoken.PrematureJwtException;
|
||||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
import io.jsonwebtoken.SignatureException;
|
||||
import io.jsonwebtoken.SigningKeyResolver;
|
||||
import io.jsonwebtoken.UnsupportedJwtException;
|
||||
import io.jsonwebtoken.impl.compression.DefaultCompressionCodecResolver;
|
||||
import io.jsonwebtoken.impl.crypto.DefaultJwtSignatureValidator;
|
||||
import io.jsonwebtoken.impl.crypto.JwtSignatureValidator;
|
||||
import io.jsonwebtoken.lang.Assert;
|
||||
|
@ -62,6 +66,8 @@ public class DefaultJwtParser implements JwtParser {
|
|||
|
||||
private SigningKeyResolver signingKeyResolver;
|
||||
|
||||
private CompressionCodecResolver compressionCodecResolver = new DefaultCompressionCodecResolver();
|
||||
|
||||
Claims expectedClaims = new DefaultClaims();
|
||||
|
||||
@Override
|
||||
|
@ -150,6 +156,13 @@ public class DefaultJwtParser implements JwtParser {
|
|||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JwtParser setCompressionCodecResolver(CompressionCodecResolver compressionCodecResolver) {
|
||||
Assert.notNull(compressionCodecResolver, "compressionCodecResolver cannot be null.");
|
||||
this.compressionCodecResolver = compressionCodecResolver;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSigned(String jwt) {
|
||||
|
||||
|
@ -221,6 +234,8 @@ public class DefaultJwtParser implements JwtParser {
|
|||
// =============== Header =================
|
||||
Header header = null;
|
||||
|
||||
CompressionCodec compressionCodec = null;
|
||||
|
||||
if (base64UrlEncodedHeader != null) {
|
||||
String origValue = TextCodec.BASE64URL.decodeToString(base64UrlEncodedHeader);
|
||||
Map<String, Object> m = readValue(origValue);
|
||||
|
@ -230,10 +245,18 @@ public class DefaultJwtParser implements JwtParser {
|
|||
} else {
|
||||
header = new DefaultHeader(m);
|
||||
}
|
||||
|
||||
compressionCodec = compressionCodecResolver.resolveCompressionCodec(header);
|
||||
}
|
||||
|
||||
// =============== Body =================
|
||||
String payload = TextCodec.BASE64URL.decodeToString(base64UrlEncodedPayload);
|
||||
String payload;
|
||||
if (compressionCodec != null) {
|
||||
byte[] decompressed = compressionCodec.decompress(TextCodec.BASE64URL.decode(base64UrlEncodedPayload));
|
||||
payload = new String(decompressed, Strings.UTF_8);
|
||||
} else {
|
||||
payload = TextCodec.BASE64URL.decodeToString(base64UrlEncodedPayload);
|
||||
}
|
||||
|
||||
Claims claims = null;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* CompressionCodecs exposes default implementation of the {@link CompressionCodec} interface.
|
||||
*
|
||||
* @since 0.5.2
|
||||
*/
|
||||
public interface CompressionCodecs {
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* 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.CompressionCodecResolver;
|
||||
import io.jsonwebtoken.CompressionException;
|
||||
import io.jsonwebtoken.Header;
|
||||
import io.jsonwebtoken.lang.Assert;
|
||||
import io.jsonwebtoken.lang.Strings;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link CompressionCodecResolver}. This implementation will resolve DEF to
|
||||
* {@link DeflateCompressionCodec} and GZIP to {@link GzipCompressionCodec}.
|
||||
*
|
||||
* @since 0.5.2
|
||||
*/
|
||||
public class DefaultCompressionCodecResolver implements CompressionCodecResolver {
|
||||
|
||||
@Override
|
||||
public CompressionCodec resolveCompressionCodec(Header header) {
|
||||
String cmpAlg = getAlgorithmFromHeader(header);
|
||||
|
||||
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 + "'");
|
||||
}
|
||||
|
||||
private String getAlgorithmFromHeader(Header header) {
|
||||
Assert.notNull(header, "header cannot be null.");
|
||||
|
||||
return header.getCompressionAlgorithm();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* 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.lang.Objects;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.zip.Deflater;
|
||||
import java.util.zip.DeflaterOutputStream;
|
||||
import java.util.zip.InflaterOutputStream;
|
||||
|
||||
/**
|
||||
* Codec implementing the <a href="https://en.wikipedia.org/wiki/DEFLATE">deflate</a> compression algorithm
|
||||
* @since 0.5.2
|
||||
*/
|
||||
public class DeflateCompressionCodec extends BaseCompressionCodec {
|
||||
|
||||
private static final String DEFLATE = "DEF";
|
||||
|
||||
@Override
|
||||
public String getAlgorithmName() {
|
||||
return DEFLATE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] doCompress(byte[] payload) throws IOException {
|
||||
|
||||
Deflater deflater = new Deflater(Deflater.BEST_COMPRESSION);
|
||||
|
||||
ByteArrayOutputStream outputStream = null;
|
||||
DeflaterOutputStream deflaterOutputStream = null;
|
||||
try {
|
||||
outputStream = new ByteArrayOutputStream();
|
||||
deflaterOutputStream = new DeflaterOutputStream(outputStream, deflater, true);
|
||||
|
||||
deflaterOutputStream.write(payload, 0, payload.length);
|
||||
deflaterOutputStream.flush();
|
||||
return outputStream.toByteArray();
|
||||
} finally {
|
||||
Objects.nullSafeClose(outputStream, deflaterOutputStream);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] doDecompress(byte[] compressed) throws IOException {
|
||||
InflaterOutputStream inflaterOutputStream = null;
|
||||
ByteArrayOutputStream decompressedOutputStream = null;
|
||||
|
||||
try {
|
||||
decompressedOutputStream = new ByteArrayOutputStream();
|
||||
inflaterOutputStream = new InflaterOutputStream(decompressedOutputStream);
|
||||
inflaterOutputStream.write(compressed);
|
||||
inflaterOutputStream.flush();
|
||||
return decompressedOutputStream.toByteArray();
|
||||
} finally {
|
||||
Objects.nullSafeClose(decompressedOutputStream, inflaterOutputStream);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* 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.lang.Objects;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
|
||||
/**
|
||||
* Codec implementing the <a href="https://en.wikipedia.org/wiki/Gzip">gzip</a> compression algorithm
|
||||
*
|
||||
* @since 0.5.2
|
||||
*/
|
||||
public class GzipCompressionCodec extends BaseCompressionCodec implements CompressionCodec {
|
||||
|
||||
private static final String GZIP = "GZIP";
|
||||
|
||||
@Override
|
||||
public String getAlgorithmName() {
|
||||
return GZIP;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] doDecompress(byte[] compressed) throws IOException {
|
||||
byte[] buffer = new byte[512];
|
||||
|
||||
ByteArrayOutputStream outputStream = null;
|
||||
GZIPInputStream gzipInputStream = null;
|
||||
ByteArrayInputStream inputStream = null;
|
||||
|
||||
try {
|
||||
inputStream = new ByteArrayInputStream(compressed);
|
||||
gzipInputStream = new GZIPInputStream(inputStream);
|
||||
outputStream = new ByteArrayOutputStream();
|
||||
int read;
|
||||
while ((read = gzipInputStream.read(buffer)) != -1) {
|
||||
outputStream.write(buffer, 0, read);
|
||||
}
|
||||
return outputStream.toByteArray();
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,6 +15,8 @@
|
|||
*/
|
||||
package io.jsonwebtoken.lang;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.Arrays;
|
||||
|
||||
|
@ -905,4 +907,19 @@ public abstract class Objects {
|
|||
return sb.toString();
|
||||
}
|
||||
|
||||
public static void nullSafeClose(Closeable... closeables) {
|
||||
if (closeables == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (Closeable closeable : closeables) {
|
||||
if (closeable != null) {
|
||||
try {
|
||||
closeable.close();
|
||||
} catch (IOException e) {
|
||||
//Ignore the exception during close.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
*/
|
||||
package io.jsonwebtoken.lang;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
@ -40,6 +41,8 @@ public abstract class Strings {
|
|||
|
||||
private static final char EXTENSION_SEPARATOR = '.';
|
||||
|
||||
public static final Charset UTF_8 = Charset.forName("UTF-8");
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// General convenience methods for working with Strings
|
||||
//---------------------------------------------------------------------
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* 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
|
||||
|
||||
import org.junit.Test
|
||||
|
||||
import static org.junit.Assert.assertEquals
|
||||
|
||||
class CompressionExceptionTest {
|
||||
|
||||
@Test
|
||||
void testDefaultConstructor() {
|
||||
def exception = new CompressionException("my message")
|
||||
|
||||
assertEquals "my message", exception.getMessage()
|
||||
}
|
||||
|
||||
@Test
|
||||
void testConstructorWithCause() {
|
||||
|
||||
def ioException = new IOException("root error")
|
||||
|
||||
def exception = new CompressionException("wrapping", ioException)
|
||||
|
||||
assertEquals "wrapping", exception.getMessage()
|
||||
assertEquals ioException, exception.getCause()
|
||||
}
|
||||
}
|
|
@ -19,9 +19,13 @@ import com.fasterxml.jackson.databind.ObjectMapper
|
|||
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
|
||||
|
@ -108,6 +112,7 @@ class JwtsTest {
|
|||
|
||||
def token = Jwts.parser().parse(jwt);
|
||||
|
||||
//noinspection GrEqualsBetweenInconvertibleTypes
|
||||
assert token.body == claims
|
||||
}
|
||||
|
||||
|
@ -186,6 +191,16 @@ class JwtsTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testWithInvalidCompressionAlgorithm() {
|
||||
try {
|
||||
|
||||
Jwts.builder().setHeaderParam(Header.COMPRESSION_ALGORITHM, "CUSTOM").setId("andId").compact()
|
||||
} catch (CompressionException e) {
|
||||
assertEquals "Unsupported compression algorithm 'CUSTOM'", e.getMessage()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testConvenienceIssuer() {
|
||||
String compact = Jwts.builder().setIssuer("Me").compact();
|
||||
|
@ -320,6 +335,140 @@ 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() {
|
||||
|
||||
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(CompressionCodecs.DEFLATE).compact()
|
||||
|
||||
def jws = Jwts.parser().setSigningKey(key).parseClaimsJws(compact)
|
||||
|
||||
Claims claims = jws.body
|
||||
|
||||
assertEquals "DEF", jws.header.getCompressionAlgorithm()
|
||||
|
||||
assertEquals id, claims.getId()
|
||||
assertEquals "an audience", claims.getAudience()
|
||||
assertEquals "hello this is an amazing jwt", claims.state
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCompressedJwtWithGZIP() {
|
||||
|
||||
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(CompressionCodecs.GZIP).compact()
|
||||
|
||||
def jws = Jwts.parser().setSigningKey(key).parseClaimsJws(compact)
|
||||
|
||||
Claims claims = jws.body
|
||||
|
||||
assertEquals "GZIP", jws.header.getCompressionAlgorithm()
|
||||
|
||||
assertEquals id, claims.getId()
|
||||
assertEquals "an audience", claims.getAudience()
|
||||
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 = header.getCompressionAlgorithm()
|
||||
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() {
|
||||
|
||||
byte[] key = MacProvider.generateKey().getEncoded()
|
||||
|
||||
String payload = "this is my test for a payload"
|
||||
|
||||
String compact = Jwts.builder().setPayload(payload).signWith(SignatureAlgorithm.HS256, key)
|
||||
.compressWith(CompressionCodecs.DEFLATE).compact()
|
||||
|
||||
def jws = Jwts.parser().setSigningKey(key).parsePlaintextJws(compact)
|
||||
|
||||
String parsed = jws.body
|
||||
|
||||
assertEquals "DEF", jws.header.getCompressionAlgorithm()
|
||||
|
||||
assertEquals "this is my test for a payload", parsed
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHS256() {
|
||||
testHmac(SignatureAlgorithm.HS256);
|
||||
|
@ -571,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
|
||||
}
|
||||
|
||||
|
@ -585,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
|
||||
}
|
||||
|
||||
|
@ -606,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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -173,7 +173,7 @@ class DefaultJwtBuilderTest {
|
|||
|
||||
def b = new DefaultJwtBuilder() {
|
||||
@Override
|
||||
protected String toJson(Object o) throws JsonProcessingException {
|
||||
protected byte[] toJson(Object o) throws JsonProcessingException {
|
||||
throw new JsonMappingException('foo')
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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