mirror of https://github.com/jwtk/jjwt.git
- Implemented new Base64 encoder forked from MigBase64 to guarantee deterministic behavior on all JDK and Android platforms
- Allowed pluggable Encoder/Decoder for JWT building and parsing via new Encoder/Decoder and JwtBuilder#base64UrlEncodeWith and JwtParser#base64UrlDecodeWith methods respectively - added RFC 4648 Base64 test vectors per code review - Added tests for all new code to retain 100% code coverage, verified by Clover and Coveralls - Enabled oraclejdk10 and openjdk10 builds in TravisCI - Replaced gmaven plugin with gmavenplus to work on JDK >= 9 - Upgraded surefire and failsafe plugins to 2.22.0 to ensure build works on JDK >= 10 - Ensured JavaDoc linter wouldn't fail the build for JDK >= 8 (was previously only 1.8) - Updated changelog doc to reflect new Base64 functionality
This commit is contained in:
parent
130a841011
commit
6e1415c441
|
@ -7,6 +7,10 @@ jdk:
|
|||
- openjdk7
|
||||
- oraclejdk8
|
||||
- oraclejdk9
|
||||
- oraclejdk10
|
||||
- openjdk10
|
||||
- oraclejdk-ea
|
||||
- openjdk11
|
||||
|
||||
before_install:
|
||||
- export BUILD_COVERAGE="$([ $TRAVIS_JDK_VERSION == 'oraclejdk8' ] && echo 'true')"
|
||||
|
|
39
CHANGELOG.md
39
CHANGELOG.md
|
@ -1,5 +1,40 @@
|
|||
## Release Notes
|
||||
|
||||
### 0.10.0
|
||||
|
||||
This is a minor feature enhancement that ensures Base64 encoding is identical and deterministic on all JDK and
|
||||
Android platforms. JJWT previously relied on the underlying platform encoder (JAXB xml DataBinding or Android's native
|
||||
Base64), however in some cases this produced unexpected results across platforms, including when compared against the
|
||||
JDK's >= 8 Base64 implementation.
|
||||
|
||||
JJWT now embeds a super lightweight (1 class) and *extremely* fast RFC-compliant Base64 implementation to guarantee
|
||||
portability across all supported platforms. It is now enabled automatically and there is nothing you need to
|
||||
do to enable it.
|
||||
|
||||
However, if the default implementation isn't sufficient for your purposes, you may now specify your own
|
||||
encoder during JWT building or decoder during JWT parsing as you see fit.
|
||||
|
||||
For example, an encoder during building:
|
||||
|
||||
```java
|
||||
Encoder<byte[], String> encoder = new MyBase64UrlEncoder(); //implement me
|
||||
|
||||
Jwts.builder()
|
||||
.base64UrlEncodeWith(encoder)
|
||||
// ... etc ...
|
||||
.compact();
|
||||
```
|
||||
|
||||
Or a decoder during parsing:
|
||||
```java
|
||||
Decoder<String, byte[]> decoder = new MyBase64UrlDecoder(); //implement me
|
||||
|
||||
Jwts.parser()
|
||||
.base64UrlDecodeWith(decoder)
|
||||
// ... etc ...
|
||||
.parseClaimsJws(jws);
|
||||
```
|
||||
|
||||
### 0.9.1
|
||||
|
||||
This is a minor patch release that updates the Jackson dependency to 2.9.6 to address Jackson CVE-2017-17485.
|
||||
|
@ -136,7 +171,7 @@ Jwts.builder().claim("foo", "someReallyLongDataString...")
|
|||
.compact();
|
||||
```
|
||||
|
||||
This will set a new `calg` header with the name of the compression algorithm used so that parsers can see that value and decompress accordingly.
|
||||
This will set a new `zip` header with the name of the compression algorithm used so that parsers can see that value and decompress accordingly.
|
||||
|
||||
The default parser implementation will automatically decompress DEFLATE or GZIP compressed bodies, so you don't need to set anything on the parser - it looks like normal:
|
||||
|
||||
|
@ -155,7 +190,7 @@ Jwts.builder().claim("foo", "someReallyLongDataString...")
|
|||
.compact();
|
||||
```
|
||||
|
||||
You will then need to specify a `CompressionCodecResolver` on the parser, so you can inspect the `calg` header and return your custom codec when discovered:
|
||||
You will then need to specify a `CompressionCodecResolver` on the parser, so you can inspect the `zip` header and return your custom codec when discovered:
|
||||
|
||||
```java
|
||||
Jwts.parser().setSigningKey(key)
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
The io.jsonwebtoken.codec.impl.Base64 implementation is based on MigBase64 with modifications for Base64 URL support. This
|
||||
class's copyright and license notice have been retained and are repeated here per that code's requirements:
|
||||
|
||||
**** BEGIN MIGBASE64 NOTICE *****
|
||||
Licence (BSD):
|
||||
==============
|
||||
|
||||
Copyright (c) 2004, Mikael Grev, MiG InfoCom AB. (base64 @ miginfocom . com)
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
Redistributions of source code must retain the above copyright notice, this list
|
||||
of conditions and the following disclaimer.
|
||||
Redistributions in binary form must reproduce the above copyright notice, this
|
||||
list of conditions and the following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
Neither the name of the MiG InfoCom AB nor the names of its contributors may be
|
||||
used to endorse or promote products derived from this software without specific
|
||||
prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
||||
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
|
||||
OF SUCH DAMAGE.
|
||||
**** END MIGBASE64 NOTICE *****
|
39
pom.xml
39
pom.xml
|
@ -91,13 +91,13 @@
|
|||
<bouncycastle.version>1.56</bouncycastle.version>
|
||||
|
||||
<!-- Test Dependencies: Only required for testing when building. Not required by users at runtime: -->
|
||||
<groovy.version>2.4.11</groovy.version>
|
||||
<groovy.version>2.4.15</groovy.version>
|
||||
<logback.version>1.2.3</logback.version>
|
||||
<easymock.version>3.5</easymock.version>
|
||||
<junit.version>4.12</junit.version>
|
||||
<powermock.version>2.0.0-beta.5</powermock.version> <!-- necessary for Java 9 support -->
|
||||
<failsafe.plugin.version>2.20.1</failsafe.plugin.version>
|
||||
<surefire.plugin.version>2.20.1</surefire.plugin.version>
|
||||
<failsafe.plugin.version>2.22.0</failsafe.plugin.version>
|
||||
<surefire.plugin.version>2.22.0</surefire.plugin.version>
|
||||
<clover.version>4.2.0</clover.version>
|
||||
|
||||
</properties>
|
||||
|
@ -233,35 +233,24 @@
|
|||
</plugin>
|
||||
<!-- Allow for writing tests in Groovy: -->
|
||||
<plugin>
|
||||
<groupId>org.codehaus.gmaven</groupId>
|
||||
<artifactId>gmaven-plugin</artifactId>
|
||||
<version>1.5</version>
|
||||
<configuration>
|
||||
<providerSelection>2.0</providerSelection>
|
||||
<source />
|
||||
</configuration>
|
||||
<groupId>org.codehaus.gmavenplus</groupId>
|
||||
<artifactId>gmavenplus-plugin</artifactId>
|
||||
<version>1.6.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>addSources</goal>
|
||||
<goal>addTestSources</goal>
|
||||
<goal>generateStubs</goal>
|
||||
<goal>compile</goal>
|
||||
<goal>generateTestStubs</goal>
|
||||
<goal>testCompile</goal>
|
||||
<goal>compileTests</goal>
|
||||
<goal>removeStubs</goal>
|
||||
<goal>removeTestStubs</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.gmaven.runtime</groupId>
|
||||
<artifactId>gmaven-runtime-2.0</artifactId>
|
||||
<version>1.5</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.codehaus.groovy</groupId>
|
||||
<artifactId>groovy-all</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.groovy</groupId>
|
||||
<artifactId>groovy-all</artifactId>
|
||||
|
@ -406,12 +395,12 @@
|
|||
</build>
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>jdk8</id>
|
||||
<id>nonJDK7</id>
|
||||
<activation>
|
||||
<jdk>1.8</jdk>
|
||||
<jdk>[1.8,)</jdk>
|
||||
</activation>
|
||||
<properties>
|
||||
<!-- Turn off JDK 8's lint checks: -->
|
||||
<!-- Turn off JDK >= 8's lint checks: -->
|
||||
<additionalparam>-Xdoclint:none</additionalparam>
|
||||
</properties>
|
||||
</profile>
|
||||
|
|
|
@ -25,9 +25,9 @@ package io.jsonwebtoken;
|
|||
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();
|
||||
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
* <p>JJWT's default {@link JwtParser} implementation supports both the
|
||||
|
@ -34,12 +34,12 @@ package io.jsonwebtoken;
|
|||
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;
|
||||
|
||||
|
|
|
@ -109,24 +109,24 @@ 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.
|
||||
* Returns the JWT <code>zip</code> (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 <code>calg</code> (Compression Algorithm) header parameter value. A {@code null} value will remove
|
||||
* Sets the JWT <code>zip</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.
|
||||
* @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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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<JwtBuilder> {
|
|||
* <p>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[])}.</p>
|
||||
*
|
||||
* <h4>Deprecation Notice: Deprecated as of 0.10.0, will be removed in 1.0.0</h4>
|
||||
*
|
||||
* <p>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.</p>
|
||||
*
|
||||
* <p>This method always expected a String argument that was effectively the same as the result of the following
|
||||
* (pseudocode):</p>
|
||||
*
|
||||
* <p>{@code String base64EncodedSecretKey = base64Encode(secretKeyBytes);}</p>
|
||||
*
|
||||
* <p>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.</p>
|
||||
*
|
||||
* <p>See this
|
||||
* <a href="https://stackoverflow.com/questions/40252903/static-secret-as-byte-key-or-string/40274325#40274325">
|
||||
* StackOverflow answer</a> explaining why raw (non-base64-encoded) strings are almost always incorrect for
|
||||
* signature operations.</p>
|
||||
*
|
||||
* <p>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.</p>
|
||||
*
|
||||
* @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> {
|
|||
*/
|
||||
JwtBuilder compressWith(CompressionCodec codec);
|
||||
|
||||
/**
|
||||
* Perform Base64Url encoding with the specified Encoder.
|
||||
*
|
||||
* <p>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.</p>
|
||||
*
|
||||
* @param base64UrlEncoder the encoder to use when Base64Url-encoding
|
||||
* @return the builder for method chaining.
|
||||
* @since 0.10.0
|
||||
*/
|
||||
JwtBuilder base64UrlEncodeWith(Encoder<byte[], String> base64UrlEncoder);
|
||||
|
||||
/**
|
||||
* 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>
|
||||
|
|
|
@ -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.
|
||||
* <p>
|
||||
*
|
||||
* <p>Note that this key <em>MUST</em> be a valid key for the signature algorithm found in the JWT header
|
||||
* (as the {@code alg} header parameter).</p>
|
||||
* <p>
|
||||
*
|
||||
* <p>This method overwrites any previously set key.</p>
|
||||
* <p>
|
||||
*
|
||||
* <p>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[])}.</p>
|
||||
*
|
||||
* @param base64EncodedKeyBytes the BASE64-encoded algorithm-specific signature verification key to use to validate
|
||||
* <h4>Deprecation Notice: Deprecated as of 0.10.0, will be removed in 1.0.0</h4>
|
||||
*
|
||||
* <p>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.</p>
|
||||
*
|
||||
* <p>This method always expected a String argument that was effectively the same as the result of the following
|
||||
* (pseudocode):</p>
|
||||
*
|
||||
* <p>{@code String base64EncodedSecretKey = base64Encode(secretKeyBytes);}</p>
|
||||
*
|
||||
* <p>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.</p>
|
||||
*
|
||||
* <p>See this
|
||||
* <a href="https://stackoverflow.com/questions/40252903/static-secret-as-byte-key-or-string/40274325#40274325">
|
||||
* StackOverflow answer</a> explaining why raw (non-base64-encoded) strings are almost always incorrect for
|
||||
* signature operations.</p>
|
||||
*
|
||||
* <p>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.</p>
|
||||
*
|
||||
* @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
|
||||
*
|
||||
* <p>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.</p>
|
||||
*
|
||||
* @param base64UrlDecoder the decoder to use when Base64Url-decoding
|
||||
* @return the parser for method chaining.
|
||||
* @since 0.10.0
|
||||
*/
|
||||
JwtParser base64UrlDecodeWith(Decoder<String, byte[]> base64UrlDecoder);
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the specified JWT compact string represents a signed JWT (aka a 'JWS'), {@code false}
|
||||
* otherwise.
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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 <T>
|
||||
* @param <R>
|
||||
* @since 0.10.0
|
||||
*/
|
||||
public interface Decoder<T, R> {
|
||||
|
||||
Decoder<String, byte[]> BASE64 = new ExceptionPropagatingDecoder<>(new Base64Decoder());
|
||||
Decoder<String, byte[]> BASE64URL = new ExceptionPropagatingDecoder<>(new Base64UrlDecoder());
|
||||
|
||||
R decode(T t) throws DecodingException;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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 <T>
|
||||
* @param <R>
|
||||
* @since 0.10.0
|
||||
*/
|
||||
public interface Encoder<T, R> {
|
||||
|
||||
Encoder<byte[], String> BASE64 = new ExceptionPropagatingEncoder<>(new Base64Encoder());
|
||||
Encoder<byte[], String> BASE64URL = new ExceptionPropagatingEncoder<>(new Base64UrlEncoder());
|
||||
|
||||
R encode(T t) throws EncodingException;
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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 <a href="https://tools.ietf.org/html/rfc4648">RFC 4648</a>.
|
||||
*
|
||||
* <p>Based initially on MigBase64 with continued modifications for Base64 URL support and JDK-standard code formatting.</p>
|
||||
*
|
||||
* <p>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.</p>
|
||||
*
|
||||
* <p>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.</p>
|
||||
*
|
||||
* @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 <code>char[]</code> representation in accordance with RFC 2045.
|
||||
*
|
||||
* @param sArr The bytes to convert. If <code>null</code> or length 0 an empty array will be returned.
|
||||
* @param lineSep Optional "\r\n" after 76 characters, unless end of file.<br>
|
||||
* 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 <code>null</code>.
|
||||
*/
|
||||
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. <code>null</code> or length 0 will return an empty array.
|
||||
* @return The decoded array of bytes. May be of length 0. Will be <code>null</code> 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:<br>
|
||||
* + The array must have a line length of 76 chars OR no line separators at all (one line).<br>
|
||||
* + Line separator must be "\r\n", as specified in RFC 2045
|
||||
* + The array must not contain illegal characters within the encoded string<br>
|
||||
* + The array CAN have illegal characters at the beginning and end, those will be dealt with appropriately.<br>
|
||||
*
|
||||
* @param sArr The source array. Length 0 will return an empty array. <code>null</code> 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 <code>byte[]</code> representation i accordance with RFC 2045.
|
||||
*
|
||||
* @param sArr The bytes to convert. If <code>null</code> or length 0 an empty array will be returned.
|
||||
* @param lineSep Optional "\r\n" after 76 characters, unless end of file.<br>
|
||||
* 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 <code>null</code>.
|
||||
*
|
||||
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 <code>byte[]</code> representation i accordance with RFC 2045.
|
||||
*
|
||||
* @param sArr The bytes to convert. If <code>null</code> 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.<br>
|
||||
* 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 <code>null</code>.
|
||||
*
|
||||
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. <code>null</code> will throw an exception.
|
||||
* @return The decoded array of bytes. May be of length 0. Will be <code>null</code> 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. <code>null</code> 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 <code>null</code> 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:<br>
|
||||
* + The array must have a line length of 76 chars OR no line separators at all (one line).<br>
|
||||
* + Line separator must be "\r\n", as specified in RFC 2045
|
||||
* + The array must not contain illegal characters within the encoded string<br>
|
||||
* + The array CAN have illegal characters at the beginning and end, those will be dealt with appropriately.<br>
|
||||
*
|
||||
* @param sArr The source array. Length 0 will return an empty array. <code>null</code> 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 <code>String</code> representation i accordance with RFC 2045.
|
||||
*
|
||||
* @param sArr The bytes to convert. If <code>null</code> or length 0 an empty array will be returned.
|
||||
* @param lineSep Optional "\r\n" after 76 characters, unless end of file.<br>
|
||||
* 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 <code>null</code>.
|
||||
*/
|
||||
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 <code>String</code>. All illegal characters will be ignored and can handle both strings with
|
||||
* and without line separators.<br>
|
||||
* <b>Note!</b> It can be up to about 2x the speed to call <code>decode(str.toCharArray())</code> instead. That
|
||||
* will create a temporary array though. This version will use <code>str.charAt(i)</code> to iterate the string.
|
||||
*
|
||||
* @param str The source string. <code>null</code> or length 0 will return an empty array.
|
||||
* @return The decoded array of bytes. May be of length 0. Will be <code>null</code> 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:<br>
|
||||
* + The array must have a line length of 76 chars OR no line separators at all (one line).<br>
|
||||
* + Line separator must be "\r\n", as specified in RFC 2045
|
||||
* + The array must not contain illegal characters within the encoded string<br>
|
||||
* + The array CAN have illegal characters at the beginning and end, those will be dealt with appropriately.<br>
|
||||
*
|
||||
* @param s The source string. Length 0 will return an empty array. <code>null</code> 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;
|
||||
}
|
||||
*/
|
||||
}
|
|
@ -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<String, byte[]> {
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
|
@ -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<byte[], String> {
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package io.jsonwebtoken.codec.impl;
|
||||
|
||||
/**
|
||||
* @since 0.10.0
|
||||
*/
|
||||
public class Base64UrlDecoder extends Base64Decoder {
|
||||
|
||||
public Base64UrlDecoder() {
|
||||
super(Base64.URL_SAFE);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package io.jsonwebtoken.codec.impl;
|
||||
|
||||
/**
|
||||
* @since 0.10.0
|
||||
*/
|
||||
public class Base64UrlEncoder extends Base64Encoder {
|
||||
|
||||
public Base64UrlEncoder() {
|
||||
super(Base64.URL_SAFE);
|
||||
}
|
||||
}
|
|
@ -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<T, R> implements Decoder<T, R> {
|
||||
|
||||
private final Decoder<T, R> decoder;
|
||||
|
||||
public ExceptionPropagatingDecoder(Decoder<T, R> 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<T,R> implements Encoder<T,R> {
|
||||
|
||||
private final Encoder<T,R> encoder;
|
||||
|
||||
public ExceptionPropagatingEncoder(Encoder<T,R> 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -17,12 +17,20 @@ package io.jsonwebtoken.impl;
|
|||
|
||||
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<byte[], String> base64UrlEncoder = Encoder.BASE64URL;
|
||||
|
||||
private CompressionCodec compressionCodec;
|
||||
|
||||
@Override
|
||||
public JwtBuilder base64UrlEncodeWith(Encoder<byte[], String> 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 class DefaultJwtBuilder implements JwtBuilder {
|
|||
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 class DefaultJwtBuilder implements JwtBuilder {
|
|||
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 class DefaultJwtBuilder implements JwtBuilder {
|
|||
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 class DefaultJwtBuilder implements JwtBuilder {
|
|||
|
||||
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 class DefaultJwtBuilder implements JwtBuilder {
|
|||
* @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 @@ public class DefaultJwtBuilder implements JwtBuilder {
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,6 +38,7 @@ import io.jsonwebtoken.SignatureAlgorithm;
|
|||
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<String, byte[]> base64UrlDecoder = Decoder.BASE64URL;
|
||||
|
||||
private Claims expectedClaims = new DefaultClaims();
|
||||
|
||||
private Clock clock = DefaultClock.INSTANCE;
|
||||
|
||||
private long allowedClockSkewMillis = 0;
|
||||
|
||||
@Override
|
||||
public JwtParser base64UrlDecodeWith(Decoder<String, byte[]> 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 class DefaultJwtParser implements JwtParser {
|
|||
}
|
||||
|
||||
@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 class DefaultJwtParser implements JwtParser {
|
|||
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 class DefaultJwtParser implements JwtParser {
|
|||
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<String, Object> m = readValue(origValue);
|
||||
|
||||
if (base64UrlEncodedDigest != null) {
|
||||
|
@ -261,13 +272,11 @@ public class DefaultJwtParser implements JwtParser {
|
|||
}
|
||||
|
||||
// =============== 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 class DefaultJwtParser implements JwtParser {
|
|||
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 class DefaultJwtParser implements JwtParser {
|
|||
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 class DefaultJwtParser implements JwtParser {
|
|||
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 @@ public class DefaultJwtParser implements JwtParser {
|
|||
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 @@ public class DefaultJwtParser implements JwtParser {
|
|||
* @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
|
||||
|
|
|
@ -15,6 +15,10 @@
|
|||
*/
|
||||
package io.jsonwebtoken.impl;
|
||||
|
||||
/**
|
||||
* @deprecated since 0.10.0
|
||||
*/
|
||||
@Deprecated
|
||||
public class DefaultTextCodecFactory implements TextCodecFactory {
|
||||
|
||||
protected String getSystemProperty(String key) {
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -15,6 +15,10 @@
|
|||
*/
|
||||
package io.jsonwebtoken.impl;
|
||||
|
||||
/**
|
||||
* @deprecated since 0.10.0
|
||||
*/
|
||||
@Deprecated
|
||||
public interface TextCodecFactory {
|
||||
|
||||
TextCodec getTextCodec();
|
||||
|
|
|
@ -26,11 +26,11 @@ import io.jsonwebtoken.lang.Strings;
|
|||
* Default implementation of {@link CompressionCodecResolver} that supports the following:
|
||||
* <p>
|
||||
* <ul>
|
||||
* <li>If the specified JWT {@link Header} does not have a {@code calg} header, this implementation does
|
||||
* <li>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.</li>
|
||||
* <li>If the header has a {@code calg} value of {@code DEF}, a {@link DeflateCompressionCodec} will be returned.</li>
|
||||
* <li>If the header has a {@code calg} value of {@code GZIP}, a {@link GzipCompressionCodec} will be returned.</li>
|
||||
* <li>If the header has any other {@code calg} value, a {@link CompressionException} is thrown to reflect an
|
||||
* <li>If the header has a {@code zip} value of {@code DEF}, a {@link DeflateCompressionCodec} will be returned.</li>
|
||||
* <li>If the header has a {@code zip} value of {@code GZIP}, a {@link GzipCompressionCodec} will be returned.</li>
|
||||
* <li>If the header has any other {@code zip} value, a {@link CompressionException} is thrown to reflect an
|
||||
* unrecognized algorithm.</li>
|
||||
* </ul>
|
||||
*
|
||||
|
|
|
@ -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<String, byte[]> 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<String, byte[]> 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<String, byte[]> 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 class DefaultJwtSignatureValidator implements JwtSignatureValidator {
|
|||
|
||||
byte[] data = jwtWithoutSignature.getBytes(US_ASCII);
|
||||
|
||||
byte[] signature = TextCodec.BASE64URL.decode(base64UrlEncodedSignature);
|
||||
byte[] signature = base64UrlDecoder.decode(base64UrlEncodedSignature);
|
||||
|
||||
return this.signatureValidator.isValid(data, signature);
|
||||
}
|
||||
|
|
|
@ -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<byte[], String> 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<byte[], String> 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<byte[], String> 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 class DefaultJwtSigner implements JwtSigner {
|
|||
|
||||
byte[] signature = signer.sign(bytesToSign);
|
||||
|
||||
return TextCodec.BASE64URL.encode(signature);
|
||||
return base64UrlEncoder.encode(signature);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,141 @@
|
|||
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")
|
||||
|
||||
def input = 'special: [\r\n \t], ascii[32..126]: [ !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~]\n'
|
||||
def expected = "c3BlY2lhbDogWw0KIAldLCBhc2NpaVszMi4uMTI2XTogWyAhIiMkJSYnKCkqKywtLi8wMTIzNDU2Nzg5Ojs8PT4/QEFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaW1xdXl9gYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXp7fH1+XQo="
|
||||
assertEquals expected, BASE64(input)
|
||||
}
|
||||
|
||||
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 & some specials swapped: https://brockallen.com/2014/10/17/base64url-encoding/
|
||||
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")
|
||||
|
||||
def input = 'special: [\r\n \t], ascii[32..126]: [ !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~]\n'
|
||||
def expected = "c3BlY2lhbDogWw0KIAldLCBhc2NpaVszMi4uMTI2XTogWyAhIiMkJSYnKCkqKywtLi8wMTIzNDU2Nzg5Ojs8PT4/QEFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaW1xdXl9gYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXp7fH1+XQo="
|
||||
.replace("=", "")
|
||||
.replace("+", "-")
|
||||
.replace("/", "_")
|
||||
assertEquals expected, BASE64URL(input)
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
|
@ -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))
|
||||
|
||||
def result = codec.removePadding(empty)
|
||||
byte[] decoded = codec.decode(base64url)
|
||||
|
||||
assertSame empty, result
|
||||
String result = new String(decoded, Strings.UTF_8)
|
||||
|
||||
assertEquals s, result
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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() {
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in New Issue