Merge remote-tracking branch 'upstream/master'

This commit is contained in:
brentstormpath 2016-02-23 16:38:12 -08:00
commit 1d525e94c6
48 changed files with 2643 additions and 162 deletions

1
.gitignore vendored
View File

@ -1,4 +1,5 @@
*.class
.DS_Store
# Mobile Tools for Java (J2ME)
.mtj.tmp/

View File

@ -6,5 +6,9 @@ jdk:
- oraclejdk7
- oraclejdk8
before_install:
- export BUILD_COVERAGE="$([ $TRAVIS_JDK_VERSION == 'openjdk7' ] && echo 'true')"
install: echo "No need to run mvn install -DskipTests then mvn install. Running mvn install."
script: mvn install
after_success:
- test -z "$BUILD_COVERAGE" || mvn clean cobertura:cobertura coveralls:report

View File

@ -1,10 +1,11 @@
[![Build Status](https://travis-ci.org/jwtk/jjwt.svg?branch=master)](https://travis-ci.org/jwtk/jjwt)
[![Coverage Status](https://coveralls.io/repos/jwtk/jjwt/badge.svg?branch=master)](https://coveralls.io/r/jwtk/jjwt?branch=master)
# Java JWT: JSON Web Token for Java and Android
JJWT aims to be the easiest to use and understand library for creating and verifying JSON Web Tokens (JWTs) on the JVM.
JJWT is a 'clean room' implementation based solely on the [JWT](https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-25), [JWS](https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-31), [JWE](https://tools.ietf.org/html/draft-ietf-jose-json-web-encryption-31) and [JWA](https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-31) RFC draft specifications.
JJWT is a 'clean room' implementation based solely on the [JWT](https://tools.ietf.org/html/rfc7519), [JWS](https://tools.ietf.org/html/rfc7515), [JWE](https://tools.ietf.org/html/rfc7516), [JWK](https://tools.ietf.org/html/rfc7517) and [JWA](https://tools.ietf.org/html/rfc7518) RFC specifications.
## Installation
@ -16,7 +17,7 @@ Maven:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.5.1</version>
<version>0.6.0</version>
</dependency>
```
@ -24,7 +25,7 @@ Gradle:
```groovy
dependencies {
compile 'io.jsonwebtoken:jjwt:0.5.1'
compile 'io.jsonwebtoken:jjwt:0.6.0'
}
```
@ -62,7 +63,7 @@ But what if signature validation failed? You can catch `SignatureException` and
```java
try {
Jwts.parser().setSigningKey(key).parse(compactJwt);
Jwts.parser().setSigningKey(key).parseClaimsJws(compactJwt);
//OK, we can trust this JWT
@ -108,6 +109,88 @@ Maintained by [Stormpath](https://stormpath.com/)
## Release Notes
### 0.6.0
#### Enforce JWT Claims when Parsing
You can now enforce that JWT claims have expected values when parsing a compact JWT string.
For example, let's say that you require that the JWT you are parsing has a specific `sub` (subject) value,
otherwise you may not trust the token. You can do that by using one of the `require` methods on the parser builder:
```java
try {
Jwts.parser().requireSubject("jsmith").setSigningKey(key).parseClaimsJws(s);
} catch(InvalidClaimException ice) {
// the sub field was missing or did not have a 'jsmith' value
}
```
If it is important to react to a missing vs an incorrect value, instead of catching `InvalidClaimException`, you can catch either `MissingClaimException` or `IncorrectClaimException`:
```java
try {
Jwts.parser().requireSubject("jsmith").setSigningKey(key).parseClaimsJws(s);
} catch(MissingClaimException mce) {
// the parsed JWT did not have the sub field
} catch(IncorrectClaimException ice) {
// the parsed JWT had a sub field, but its value was not equal to 'jsmith'
}
```
You can also require custom fields by using the `require(fieldName, requiredFieldValue)` method - for example:
```java
try {
Jwts.parser().require("myfield", "myRequiredValue").setSigningKey(key).parseClaimsJws(s);
} catch(InvalidClaimException ice) {
// the 'myfield' field was missing or did not have a 'myRequiredValue' value
}
```
(or, again, you could catch either MissingClaimException or IncorrectClaimException instead)
#### Body Compression
**This feature is NOT JWT specification compliant**, *but it can be very useful when you parse your own tokens*.
If your JWT body is large and you have size restrictions (for example, if embedding a JWT in a URL and the URL must be under a certain length for legacy browsers or mail user agents), you may now compress the JWT body using a `CompressionCodec`:
```java
Jwts.builder().claim("foo", "someReallyLongDataString...")
.compressWith(CompressionCodecs.DEFLATE) // or CompressionCodecs.GZIP
.signWith(SignatureAlgorithm.HS256, key)
.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.
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:
```java
Jwts.parser().setSigningKey(key).parseClaimsJws(compact);
```
##### Custom Compression Algorithms
If the DEFLATE or GZIP algorithms are not sufficient for your needs, you can specify your own Compression algorithms by implementing the `CompressionCodec` interface and setting it on the parser:
```java
Jwts.builder().claim("foo", "someReallyLongDataString...")
.compressWith(new MyCompressionCodec())
.signWith(SignatureAlgorithm.HS256, key)
.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:
```java
Jwts.parser().setSigningKey(key)
.setCompressionCodecResolver(new MyCustomCompressionCodecResolver())
.parseClaimsJws(compact);
```
*NOTE*: Because body compression is not JWT specification compliant, you should only enable compression if both your JWT builder and parser are JJWT versions >= 0.6.0, or if you're using another library that implements the exact same functionality. This feature is best reserved for your own use cases - where you both create and later parse the tokens. It will likely cause problems if you compressed a token and expected a 3rd party (who doesn't use JJWT) to parse the token.
### 0.5.1
- Minor [bug](https://github.com/jwtk/jjwt/issues/31) fix [release](https://github.com/jwtk/jjwt/issues?q=milestone%3A0.5.1+is%3Aclosed) that ensures correct Base64 padding in Android runtimes.

24
pom.xml
View File

@ -25,7 +25,7 @@
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.5.2-SNAPSHOT</version>
<version>0.7.0-SNAPSHOT</version>
<name>JSON Web Token support for the JVM</name>
<packaging>jar</packaging>
@ -61,7 +61,6 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<buildNumber>${user.name}-${maven.build.timestamp}</buildNumber>
<slf4j.version>1.7.6</slf4j.version>
<jackson.version>2.4.2</jackson.version>
<!-- Optional Runtime Dependencies: -->
@ -78,12 +77,6 @@
</properties>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
@ -281,6 +274,8 @@
<artifactId>cobertura-maven-plugin</artifactId>
<version>2.7</version>
<configuration>
<maxmem>256m</maxmem>
<aggregate>true</aggregate>
<instrumentation>
<excludes>
<exclude>io/jsonwebtoken/lang/*.class</exclude>
@ -303,11 +298,6 @@
<lineRate>96</lineRate>
<branchRate>100</branchRate>
</regex>
<regex>
<pattern>io.jsonwebtoken.impl.Base64UrlCodec</pattern>
<lineRate>100</lineRate>
<branchRate>95</branchRate>
</regex>
<regex>
<pattern>io.jsonwebtoken.impl.DefaultJwtBuilder</pattern>
<lineRate>91</lineRate>
@ -331,6 +321,7 @@
</regexes>
</check>
<formats>
<format>xml</format>
<format>html</format>
</formats>
</configuration>
@ -376,6 +367,11 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.eluder.coveralls</groupId>
<artifactId>coveralls-maven-plugin</artifactId>
<version>4.0.0</version>
</plugin>
</plugins>
</build>
@ -446,4 +442,4 @@
</profile>
</profiles>
</project>
</project>

View File

@ -22,6 +22,9 @@ package io.jsonwebtoken;
*/
public abstract class ClaimJwtException extends JwtException {
public static final String INCORRECT_EXPECTED_CLAIM_MESSAGE_TEMPLATE = "Expected %s claim to be: %s, but was: %s.";
public static final String MISSING_EXPECTED_CLAIM_MESSAGE_TEMPLATE = "Expected %s claim to be: %s, but was not present in the JWT claims.";
private final Header header;
private final Claims claims;

View File

@ -170,4 +170,5 @@ public interface Claims extends Map<String, Object>, ClaimsMutator<Claims> {
@Override //only for better/targeted JavaDoc
Claims setId(String jti);
<T> T get(String claimName, Class<T> requiredType);
}

View File

@ -0,0 +1,55 @@
/*
* Copyright (C) 2015 jsonwebtoken.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.jsonwebtoken;
/**
* Compresses and decompresses byte arrays according to a compression algorithm.
*
* @see io.jsonwebtoken.impl.compression.DeflateCompressionCodec
* @see io.jsonwebtoken.impl.compression.GzipCompressionCodec
* @since 0.6.0
*/
public interface CompressionCodec {
/**
* The algorithm name to use as the JWT's {@code calg} header value.
*
* @return the algorithm name to use as the JWT's {@code calg} header value.
*/
String getAlgorithmName();
/**
* Compresses the specified byte array according to the compression {@link #getAlgorithmName() algorithm}.
*
* @param payload bytes to compress
* @return compressed bytes
* @throws CompressionException if the specified byte array cannot be compressed according to the compression
* {@link #getAlgorithmName() algorithm}.
*/
byte[] compress(byte[] payload) throws CompressionException;
/**
* Decompresses the specified compressed byte array according to the compression
* {@link #getAlgorithmName() algorithm}. The specified byte array must already be in compressed form
* according to the {@link #getAlgorithmName() algorithm}.
*
* @param compressed compressed bytes
* @return decompressed bytes
* @throws CompressionException if the specified byte array cannot be decompressed according to the compression
* {@link #getAlgorithmName() algorithm}.
*/
byte[] decompress(byte[] compressed) throws CompressionException;
}

View File

@ -0,0 +1,46 @@
/*
* Copyright (C) 2015 jsonwebtoken.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.jsonwebtoken;
/**
* Looks for a JWT {@code calg} 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
* {@link io.jsonwebtoken.impl.compression.DeflateCompressionCodec DEFLATE}
* and {@link io.jsonwebtoken.impl.compression.GzipCompressionCodec GZIP} algorithms by default - you do not need to
* specify a {@code CompressionCodecResolver} in these cases.</p>
*
* <p>However, if you want to use a compression algorithm other than {@code DEF} or {@code GZIP}, you must implement
* your own {@link CompressionCodecResolver} and specify that when
* {@link io.jsonwebtoken.JwtBuilder#compressWith(CompressionCodec) building} and
* {@link io.jsonwebtoken.JwtParser#setCompressionCodecResolver(CompressionCodecResolver) parsing} JWTs.</p>
*
* @since 0.6.0
*/
public interface CompressionCodecResolver {
/**
* Looks for a JWT {@code calg} 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.
*/
CompressionCodec resolveCompressionCodec(Header header) throws CompressionException;
}

View File

@ -0,0 +1,33 @@
/*
* Copyright (C) 2015 jsonwebtoken.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.jsonwebtoken;
/**
* Exception indicating that either compressing or decompressing an JWT body failed.
*
* @since 0.6.0
*/
public class CompressionException extends JwtException {
public CompressionException(String message) {
super(message);
}
public CompressionException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -48,6 +48,9 @@ public interface Header<T extends Header<T>> extends Map<String,Object> {
/** JWT {@code Content Type} header parameter name: <code>"cty"</code> */
public static final String CONTENT_TYPE = "cty";
/** JWT {@code Compression Algorithm} header parameter name: <code>"calg"</code> */
public static final String COMPRESSION_ALGORITHM = "calg";
/**
* Returns the <a href="https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-25#section-5.1">
* <code>typ</code></a> (type) header value or {@code null} if not present.
@ -100,4 +103,25 @@ public interface Header<T extends Header<T>> extends Map<String,Object> {
*/
T setContentType(String cty);
/**
* Returns the JWT <code>calg</code> (Compression Algorithm) header value or {@code null} if not present.
*
* @return the {@code calg} header parameter value or {@code null} if not present.
* @since 0.6.0
*/
String getCompressionAlgorithm();
/**
* Sets the JWT <code>calg</code> (Compression Algorithm) header parameter value. A {@code null} value will remove
* the property from the JSON map.
* <p>
* <p>The compression algorithm is NOT part of the <a href="https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-25>JWT specification</a>
* and must be used carefully since, is not expected that other libraries (including previous versions of this one)
* be able to deserialize a compressed JTW body correctly. </p>
*
* @param calg the JWT compression algorithm {@code calg} value or {@code null} to remove the property from the JSON map.
* @since 0.6.0
*/
T setCompressionAlgorithm(String calg);
}

View File

@ -0,0 +1,32 @@
/*
* Copyright (C) 2015 jsonwebtoken.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.jsonwebtoken;
/**
* Exception thrown when discovering that a required claim does not equal the required value, indicating the JWT is
* invalid and may not be used.
*
* @since 0.6
*/
public class IncorrectClaimException extends InvalidClaimException {
public IncorrectClaimException(Header header, Claims claims, String message) {
super(header, claims, message);
}
public IncorrectClaimException(Header header, Claims claims, String message, Throwable cause) {
super(header, claims, message, cause);
}
}

View File

@ -0,0 +1,54 @@
/*
* Copyright (C) 2015 jsonwebtoken.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.jsonwebtoken;
/**
* Exception indicating a parsed claim is invalid in some way. Subclasses reflect the specific
* reason the claim is invalid.
*
* @see IncorrectClaimException
* @see MissingClaimException
*
* @since 0.6
*/
public class InvalidClaimException extends ClaimJwtException {
private String claimName;
private Object claimValue;
protected InvalidClaimException(Header header, Claims claims, String message) {
super(header, claims, message);
}
protected InvalidClaimException(Header header, Claims claims, String message, Throwable cause) {
super(header, claims, message, cause);
}
public String getClaimName() {
return claimName;
}
public void setClaimName(String claimName) {
this.claimName = claimName;
}
public Object getClaimValue() {
return claimValue;
}
public void setClaimValue(Object claimValue) {
this.claimValue = claimValue;
}
}

View File

@ -349,6 +349,26 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
*/
JwtBuilder signWith(SignatureAlgorithm alg, Key key);
/**
* Compresses the JWT body using the specified {@link CompressionCodec}.
*
* <p>If your compact JWTs are large, and you want to reduce their total size during network transmission, this
* can be useful. For example, when embedding JWTs in URLs, some browsers may not support URLs longer than a
* certain length. Using compression can help ensure the compact JWT fits within that length. However, NOTE:</p>
*
* <p><b>WARNING:</b> Compression is not defined by the JWT Specification, and it is not expected that other libraries
* (including JJWT versions < 0.6.0) are able to consume a compressed JWT body correctly. Only use this method
* if you are sure that you will consume the JWT with JJWT >= 0.6.0 or another library that you know implements
* the same behavior.</p>
*
* @see io.jsonwebtoken.impl.compression.CompressionCodecs
*
* @param codec implementation of the {@link CompressionCodec} to be used.
* @return the builder for method chaining.
* @since 0.6.0
*/
JwtBuilder compressWith(CompressionCodec codec);
/**
* Actually builds the JWT and serializes it to a compact, URL-safe string according to the
* <a href="https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-25#section-7">JWT Compact Serialization</a>

View File

@ -16,6 +16,7 @@
package io.jsonwebtoken;
import java.security.Key;
import java.util.Date;
/**
* A parser for reading JWT strings, used to convert them into a {@link Jwt} object representing the expanded JWT.
@ -26,13 +27,110 @@ public interface JwtParser {
public static final char SEPARATOR_CHAR = '.';
/**
* Ensures that the specified {@code jti} exists in the parsed JWT. If missing or if the parsed
* value does not equal the specified value, an exception will be thrown indicating that the
* JWT is invalid and may not be used.
*
* @param id
* @return the parser method for chaining.
* @see MissingClaimException
* @see IncorrectClaimException
*/
JwtParser requireId(String id);
/**
* Ensures that the specified {@code sub} exists in the parsed JWT. If missing or if the parsed
* value does not equal the specified value, an exception will be thrown indicating that the
* JWT is invalid and may not be used.
*
* @param subject
* @return the parser for method chaining.
* @see MissingClaimException
* @see IncorrectClaimException
*/
JwtParser requireSubject(String subject);
/**
* Ensures that the specified {@code aud} exists in the parsed JWT. If missing or if the parsed
* value does not equal the specified value, an exception will be thrown indicating that the
* JWT is invalid and may not be used.
*
* @param audience
* @return the parser for method chaining.
* @see MissingClaimException
* @see IncorrectClaimException
*/
JwtParser requireAudience(String audience);
/**
* Ensures that the specified {@code iss} exists in the parsed JWT. If missing or if the parsed
* value does not equal the specified value, an exception will be thrown indicating that the
* JWT is invalid and may not be used.
*
* @param issuer
* @return the parser for method chaining.
* @see MissingClaimException
* @see IncorrectClaimException
*/
JwtParser requireIssuer(String issuer);
/**
* Ensures that the specified {@code iat} exists in the parsed JWT. If missing or if the parsed
* value does not equal the specified value, an exception will be thrown indicating that the
* JWT is invalid and may not be used.
*
* @param issuedAt
* @return the parser for method chaining.
* @see MissingClaimException
* @see IncorrectClaimException
*/
JwtParser requireIssuedAt(Date issuedAt);
/**
* Ensures that the specified {@code exp} exists in the parsed JWT. If missing or if the parsed
* value does not equal the specified value, an exception will be thrown indicating that the
* JWT is invalid and may not be used.
*
* @param expiration
* @return the parser for method chaining.
* @see MissingClaimException
* @see IncorrectClaimException
*/
JwtParser requireExpiration(Date expiration);
/**
* Ensures that the specified {@code nbf} exists in the parsed JWT. If missing or if the parsed
* value does not equal the specified value, an exception will be thrown indicating that the
* JWT is invalid and may not be used.
*
* @param notBefore
* @return the parser for method chaining
* @see MissingClaimException
* @see IncorrectClaimException
*/
JwtParser requireNotBefore(Date notBefore);
/**
* Ensures that the specified {@code claimName} exists in the parsed JWT. If missing or if the parsed
* value does not equal the specified value, an exception will be thrown indicating that the
* JWT is invalid and may not be used.
*
* @param claimName
* @param value
* @return the parser for method chaining.
* @see MissingClaimException
* @see IncorrectClaimException
*/
JwtParser require(String claimName, Object value);
/**
* 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>
*
* @param key the algorithm-specific signature verification key used to validate any discovered JWS digital
@ -44,12 +142,12 @@ 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>
*
@ -62,12 +160,12 @@ 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>
*
@ -80,12 +178,12 @@ public interface JwtParser {
/**
* Sets the {@link SigningKeyResolver} used to acquire the <code>signing key</code> that should be used to verify
* a JWS's signature. If the parsed String is not a JWS (no signature), this resolver is not used.
*
* <p>
* <p>Specifying a {@code SigningKeyResolver} is necessary when the signing key is not already known before parsing
* the JWT and the JWT header or payload (plaintext body or Claims) must be inspected first to determine how to
* look up the signing key. Once returned by the resolver, the JwtParser will then verify the JWS signature with the
* returned key. For example:</p>
*
* <p>
* <pre>
* Jws&lt;Claims&gt; jws = Jwts.parser().setSigningKeyResolver(new SigningKeyResolverAdapter() {
* &#64;Override
@ -95,9 +193,9 @@ public interface JwtParser {
* }})
* .parseClaimsJws(compact);
* </pre>
*
* <p>
* <p>A {@code SigningKeyResolver} is invoked once during parsing before the signature is verified.</p>
*
* <p>
* <p>This method should only be used if a signing key is not provided by the other {@code setSigningKey*} builder
* methods.</p>
*
@ -107,10 +205,32 @@ public interface JwtParser {
*/
JwtParser setSigningKeyResolver(SigningKeyResolver signingKeyResolver);
/**
* Sets the {@link CompressionCodecResolver} used to acquire the {@link CompressionCodec} that should be used to
* decompress the JWT body. If the parsed JWT is not compressed, this resolver is not used.
* <p><b>NOTE:</b> Compression is not defined by the JWT Specification, and it is not expected that other libraries
* (including JJWT versions < 0.6.0) are able to consume a compressed JWT body correctly. This method is only
* useful if the compact JWT was compressed with JJWT >= 0.6.0 or another library that you know implements
* the same behavior.</p>
* <h5>Default Support</h5>
* <p>JJWT's default {@link JwtParser} implementation supports both the
* {@link io.jsonwebtoken.impl.compression.DeflateCompressionCodec DEFLATE}
* and {@link io.jsonwebtoken.impl.compression.GzipCompressionCodec GZIP} algorithms by default - you do not need to
* specify a {@code CompressionCodecResolver} in these cases.</p>
* <p>However, if you want to use a compression algorithm other than {@code DEF} or {@code GZIP}, you must implement
* your own {@link CompressionCodecResolver} and specify that via this method and also when
* {@link io.jsonwebtoken.JwtBuilder#compressWith(CompressionCodec) building} JWTs.</p>
*
* @param compressionCodecResolver the compression codec resolver used to decompress the JWT body.
* @return the parser for method chaining.
* @since 0.6.0
*/
JwtParser setCompressionCodecResolver(CompressionCodecResolver compressionCodecResolver);
/**
* Returns {@code true} if the specified JWT compact string represents a signed JWT (aka a 'JWS'), {@code false}
* otherwise.
*
* <p>
* <p>Note that if you are reasonably sure that the token is signed, it is more efficient to attempt to
* parse the token (and catching exceptions if necessary) instead of calling this method first before parsing.</p>
*
@ -123,7 +243,7 @@ public interface JwtParser {
/**
* Parses the specified compact serialized JWT string based on the builder's current configuration state and
* returns the resulting JWT or JWS instance.
*
* <p>
* <p>This method returns a JWT or JWS based on the parsed string. Because it may be cumbersome to determine if it
* is a JWT or JWS, or if the body/payload is a Claims or String with {@code instanceof} checks, the
* {@link #parse(String, JwtHandler) parse(String,JwtHandler)} method allows for a type-safe callback approach that
@ -150,11 +270,11 @@ public interface JwtParser {
/**
* Parses the specified compact serialized JWT string based on the builder's current configuration state and
* invokes the specified {@code handler} with the resulting JWT or JWS instance.
*
* <p>
* <p>If you are confident of the format of the JWT before parsing, you can create an anonymous subclass using the
* {@link io.jsonwebtoken.JwtHandlerAdapter JwtHandlerAdapter} and override only the methods you know are relevant
* for your use case(s), for example:</p>
*
* <p>
* <pre>
* String compactJwt = request.getParameter("jwt"); //we are confident this is a signed JWS
*
@ -165,10 +285,10 @@ public interface JwtParser {
* }
* });
* </pre>
*
* <p>
* <p>If you know the JWT string can be only one type of JWT, then it is even easier to invoke one of the
* following convenience methods instead of this one:</p>
*
* <p>
* <ul>
* <li>{@link #parsePlaintextJwt(String)}</li>
* <li>{@link #parseClaimsJwt(String)}</li>
@ -194,17 +314,17 @@ public interface JwtParser {
* @since 0.2
*/
<T> T parse(String jwt, JwtHandler<T> handler)
throws ExpiredJwtException, UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException;
throws ExpiredJwtException, UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException;
/**
* Parses the specified compact serialized JWT string based on the builder's current configuration state and
* returns
* the resulting unsigned plaintext JWT instance.
*
* <p>
* <p>This is a convenience method that is usable if you are confident that the compact string argument reflects an
* unsigned plaintext JWT. An unsigned plaintext JWT has a String (non-JSON) body payload and it is not
* cryptographically signed.</p>
*
* <p>
* <p><b>If the compact string presented does not reflect an unsigned plaintext JWT with non-JSON string body,
* an {@link UnsupportedJwtException} will be thrown.</b></p>
*
@ -224,17 +344,17 @@ public interface JwtParser {
* @since 0.2
*/
Jwt<Header, String> parsePlaintextJwt(String plaintextJwt)
throws UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException;
throws UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException;
/**
* Parses the specified compact serialized JWT string based on the builder's current configuration state and
* returns
* the resulting unsigned plaintext JWT instance.
*
* <p>
* <p>This is a convenience method that is usable if you are confident that the compact string argument reflects an
* unsigned Claims JWT. An unsigned Claims JWT has a {@link Claims} body and it is not cryptographically
* signed.</p>
*
* <p>
* <p><b>If the compact string presented does not reflect an unsigned Claims JWT, an
* {@link UnsupportedJwtException} will be thrown.</b></p>
*
@ -255,17 +375,17 @@ public interface JwtParser {
* @since 0.2
*/
Jwt<Header, Claims> parseClaimsJwt(String claimsJwt)
throws ExpiredJwtException, UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException;
throws ExpiredJwtException, UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException;
/**
* Parses the specified compact serialized JWS string based on the builder's current configuration state and
* returns
* the resulting plaintext JWS instance.
*
* <p>
* <p>This is a convenience method that is usable if you are confident that the compact string argument reflects a
* plaintext JWS. A plaintext JWS is a JWT with a String (non-JSON) body (payload) that has been
* cryptographically signed.</p>
*
* <p>
* <p><b>If the compact string presented does not reflect a plaintext JWS, an {@link UnsupportedJwtException}
* will be thrown.</b></p>
*
@ -283,16 +403,16 @@ public interface JwtParser {
* @since 0.2
*/
Jws<String> parsePlaintextJws(String plaintextJws)
throws UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException;
throws UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException;
/**
* Parses the specified compact serialized JWS string based on the builder's current configuration state and
* returns
* the resulting Claims JWS instance.
*
* <p>
* <p>This is a convenience method that is usable if you are confident that the compact string argument reflects a
* Claims JWS. A Claims JWS is a JWT with a {@link Claims} body that has been cryptographically signed.</p>
*
* <p>
* <p><b>If the compact string presented does not reflect a Claims JWS, an {@link UnsupportedJwtException} will be
* thrown.</b></p>
*
@ -312,5 +432,5 @@ public interface JwtParser {
* @since 0.2
*/
Jws<Claims> parseClaimsJws(String claimsJws)
throws ExpiredJwtException, UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException;
throws ExpiredJwtException, UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException;
}

View File

@ -0,0 +1,32 @@
/*
* Copyright (C) 2015 jsonwebtoken.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.jsonwebtoken;
/**
* Exception thrown when discovering that a required claim is not present, indicating the JWT is
* invalid and may not be used.
*
* @since 0.6
*/
public class MissingClaimException extends InvalidClaimException {
public MissingClaimException(Header header, Claims claims, String message) {
super(header, claims, message);
}
public MissingClaimException(Header header, Claims claims, String message, Throwable cause) {
super(header, claims, message, cause);
}
}

View File

@ -0,0 +1,32 @@
/*
* Copyright (C) 2014 jsonwebtoken.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.jsonwebtoken;
/**
* Exception thrown when {@link Claims#get(String, Class)} is called and the value does not match the type of the
* {@code Class} argument.
*
* @since 0.6
*/
public class RequiredTypeException extends JwtException {
public RequiredTypeException(String message) {
super(message);
}
public RequiredTypeException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -16,6 +16,7 @@
package io.jsonwebtoken.impl;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.RequiredTypeException;
import java.util.Date;
import java.util.Map;
@ -65,7 +66,7 @@ public class DefaultClaims extends JwtMap implements Claims {
@Override
public Date getExpiration() {
return getDate(Claims.EXPIRATION);
return get(Claims.EXPIRATION, Date.class);
}
@Override
@ -76,7 +77,7 @@ public class DefaultClaims extends JwtMap implements Claims {
@Override
public Date getNotBefore() {
return getDate(Claims.NOT_BEFORE);
return get(Claims.NOT_BEFORE, Date.class);
}
@Override
@ -87,7 +88,7 @@ public class DefaultClaims extends JwtMap implements Claims {
@Override
public Date getIssuedAt() {
return getDate(Claims.ISSUED_AT);
return get(Claims.ISSUED_AT, Date.class);
}
@Override
@ -106,4 +107,27 @@ public class DefaultClaims extends JwtMap implements Claims {
setValue(Claims.ID, jti);
return this;
}
@Override
public <T> T get(String claimName, Class<T> requiredType) {
Object value = get(claimName);
if (value == null) { return null; }
if (Claims.EXPIRATION.equals(claimName) ||
Claims.ISSUED_AT.equals(claimName) ||
Claims.NOT_BEFORE.equals(claimName)
) {
value = getDate(claimName);
}
if (requiredType == Date.class && value instanceof Long) {
value = new Date((Long)value);
}
if (!requiredType.isInstance(value)) {
throw new RequiredTypeException("Expected value to be of type: " + requiredType + ", but was " + value.getClass());
}
return requiredType.cast(value);
}
}

View File

@ -51,4 +51,16 @@ public class DefaultHeader<T extends Header<T>> extends JwtMap implements Header
setValue(CONTENT_TYPE, cty);
return (T)this;
}
@Override
public String getCompressionAlgorithm() {
return getString(COMPRESSION_ALGORITHM);
}
@Override
public T setCompressionAlgorithm(String compressionAlgorithm) {
setValue(COMPRESSION_ALGORITHM, compressionAlgorithm);
return (T) this;
}
}

View File

@ -18,7 +18,7 @@ package io.jsonwebtoken.impl;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.JwsHeader;
public class DefaultJws<B> implements Jws {
public class DefaultJws<B> implements Jws<B> {
private final JwsHeader header;
private final B body;
@ -44,4 +44,9 @@ public class DefaultJws<B> implements Jws {
public String getSignature() {
return this.signature;
}
@Override
public String toString() {
return "header=" + header + ",body=" + body + ",signature=" + signature;
}
}

View File

@ -37,4 +37,9 @@ public class DefaultJwt<B> implements Jwt<Header,B> {
public B getBody() {
return body;
}
@Override
public String toString() {
return "header=" + header + ",body=" + body;
}
}

View File

@ -18,6 +18,7 @@ package io.jsonwebtoken.impl;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.CompressionCodec;
import io.jsonwebtoken.Header;
import io.jsonwebtoken.JwsHeader;
import io.jsonwebtoken.JwtBuilder;
@ -48,6 +49,8 @@ public class DefaultJwtBuilder implements JwtBuilder {
private Key key;
private byte[] keyBytes;
private CompressionCodec compressionCodec;
@Override
public JwtBuilder setHeader(Header header) {
this.header = header;
@ -113,6 +116,13 @@ public class DefaultJwtBuilder implements JwtBuilder {
return this;
}
@Override
public JwtBuilder compressWith(CompressionCodec compressionCodec) {
Assert.notNull(compressionCodec, "compressionCodec cannot be null");
this.compressionCodec = compressionCodec;
return this;
}
@Override
public JwtBuilder setPayload(String payload) {
this.payload = payload;
@ -279,11 +289,30 @@ public class DefaultJwtBuilder implements JwtBuilder {
jwsHeader.setAlgorithm(SignatureAlgorithm.NONE.getValue());
}
if (compressionCodec != null) {
jwsHeader.setCompressionAlgorithm(compressionCodec.getAlgorithmName());
}
String base64UrlEncodedHeader = base64UrlEncode(jwsHeader, "Unable to serialize header to json.");
String base64UrlEncodedBody = this.payload != null ?
TextCodec.BASE64URL.encode(this.payload) :
base64UrlEncode(claims, "Unable to serialize claims object to json.");
String base64UrlEncodedBody;
if (compressionCodec != null) {
byte[] bytes;
try {
bytes = this.payload != null ? payload.getBytes(Strings.UTF_8) : toJson(claims);
} catch (JsonProcessingException e) {
throw new IllegalArgumentException("Unable to serialize claims object to json.");
}
base64UrlEncodedBody = TextCodec.BASE64URL.encode(compressionCodec.compress(bytes));
} else {
base64UrlEncodedBody = this.payload != null ?
TextCodec.BASE64URL.encode(this.payload) :
base64UrlEncode(claims, "Unable to serialize claims object to json.");
}
String jwt = base64UrlEncodedHeader + JwtParser.SEPARATOR_CHAR + base64UrlEncodedBody;
@ -311,17 +340,18 @@ public class DefaultJwtBuilder implements JwtBuilder {
}
protected String base64UrlEncode(Object o, String errMsg) {
String s;
byte[] bytes;
try {
s = toJson(o);
bytes = toJson(o);
} catch (JsonProcessingException e) {
throw new IllegalStateException(errMsg, e);
}
return TextCodec.BASE64URL.encode(s);
return TextCodec.BASE64URL.encode(bytes);
}
protected String toJson(Object o) throws JsonProcessingException {
return OBJECT_MAPPER.writeValueAsString(o);
protected byte[] toJson(Object object) throws JsonProcessingException {
return OBJECT_MAPPER.writeValueAsBytes(object);
}
}

View File

@ -16,21 +16,28 @@
package io.jsonwebtoken.impl;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.ClaimJwtException;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.CompressionCodec;
import io.jsonwebtoken.CompressionCodecResolver;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Header;
import io.jsonwebtoken.IncorrectClaimException;
import io.jsonwebtoken.InvalidClaimException;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.JwsHeader;
import io.jsonwebtoken.SigningKeyResolver;
import io.jsonwebtoken.Jwt;
import io.jsonwebtoken.JwtHandler;
import io.jsonwebtoken.JwtHandlerAdapter;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.MissingClaimException;
import io.jsonwebtoken.PrematureJwtException;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.SignatureException;
import io.jsonwebtoken.SigningKeyResolver;
import io.jsonwebtoken.UnsupportedJwtException;
import io.jsonwebtoken.impl.compression.DefaultCompressionCodecResolver;
import io.jsonwebtoken.impl.crypto.DefaultJwtSignatureValidator;
import io.jsonwebtoken.impl.crypto.JwtSignatureValidator;
import io.jsonwebtoken.lang.Assert;
@ -58,6 +65,68 @@ public class DefaultJwtParser implements JwtParser {
private SigningKeyResolver signingKeyResolver;
private CompressionCodecResolver compressionCodecResolver = new DefaultCompressionCodecResolver();
Claims expectedClaims = new DefaultClaims();
@Override
public JwtParser requireIssuedAt(Date issuedAt) {
expectedClaims.setIssuedAt(issuedAt);
return this;
}
@Override
public JwtParser requireIssuer(String issuer) {
expectedClaims.setIssuer(issuer);
return this;
}
@Override
public JwtParser requireAudience(String audience) {
expectedClaims.setAudience(audience);
return this;
}
@Override
public JwtParser requireSubject(String subject) {
expectedClaims.setSubject(subject);
return this;
}
@Override
public JwtParser requireId(String id) {
expectedClaims.setId(id);
return this;
}
@Override
public JwtParser requireExpiration(Date expiration) {
expectedClaims.setExpiration(expiration);
return this;
}
@Override
public JwtParser requireNotBefore(Date notBefore) {
expectedClaims.setNotBefore(notBefore);
return this;
}
@Override
public JwtParser require(String claimName, Object value) {
Assert.hasText(claimName, "claim name cannot be null or empty.");
Assert.notNull(value, "The value cannot be null for claim name: " + claimName);
expectedClaims.put(claimName, value);
return this;
}
@Override
public JwtParser setSigningKey(byte[] key) {
Assert.notEmpty(key, "signing key cannot be null or empty.");
@ -86,6 +155,13 @@ public class DefaultJwtParser implements JwtParser {
return this;
}
@Override
public JwtParser setCompressionCodecResolver(CompressionCodecResolver compressionCodecResolver) {
Assert.notNull(compressionCodecResolver, "compressionCodecResolver cannot be null.");
this.compressionCodecResolver = compressionCodecResolver;
return this;
}
@Override
public boolean isSigned(String jwt) {
@ -157,6 +233,8 @@ public class DefaultJwtParser implements JwtParser {
// =============== Header =================
Header header = null;
CompressionCodec compressionCodec = null;
if (base64UrlEncodedHeader != null) {
String origValue = TextCodec.BASE64URL.decodeToString(base64UrlEncodedHeader);
Map<String, Object> m = readValue(origValue);
@ -166,10 +244,18 @@ public class DefaultJwtParser implements JwtParser {
} else {
header = new DefaultHeader(m);
}
compressionCodec = compressionCodecResolver.resolveCompressionCodec(header);
}
// =============== Body =================
String payload = TextCodec.BASE64URL.decodeToString(base64UrlEncodedPayload);
String payload;
if (compressionCodec != null) {
byte[] decompressed = compressionCodec.decompress(TextCodec.BASE64URL.decode(base64UrlEncodedPayload));
payload = new String(decompressed, Strings.UTF_8);
} else {
payload = TextCodec.BASE64URL.decodeToString(base64UrlEncodedPayload);
}
Claims claims = null;
@ -298,6 +384,8 @@ public class DefaultJwtParser implements JwtParser {
throw new PrematureJwtException(header, claims, msg);
}
}
validateExpectedClaims(header, claims);
}
Object body = claims != null ? claims : payload;
@ -309,6 +397,51 @@ public class DefaultJwtParser implements JwtParser {
}
}
private void validateExpectedClaims(Header header, Claims claims) {
for (String expectedClaimName : expectedClaims.keySet()) {
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)
) {
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);
}
InvalidClaimException invalidClaimException = null;
if (actualClaimValue == null) {
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
);
invalidClaimException = new IncorrectClaimException(header, claims, msg);
}
if (invalidClaimException != null) {
invalidClaimException.setClaimName(expectedClaimName);
invalidClaimException.setClaimValue(expectedClaimValue);
throw invalidClaimException;
}
}
}
/*
* @since 0.5 mostly to allow testing overrides
*/

View File

@ -150,4 +150,9 @@ public class JwtMap implements Map<String,Object> {
public Set<Entry<String, Object>> entrySet() {
return map.entrySet();
}
@Override
public String toString() {
return map.toString();
}
}

View File

@ -0,0 +1,84 @@
/*
* Copyright (C) 2015 jsonwebtoken.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.jsonwebtoken.impl.compression;
import io.jsonwebtoken.CompressionCodec;
import io.jsonwebtoken.CompressionException;
import io.jsonwebtoken.lang.Assert;
import java.io.IOException;
/**
* Abstract class that asserts arguments and wraps IOException with CompressionException.
*
* @since 0.6.0
*/
public abstract class AbstractCompressionCodec implements CompressionCodec {
/**
* Implement this method to do the actual work of compressing the payload
*
* @param payload the bytes to compress
* @return the compressed bytes
* @throws IOException if the compression causes an IOException
*/
protected abstract byte[] doCompress(byte[] payload) throws IOException;
/**
* Asserts that payload is not null and calls {@link #doCompress(byte[]) doCompress}
*
* @param payload bytes to compress
* @return compressed bytes
* @throws CompressionException if {@link #doCompress(byte[]) doCompress} throws an IOException
*/
@Override
public final byte[] compress(byte[] payload) {
Assert.notNull(payload, "payload cannot be null.");
try {
return doCompress(payload);
} catch (IOException e) {
throw new CompressionException("Unable to compress payload.", e);
}
}
/**
* Asserts the compressed bytes is not null and calls {@link #doDecompress(byte[]) doDecompress}
*
* @param compressed compressed bytes
* @return decompressed bytes
* @throws CompressionException if {@link #doDecompress(byte[]) doDecompress} throws an IOException
*/
@Override
public final byte[] decompress(byte[] compressed) {
Assert.notNull(compressed, "compressed bytes cannot be null.");
try {
return doDecompress(compressed);
} catch (IOException e) {
throw new CompressionException("Unable to decompress bytes.", e);
}
}
/**
* Implement this method to do the actual work of decompressing the compressed bytes.
*
* @param compressed compressed bytes
* @return decompressed bytes
* @throws IOException if the decompression runs into an IO problem
*/
protected abstract byte[] doDecompress(byte[] compressed) throws IOException;
}

View File

@ -0,0 +1,44 @@
/*
* Copyright (C) 2015 jsonwebtoken.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.jsonwebtoken.impl.compression;
import io.jsonwebtoken.CompressionCodec;
/**
* Provides default implementations of the {@link CompressionCodec} interface.
*
* @see #DEFLATE
* @see #GZIP
*
* @since 0.6.0
*/
public final class CompressionCodecs {
private static final CompressionCodecs I = new CompressionCodecs();
private CompressionCodecs(){} //prevent external instantiation
/**
* Codec implementing the <a href="https://en.wikipedia.org/wiki/DEFLATE">deflate</a> compression algorithm
*/
public static final CompressionCodec DEFLATE = new DeflateCompressionCodec();
/**
* Codec implementing the <a href="https://en.wikipedia.org/wiki/Gzip">gzip</a> compression algorithm
*/
public static final CompressionCodec GZIP = new GzipCompressionCodec();
}

View File

@ -0,0 +1,72 @@
/*
* Copyright (C) 2015 jsonwebtoken.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.jsonwebtoken.impl.compression;
import io.jsonwebtoken.CompressionCodec;
import io.jsonwebtoken.CompressionCodecResolver;
import io.jsonwebtoken.CompressionException;
import io.jsonwebtoken.Header;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Strings;
/**
* Default implementation of {@link CompressionCodecResolver} that supports the following:
* <p>
* <ul>
* <li>If the specified JWT {@link Header} does not have a {@code calg} 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
* unrecognized algorithm.</li>
* </ul>
*
* <p>If you want to use a compression algorithm other than {@code DEF} or {@code GZIP}, you must implement your own
* {@link CompressionCodecResolver} and specify that when
* {@link io.jsonwebtoken.JwtBuilder#compressWith(CompressionCodec) building} and
* {@link io.jsonwebtoken.JwtParser#setCompressionCodecResolver(CompressionCodecResolver) parsing} JWTs.</p>
*
* @see DeflateCompressionCodec
* @see GzipCompressionCodec
* @since 0.6.0
*/
public class DefaultCompressionCodecResolver implements CompressionCodecResolver {
@Override
public CompressionCodec resolveCompressionCodec(Header header) {
String cmpAlg = getAlgorithmFromHeader(header);
final boolean hasCompressionAlgorithm = Strings.hasText(cmpAlg);
if (!hasCompressionAlgorithm) {
return null;
}
if (CompressionCodecs.DEFLATE.getAlgorithmName().equalsIgnoreCase(cmpAlg)) {
return CompressionCodecs.DEFLATE;
}
if (CompressionCodecs.GZIP.getAlgorithmName().equalsIgnoreCase(cmpAlg)) {
return CompressionCodecs.GZIP;
}
throw new CompressionException("Unsupported compression algorithm '" + cmpAlg + "'");
}
private String getAlgorithmFromHeader(Header header) {
Assert.notNull(header, "header cannot be null.");
return header.getCompressionAlgorithm();
}
}

View File

@ -0,0 +1,74 @@
/*
* Copyright (C) 2015 jsonwebtoken.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.jsonwebtoken.impl.compression;
import io.jsonwebtoken.lang.Objects;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.InflaterOutputStream;
/**
* Codec implementing the <a href="https://en.wikipedia.org/wiki/DEFLATE">deflate compression algorithm</a>.
*
* @since 0.6.0
*/
public class DeflateCompressionCodec extends AbstractCompressionCodec {
private static final String DEFLATE = "DEF";
@Override
public String getAlgorithmName() {
return DEFLATE;
}
@Override
public byte[] doCompress(byte[] payload) throws IOException {
Deflater deflater = new Deflater(Deflater.BEST_COMPRESSION);
ByteArrayOutputStream outputStream = null;
DeflaterOutputStream deflaterOutputStream = null;
try {
outputStream = new ByteArrayOutputStream();
deflaterOutputStream = new DeflaterOutputStream(outputStream, deflater, true);
deflaterOutputStream.write(payload, 0, payload.length);
deflaterOutputStream.flush();
return outputStream.toByteArray();
} finally {
Objects.nullSafeClose(outputStream, deflaterOutputStream);
}
}
@Override
public byte[] doDecompress(byte[] compressed) throws IOException {
InflaterOutputStream inflaterOutputStream = null;
ByteArrayOutputStream decompressedOutputStream = null;
try {
decompressedOutputStream = new ByteArrayOutputStream();
inflaterOutputStream = new InflaterOutputStream(decompressedOutputStream);
inflaterOutputStream.write(compressed);
inflaterOutputStream.flush();
return decompressedOutputStream.toByteArray();
} finally {
Objects.nullSafeClose(decompressedOutputStream, inflaterOutputStream);
}
}
}

View File

@ -0,0 +1,74 @@
/*
* Copyright (C) 2015 jsonwebtoken.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.jsonwebtoken.impl.compression;
import io.jsonwebtoken.CompressionCodec;
import io.jsonwebtoken.lang.Objects;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
/**
* Codec implementing the <a href="https://en.wikipedia.org/wiki/Gzip">gzip compression algorithm</a>.
*
* @since 0.6.0
*/
public class GzipCompressionCodec extends AbstractCompressionCodec implements CompressionCodec {
private static final String GZIP = "GZIP";
@Override
public String getAlgorithmName() {
return GZIP;
}
@Override
protected byte[] doDecompress(byte[] compressed) throws IOException {
byte[] buffer = new byte[512];
ByteArrayOutputStream outputStream = null;
GZIPInputStream gzipInputStream = null;
ByteArrayInputStream inputStream = null;
try {
inputStream = new ByteArrayInputStream(compressed);
gzipInputStream = new GZIPInputStream(inputStream);
outputStream = new ByteArrayOutputStream();
int read;
while ((read = gzipInputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, read);
}
return outputStream.toByteArray();
} finally {
Objects.nullSafeClose(inputStream, gzipInputStream, outputStream);
}
}
protected byte[] doCompress(byte[] payload) throws IOException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
GZIPOutputStream compressorOutputStream = new GZIPOutputStream(outputStream, true);
try {
compressorOutputStream.write(payload, 0, payload.length);
compressorOutputStream.finish();
return outputStream.toByteArray();
} finally {
Objects.nullSafeClose(compressorOutputStream, outputStream);
}
}
}

View File

@ -18,6 +18,7 @@ package io.jsonwebtoken.impl.crypto;
import io.jsonwebtoken.SignatureAlgorithm;
import java.security.Key;
import java.security.MessageDigest;
import java.util.Arrays;
public class MacValidator implements SignatureValidator {
@ -31,6 +32,6 @@ public class MacValidator implements SignatureValidator {
@Override
public boolean isValid(byte[] data, byte[] signature) {
byte[] computed = this.signer.sign(data);
return Arrays.equals(computed, signature);
return MessageDigest.isEqual(computed, signature);
}
}

View File

@ -22,15 +22,17 @@ import java.security.InvalidKeyException;
import java.security.Key;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAKey;
public class RsaSigner extends RsaProvider implements Signer {
public RsaSigner(SignatureAlgorithm alg, Key key) {
super(alg, key);
if (!(key instanceof RSAPrivateKey)) {
String msg = "RSA signatures must be computed using an RSAPrivateKey. The specified key of type " +
key.getClass().getName() + " is not an RSAPrivateKey.";
// https://github.com/jwtk/jjwt/issues/68
// Instead of checking for an instance of RSAPrivateKey, check for PrivateKey and RSAKey:
if (!(key instanceof PrivateKey && key instanceof RSAKey)) {
String msg = "RSA signatures must be computed using an RSA PrivateKey. The specified key of type " +
key.getClass().getName() + " is not an RSA PrivateKey.";
throw new IllegalArgumentException(msg);
}
}

View File

@ -15,9 +15,6 @@
*/
package io.jsonwebtoken.lang;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.InputStream;
import java.lang.reflect.Constructor;
@ -26,11 +23,6 @@ import java.lang.reflect.Constructor;
*/
public class Classes {
/**
* Private internal log instance.
*/
private static final Logger log = LoggerFactory.getLogger(Classes.class);
/**
* @since 0.1
*/
@ -78,18 +70,10 @@ public class Classes {
Class clazz = THREAD_CL_ACCESSOR.loadClass(fqcn);
if (clazz == null) {
if (log.isTraceEnabled()) {
log.trace("Unable to load class named [" + fqcn +
"] from the thread context ClassLoader. Trying the current ClassLoader...");
}
clazz = CLASS_CL_ACCESSOR.loadClass(fqcn);
}
if (clazz == null) {
if (log.isTraceEnabled()) {
log.trace("Unable to load class named [" + fqcn + "] from the current ClassLoader. " +
"Trying the system/application ClassLoader...");
}
clazz = SYSTEM_CL_ACCESSOR.loadClass(fqcn);
}
@ -212,9 +196,7 @@ public class Classes {
try {
clazz = cl.loadClass(fqcn);
} catch (ClassNotFoundException e) {
if (log.isTraceEnabled()) {
log.trace("Unable to load clazz named [" + fqcn + "] from class loader [" + cl + "]");
}
//Class couldn't be found by loader
}
}
return clazz;
@ -233,9 +215,7 @@ public class Classes {
try {
return doGetClassLoader();
} catch (Throwable t) {
if (log.isDebugEnabled()) {
log.debug("Unable to acquire ClassLoader.", t);
}
//Unable to get ClassLoader
}
return null;
}

View File

@ -15,6 +15,8 @@
*/
package io.jsonwebtoken.lang;
import java.io.Closeable;
import java.io.IOException;
import java.lang.reflect.Array;
import java.util.Arrays;
@ -905,4 +907,19 @@ public abstract class Objects {
return sb.toString();
}
public static void nullSafeClose(Closeable... closeables) {
if (closeables == null) {
return;
}
for (Closeable closeable : closeables) {
if (closeable != null) {
try {
closeable.close();
} catch (IOException e) {
//Ignore the exception during close.
}
}
}
}
}

View File

@ -15,6 +15,7 @@
*/
package io.jsonwebtoken.lang;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@ -40,6 +41,8 @@ public abstract class Strings {
private static final char EXTENSION_SEPARATOR = '.';
public static final Charset UTF_8 = Charset.forName("UTF-8");
//---------------------------------------------------------------------
// General convenience methods for working with Strings
//---------------------------------------------------------------------

View File

@ -0,0 +1,41 @@
/*
* Copyright (C) 2015 jsonwebtoken.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.jsonwebtoken
import org.junit.Test
import static org.junit.Assert.assertEquals
class CompressionExceptionTest {
@Test
void testDefaultConstructor() {
def exception = new CompressionException("my message")
assertEquals "my message", exception.getMessage()
}
@Test
void testConstructorWithCause() {
def ioException = new IOException("root error")
def exception = new CompressionException("wrapping", ioException)
assertEquals "wrapping", exception.getMessage()
assertEquals ioException, exception.getCause()
}
}

View File

@ -0,0 +1,46 @@
/*
* Copyright (C) 2015 jsonwebtoken.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.jsonwebtoken
import org.junit.Test
import static org.junit.Assert.assertEquals
import static org.junit.Assert.assertSame
class IncorrectClaimExceptionTest {
@Test
void testOverloadedConstructor() {
def header = Jwts.header()
def claims = Jwts.claims()
def msg = 'foo'
def cause = new NullPointerException()
def claimName = 'cName'
def claimValue = 'cValue'
def ex = new IncorrectClaimException(header, claims, msg, cause)
ex.setClaimName(claimName)
ex.setClaimValue(claimValue)
assertSame ex.header, header
assertSame ex.claims, claims
assertEquals ex.message, msg
assertSame ex.cause, cause
assertEquals ex.claimName, claimName
assertEquals ex.claimValue, claimValue
}
}

View File

@ -0,0 +1,46 @@
/*
* Copyright (C) 2015 jsonwebtoken.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.jsonwebtoken
import org.junit.Test
import static org.junit.Assert.assertEquals
import static org.junit.Assert.assertSame
class InvalidClaimExceptionTest {
@Test
void testOverloadedConstructor() {
def header = Jwts.header()
def claims = Jwts.claims()
def msg = 'foo'
def cause = new NullPointerException()
def claimName = 'cName'
def claimValue = 'cValue'
def ex = new InvalidClaimException(header, claims, msg, cause)
ex.setClaimName(claimName)
ex.setClaimValue(claimValue)
assertSame ex.header, header
assertSame ex.claims, claims
assertEquals ex.message, msg
assertSame ex.cause, cause
assertEquals ex.claimName, claimName
assertEquals ex.claimValue, claimValue
}
}

File diff suppressed because it is too large Load Diff

View File

@ -19,9 +19,13 @@ import com.fasterxml.jackson.databind.ObjectMapper
import io.jsonwebtoken.impl.DefaultHeader
import io.jsonwebtoken.impl.DefaultJwsHeader
import io.jsonwebtoken.impl.TextCodec
import io.jsonwebtoken.impl.compression.CompressionCodecs
import io.jsonwebtoken.impl.compression.DefaultCompressionCodecResolver
import io.jsonwebtoken.impl.compression.GzipCompressionCodec
import io.jsonwebtoken.impl.crypto.EllipticCurveProvider
import io.jsonwebtoken.impl.crypto.MacProvider
import io.jsonwebtoken.impl.crypto.RsaProvider
import io.jsonwebtoken.lang.Strings
import org.junit.Test
import javax.crypto.Mac
@ -108,6 +112,7 @@ class JwtsTest {
def token = Jwts.parser().parse(jwt);
//noinspection GrEqualsBetweenInconvertibleTypes
assert token.body == claims
}
@ -186,6 +191,16 @@ class JwtsTest {
}
}
@Test
void testWithInvalidCompressionAlgorithm() {
try {
Jwts.builder().setHeaderParam(Header.COMPRESSION_ALGORITHM, "CUSTOM").setId("andId").compact()
} catch (CompressionException e) {
assertEquals "Unsupported compression algorithm 'CUSTOM'", e.getMessage()
}
}
@Test
void testConvenienceIssuer() {
String compact = Jwts.builder().setIssuer("Me").compact();
@ -320,6 +335,140 @@ class JwtsTest {
assertNull claims.getId()
}
@Test
void testUncompressedJwt() {
byte[] key = MacProvider.generateKey().getEncoded()
String id = UUID.randomUUID().toString()
String compact = Jwts.builder().setId(id).setAudience("an audience").signWith(SignatureAlgorithm.HS256, key)
.claim("state", "hello this is an amazing jwt").compact()
def jws = Jwts.parser().setSigningKey(key).parseClaimsJws(compact)
Claims claims = jws.body
assertNull jws.header.getCompressionAlgorithm()
assertEquals id, claims.getId()
assertEquals "an audience", claims.getAudience()
assertEquals "hello this is an amazing jwt", claims.state
}
@Test
void testCompressedJwtWithDeflate() {
byte[] key = MacProvider.generateKey().getEncoded()
String id = UUID.randomUUID().toString()
String compact = Jwts.builder().setId(id).setAudience("an audience").signWith(SignatureAlgorithm.HS256, key)
.claim("state", "hello this is an amazing jwt").compressWith(CompressionCodecs.DEFLATE).compact()
def jws = Jwts.parser().setSigningKey(key).parseClaimsJws(compact)
Claims claims = jws.body
assertEquals "DEF", jws.header.getCompressionAlgorithm()
assertEquals id, claims.getId()
assertEquals "an audience", claims.getAudience()
assertEquals "hello this is an amazing jwt", claims.state
}
@Test
void testCompressedJwtWithGZIP() {
byte[] key = MacProvider.generateKey().getEncoded()
String id = UUID.randomUUID().toString()
String compact = Jwts.builder().setId(id).setAudience("an audience").signWith(SignatureAlgorithm.HS256, key)
.claim("state", "hello this is an amazing jwt").compressWith(CompressionCodecs.GZIP).compact()
def jws = Jwts.parser().setSigningKey(key).parseClaimsJws(compact)
Claims claims = jws.body
assertEquals "GZIP", jws.header.getCompressionAlgorithm()
assertEquals id, claims.getId()
assertEquals "an audience", claims.getAudience()
assertEquals "hello this is an amazing jwt", claims.state
}
@Test
void testCompressedWithCustomResolver() {
byte[] key = MacProvider.generateKey().getEncoded()
String id = UUID.randomUUID().toString()
String compact = Jwts.builder().setId(id).setAudience("an audience").signWith(SignatureAlgorithm.HS256, key)
.claim("state", "hello this is an amazing jwt").compressWith(new GzipCompressionCodec() {
@Override
String getAlgorithmName() {
return "CUSTOM"
}
}).compact()
def jws = Jwts.parser().setSigningKey(key).setCompressionCodecResolver(new DefaultCompressionCodecResolver() {
@Override
CompressionCodec resolveCompressionCodec(Header header) {
String algorithm = header.getCompressionAlgorithm()
if ("CUSTOM".equals(algorithm)) {
return CompressionCodecs.GZIP
} else {
return null
}
}
}).parseClaimsJws(compact)
Claims claims = jws.body
assertEquals "CUSTOM", jws.header.getCompressionAlgorithm()
assertEquals id, claims.getId()
assertEquals "an audience", claims.getAudience()
assertEquals "hello this is an amazing jwt", claims.state
}
@Test(expected = CompressionException.class)
void testCompressedJwtWithUnrecognizedHeader() {
byte[] key = MacProvider.generateKey().getEncoded()
String id = UUID.randomUUID().toString()
String compact = Jwts.builder().setId(id).setAudience("an audience").signWith(SignatureAlgorithm.HS256, key)
.claim("state", "hello this is an amazing jwt").compressWith(new GzipCompressionCodec() {
@Override
String getAlgorithmName() {
return "CUSTOM"
}
}).compact()
Jwts.parser().setSigningKey(key).parseClaimsJws(compact)
}
@Test
void testCompressStringPayloadWithDeflate() {
byte[] key = MacProvider.generateKey().getEncoded()
String payload = "this is my test for a payload"
String compact = Jwts.builder().setPayload(payload).signWith(SignatureAlgorithm.HS256, key)
.compressWith(CompressionCodecs.DEFLATE).compact()
def jws = Jwts.parser().setSigningKey(key).parsePlaintextJws(compact)
String parsed = jws.body
assertEquals "DEF", jws.header.getCompressionAlgorithm()
assertEquals "this is my test for a payload", parsed
}
@Test
void testHS256() {
testHmac(SignatureAlgorithm.HS256);
@ -571,6 +720,7 @@ class JwtsTest {
def token = Jwts.parser().setSigningKey(key).parse(jwt);
assert [alg: alg.name()] == token.header
//noinspection GrEqualsBetweenInconvertibleTypes
assert token.body == claims
}
@ -585,6 +735,7 @@ class JwtsTest {
def token = Jwts.parser().setSigningKey(key).parse(jwt)
assert token.header == [alg: alg.name()]
//noinspection GrEqualsBetweenInconvertibleTypes
assert token.body == claims
}
@ -606,6 +757,7 @@ class JwtsTest {
def token = Jwts.parser().setSigningKey(key).parse(jwt);
assert token.header == [alg: alg.name()]
//noinspection GrEqualsBetweenInconvertibleTypes
assert token.body == claims
}
}

View File

@ -0,0 +1,46 @@
/*
* Copyright (C) 2015 jsonwebtoken.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.jsonwebtoken
import org.junit.Test
import static org.junit.Assert.assertEquals
import static org.junit.Assert.assertSame
class MissingClaimExceptionTest {
@Test
void testOverloadedConstructor() {
def header = Jwts.header()
def claims = Jwts.claims()
def msg = 'foo'
def cause = new NullPointerException()
def claimName = 'cName'
def claimValue = 'cValue'
def ex = new MissingClaimException(header, claims, msg, cause)
ex.setClaimName(claimName)
ex.setClaimValue(claimValue)
assertSame ex.header, header
assertSame ex.claims, claims
assertEquals ex.message, msg
assertSame ex.cause, cause
assertEquals ex.claimName, claimName
assertEquals ex.claimValue, claimValue
}
}

View File

@ -0,0 +1,20 @@
package io.jsonwebtoken
import org.junit.Test
import static org.junit.Assert.assertEquals
import static org.junit.Assert.assertSame
class RequiredTypeExceptionTest {
@Test
void testOverloadedConstructor() {
def msg = 'foo'
def cause = new NullPointerException()
def ex = new RequiredTypeException(msg, cause)
assertEquals ex.message, msg
assertSame ex.cause, cause
}
}

View File

@ -0,0 +1,19 @@
package io.jsonwebtoken.impl
import org.junit.Test
import static org.junit.Assert.*
class Base64UrlCodecTest {
@Test
void testRemovePaddingWithEmptyByteArray() {
def codec = new Base64UrlCodec()
byte[] empty = new byte[0];
def result = codec.removePadding(empty)
assertSame empty, result
}
}

View File

@ -0,0 +1,103 @@
/*
* Copyright (C) 2015 jsonwebtoken.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.jsonwebtoken.impl
import io.jsonwebtoken.Claims
import io.jsonwebtoken.RequiredTypeException
import org.junit.Before
import org.junit.Test
import static org.junit.Assert.*
class DefaultClaimsTest {
Claims claims
@Before
void setup() {
claims = new DefaultClaims()
}
@Test
void testGetClaimWithRequiredType_Null_Success() {
claims.put("aNull", null)
Object result = claims.get("aNull", Integer.class)
assertNull(result)
}
@Test
void testGetClaimWithRequiredType_Exception() {
claims.put("anInteger", new Integer(5))
try {
claims.get("anInteger", String.class)
fail()
} catch (RequiredTypeException e) {
assertEquals(
"Expected value to be of type: class java.lang.String, but was class java.lang.Integer",
e.getMessage()
)
}
}
@Test
void testGetClaimWithRequiredType_Success() {
claims.put("anInteger", new Integer(5))
Object result = claims.get("anInteger", Integer.class)
assertTrue(result instanceof Integer)
}
@Test
void testGetClaimWithRequiredType_Date_Success() {
def actual = new Date();
claims.put("aDate", actual)
Date expected = claims.get("aDate", Date.class);
assertEquals(expected, actual)
}
@Test
void testGetClaimWithRequiredType_DateWithLong_Success() {
def actual = new Date();
// note that Long is stored in claim
claims.put("aDate", actual.getTime())
Date expected = claims.get("aDate", Date.class);
assertEquals(expected, actual)
}
@Test
void testGetClaimExpiration_Success() {
def now = new Date(System.currentTimeMillis())
claims.setExpiration(now)
Date expected = claims.get("exp", Date.class)
assertEquals(expected, claims.getExpiration())
}
@Test
void testGetClaimIssuedAt_Success() {
def now = new Date(System.currentTimeMillis())
claims.setIssuedAt(now)
Date expected = claims.get("iat", Date.class)
assertEquals(expected, claims.getIssuedAt())
}
@Test
void testGetClaimNotBefore_Success() {
def now = new Date(System.currentTimeMillis())
claims.setNotBefore(now)
Date expected = claims.get("nbf", Date.class)
assertEquals(expected, claims.getNotBefore())
}
}

View File

@ -17,6 +17,8 @@ package io.jsonwebtoken.impl
import io.jsonwebtoken.JwsHeader
import io.jsonwebtoken.Jwts
import io.jsonwebtoken.SignatureAlgorithm
import io.jsonwebtoken.impl.crypto.MacProvider
import org.junit.Test
import static org.junit.Assert.*
@ -32,4 +34,15 @@ class DefaultJwsTest {
assertEquals jws.getBody(), 'foo'
assertEquals jws.getSignature(), 'sig'
}
@Test
void testToString() {
//create random signing key for testing:
byte[] key = MacProvider.generateKey().encoded
String compact = Jwts.builder().claim('foo', 'bar').signWith(SignatureAlgorithm.HS256, key).compact();
int i = compact.lastIndexOf('.')
String signature = compact.substring(i + 1)
def jws = Jwts.parser().setSigningKey(key).parseClaimsJws(compact)
assertEquals 'header={alg=HS256},body={foo=bar},signature=' + signature, jws.toString()
}
}

View File

@ -19,6 +19,7 @@ import com.fasterxml.jackson.core.JsonProcessingException
import com.fasterxml.jackson.databind.JsonMappingException
import io.jsonwebtoken.Jwts
import io.jsonwebtoken.SignatureAlgorithm
import io.jsonwebtoken.impl.compression.CompressionCodecs
import io.jsonwebtoken.impl.crypto.MacProvider
import org.junit.Test
@ -119,6 +120,17 @@ class DefaultJwtBuilderTest {
}
}
@Test
void testCompactWithoutPayloadOrClaims() {
def b = new DefaultJwtBuilder()
try {
b.compact()
fail()
} catch (IllegalStateException ise) {
assertEquals ise.message, "Either 'payload' or 'claims' must be specified."
}
}
@Test
void testCompactWithBothPayloadAndClaims() {
def b = new DefaultJwtBuilder()
@ -162,7 +174,7 @@ class DefaultJwtBuilderTest {
def b = new DefaultJwtBuilder() {
@Override
protected String toJson(Object o) throws JsonProcessingException {
protected byte[] toJson(Object o) throws JsonProcessingException {
throw new JsonMappingException('foo')
}
}
@ -176,6 +188,26 @@ class DefaultJwtBuilderTest {
}
@Test
void testCompactCompressionCodecJsonProcessingException() {
def b = new DefaultJwtBuilder() {
@Override
protected byte[] toJson(Object o) throws JsonProcessingException {
if (o instanceof DefaultJwsHeader) { return super.toJson(o) }
throw new JsonProcessingException('simulate json processing exception on claims')
}
}
def c = Jwts.claims().setSubject("Joe");
try {
b.setClaims(c).compressWith(CompressionCodecs.DEFLATE).compact()
fail()
} catch (IllegalArgumentException iae) {
assertEquals iae.message, 'Unable to serialize claims object to json.'
}
}
@Test
void testSignWithBytesWithoutHmac() {
def bytes = new byte[16];
@ -197,4 +229,54 @@ class DefaultJwtBuilderTest {
}
}
@Test
void testSetHeaderParamsWithNullMap() {
def b = new DefaultJwtBuilder()
b.setHeaderParams(null)
assertNull b.header
}
@Test
void testSetHeaderParamsWithEmptyMap() {
def b = new DefaultJwtBuilder()
b.setHeaderParams([:])
assertNull b.header
}
@Test
void testSetIssuerWithNull() {
def b = new DefaultJwtBuilder()
b.setIssuer(null)
assertNull b.claims
}
@Test
void testSetSubjectWithNull() {
def b = new DefaultJwtBuilder()
b.setSubject(null)
assertNull b.claims
}
@Test
void testSetAudienceWithNull() {
def b = new DefaultJwtBuilder()
b.setAudience(null)
assertNull b.claims
}
@Test
void testSetIdWithNull() {
def b = new DefaultJwtBuilder()
b.setId(null)
assertNull b.claims
}
@Test
void testClaimNullValue() {
def b = new DefaultJwtBuilder()
b.claim('foo', null)
assertNull b.claims
}
}

View File

@ -0,0 +1,18 @@
package io.jsonwebtoken.impl
import io.jsonwebtoken.Jwt
import io.jsonwebtoken.Jwts
import org.junit.Test
import static org.junit.Assert.assertEquals
class DefaultJwtTest {
@Test
void testToString() {
String compact = Jwts.builder().setHeaderParam('foo', 'bar').setAudience('jsmith').compact();
Jwt jwt = Jwts.parser().parseClaimsJwt(compact);
assertEquals 'header={foo=bar, alg=none},body={aud=jsmith}', jwt.toString()
}
}

View File

@ -20,6 +20,12 @@ import static org.junit.Assert.*
class JwtMapTest {
@Test
void testToDateFromNull() {
Date actual = JwtMap.toDate(null, 'foo')
assertNull actual
}
@Test
void testToDateFromDate() {

View File

@ -0,0 +1,55 @@
/*
* Copyright (C) 2015 jsonwebtoken.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.jsonwebtoken.impl.compression
import io.jsonwebtoken.CompressionCodec
import io.jsonwebtoken.CompressionException
import org.junit.Test
/**
* @since 0.6.0
*/
class AbstractCompressionCodecTest {
static class ExceptionThrowingCodec extends AbstractCompressionCodec {
@Override
protected byte[] doCompress(byte[] payload) throws IOException {
throw new IOException("Test Exception")
}
@Override
String getAlgorithmName() {
return "Test"
}
@Override
protected byte[] doDecompress(byte[] payload) throws IOException {
throw new IOException("Test Decompress Exception");
}
}
@Test(expected = CompressionException.class)
void testCompressWithException() {
CompressionCodec codecUT = new ExceptionThrowingCodec();
codecUT.compress(new byte[0]);
}
@Test(expected = CompressionException.class)
void testDecompressWithException() {
CompressionCodec codecUT = new ExceptionThrowingCodec();
codecUT.decompress(new byte[0]);
}
}

View File

@ -22,6 +22,7 @@ import javax.crypto.spec.SecretKeySpec
import java.security.InvalidKeyException
import java.security.KeyPair
import java.security.KeyPairGenerator
import java.security.MessageDigest
import java.security.PrivateKey
import java.security.PublicKey
@ -48,18 +49,50 @@ class RsaSignerTest {
}
@Test
void testConstructorWithoutRsaPrivateKey() {
void testConstructorWithoutPrivateKey() {
byte[] bytes = new byte[16]
rng.nextBytes(bytes)
SecretKeySpec key = new SecretKeySpec(bytes, 'HmacSHA256')
try {
//noinspection GroovyResultOfObjectAllocationIgnored
new RsaSigner(SignatureAlgorithm.RS256, key);
fail('RsaSigner should reject non RSAPrivateKey instances.')
} catch (IllegalArgumentException expected) {
assertEquals expected.message, "RSA signatures must be computed using an RSAPrivateKey. The specified key of type " +
key.getClass().getName() + " is not an RSAPrivateKey.";
assertEquals expected.message, "RSA signatures must be computed using an RSA PrivateKey. The specified key of type " +
key.getClass().getName() + " is not an RSA PrivateKey.";
}
}
@Test
void testConstructorWithoutRSAKey() {
//private key, but not an RSAKey instance:
PrivateKey key = new PrivateKey() {
@Override
String getAlgorithm() {
return null
}
@Override
String getFormat() {
return null
}
@Override
byte[] getEncoded() {
return new byte[0]
}
}
try {
//noinspection GroovyResultOfObjectAllocationIgnored
new RsaSigner(SignatureAlgorithm.RS256, key);
fail('RsaSigner should reject non RSAPrivateKey instances.')
} catch (IllegalArgumentException expected) {
assertEquals expected.message, "RSA signatures must be computed using an RSA PrivateKey. The specified key of type " +
key.getClass().getName() + " is not an RSA PrivateKey.";
}
}
@ -126,4 +159,24 @@ class RsaSignerTest {
assertSame se.cause, ex
}
}
@Test
void testSignSuccessful() {
KeyPairGenerator keyGenerator = KeyPairGenerator.getInstance("RSA");
keyGenerator.initialize(1024);
KeyPair kp = keyGenerator.genKeyPair();
PrivateKey privateKey = kp.getPrivate();
byte[] bytes = new byte[16]
rng.nextBytes(bytes)
RsaSigner signer = new RsaSigner(SignatureAlgorithm.RS256, privateKey);
byte[] out1 = signer.sign(bytes)
byte[] out2 = signer.sign(bytes)
assertTrue(MessageDigest.isEqual(out1, out2))
}
}