mirror of https://github.com/jwtk/jjwt.git
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
1d525e94c6
|
@ -1,4 +1,5 @@
|
||||||
*.class
|
*.class
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
# Mobile Tools for Java (J2ME)
|
# Mobile Tools for Java (J2ME)
|
||||||
.mtj.tmp/
|
.mtj.tmp/
|
||||||
|
|
|
@ -6,5 +6,9 @@ jdk:
|
||||||
- oraclejdk7
|
- oraclejdk7
|
||||||
- oraclejdk8
|
- 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."
|
install: echo "No need to run mvn install -DskipTests then mvn install. Running mvn install."
|
||||||
script: mvn install
|
script: mvn install
|
||||||
|
after_success:
|
||||||
|
- test -z "$BUILD_COVERAGE" || mvn clean cobertura:cobertura coveralls:report
|
||||||
|
|
91
README.md
91
README.md
|
@ -1,10 +1,11 @@
|
||||||
[![Build Status](https://travis-ci.org/jwtk/jjwt.svg?branch=master)](https://travis-ci.org/jwtk/jjwt)
|
[![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
|
# 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 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
|
## Installation
|
||||||
|
|
||||||
|
@ -16,7 +17,7 @@ Maven:
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.jsonwebtoken</groupId>
|
<groupId>io.jsonwebtoken</groupId>
|
||||||
<artifactId>jjwt</artifactId>
|
<artifactId>jjwt</artifactId>
|
||||||
<version>0.5.1</version>
|
<version>0.6.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -24,7 +25,7 @@ Gradle:
|
||||||
|
|
||||||
```groovy
|
```groovy
|
||||||
dependencies {
|
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
|
```java
|
||||||
try {
|
try {
|
||||||
|
|
||||||
Jwts.parser().setSigningKey(key).parse(compactJwt);
|
Jwts.parser().setSigningKey(key).parseClaimsJws(compactJwt);
|
||||||
|
|
||||||
//OK, we can trust this JWT
|
//OK, we can trust this JWT
|
||||||
|
|
||||||
|
@ -108,6 +109,88 @@ Maintained by [Stormpath](https://stormpath.com/)
|
||||||
|
|
||||||
## Release Notes
|
## 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
|
### 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.
|
- 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
24
pom.xml
|
@ -25,7 +25,7 @@
|
||||||
|
|
||||||
<groupId>io.jsonwebtoken</groupId>
|
<groupId>io.jsonwebtoken</groupId>
|
||||||
<artifactId>jjwt</artifactId>
|
<artifactId>jjwt</artifactId>
|
||||||
<version>0.5.2-SNAPSHOT</version>
|
<version>0.7.0-SNAPSHOT</version>
|
||||||
<name>JSON Web Token support for the JVM</name>
|
<name>JSON Web Token support for the JVM</name>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
@ -61,7 +61,6 @@
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
<buildNumber>${user.name}-${maven.build.timestamp}</buildNumber>
|
<buildNumber>${user.name}-${maven.build.timestamp}</buildNumber>
|
||||||
|
|
||||||
<slf4j.version>1.7.6</slf4j.version>
|
|
||||||
<jackson.version>2.4.2</jackson.version>
|
<jackson.version>2.4.2</jackson.version>
|
||||||
|
|
||||||
<!-- Optional Runtime Dependencies: -->
|
<!-- Optional Runtime Dependencies: -->
|
||||||
|
@ -78,12 +77,6 @@
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.slf4j</groupId>
|
|
||||||
<artifactId>slf4j-api</artifactId>
|
|
||||||
<version>${slf4j.version}</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.fasterxml.jackson.core</groupId>
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
<artifactId>jackson-databind</artifactId>
|
<artifactId>jackson-databind</artifactId>
|
||||||
|
@ -281,6 +274,8 @@
|
||||||
<artifactId>cobertura-maven-plugin</artifactId>
|
<artifactId>cobertura-maven-plugin</artifactId>
|
||||||
<version>2.7</version>
|
<version>2.7</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
|
<maxmem>256m</maxmem>
|
||||||
|
<aggregate>true</aggregate>
|
||||||
<instrumentation>
|
<instrumentation>
|
||||||
<excludes>
|
<excludes>
|
||||||
<exclude>io/jsonwebtoken/lang/*.class</exclude>
|
<exclude>io/jsonwebtoken/lang/*.class</exclude>
|
||||||
|
@ -303,11 +298,6 @@
|
||||||
<lineRate>96</lineRate>
|
<lineRate>96</lineRate>
|
||||||
<branchRate>100</branchRate>
|
<branchRate>100</branchRate>
|
||||||
</regex>
|
</regex>
|
||||||
<regex>
|
|
||||||
<pattern>io.jsonwebtoken.impl.Base64UrlCodec</pattern>
|
|
||||||
<lineRate>100</lineRate>
|
|
||||||
<branchRate>95</branchRate>
|
|
||||||
</regex>
|
|
||||||
<regex>
|
<regex>
|
||||||
<pattern>io.jsonwebtoken.impl.DefaultJwtBuilder</pattern>
|
<pattern>io.jsonwebtoken.impl.DefaultJwtBuilder</pattern>
|
||||||
<lineRate>91</lineRate>
|
<lineRate>91</lineRate>
|
||||||
|
@ -331,6 +321,7 @@
|
||||||
</regexes>
|
</regexes>
|
||||||
</check>
|
</check>
|
||||||
<formats>
|
<formats>
|
||||||
|
<format>xml</format>
|
||||||
<format>html</format>
|
<format>html</format>
|
||||||
</formats>
|
</formats>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
@ -376,6 +367,11 @@
|
||||||
</execution>
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.eluder.coveralls</groupId>
|
||||||
|
<artifactId>coveralls-maven-plugin</artifactId>
|
||||||
|
<version>4.0.0</version>
|
||||||
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
|
|
||||||
|
@ -446,4 +442,4 @@
|
||||||
</profile>
|
</profile>
|
||||||
</profiles>
|
</profiles>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
|
|
@ -22,6 +22,9 @@ package io.jsonwebtoken;
|
||||||
*/
|
*/
|
||||||
public abstract class ClaimJwtException extends JwtException {
|
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 Header header;
|
||||||
|
|
||||||
private final Claims claims;
|
private final Claims claims;
|
||||||
|
|
|
@ -170,4 +170,5 @@ public interface Claims extends Map<String, Object>, ClaimsMutator<Claims> {
|
||||||
@Override //only for better/targeted JavaDoc
|
@Override //only for better/targeted JavaDoc
|
||||||
Claims setId(String jti);
|
Claims setId(String jti);
|
||||||
|
|
||||||
|
<T> T get(String claimName, Class<T> requiredType);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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> */
|
/** JWT {@code Content Type} header parameter name: <code>"cty"</code> */
|
||||||
public static final String CONTENT_TYPE = "cty";
|
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">
|
* 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.
|
* <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);
|
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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -349,6 +349,26 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
|
||||||
*/
|
*/
|
||||||
JwtBuilder signWith(SignatureAlgorithm alg, Key key);
|
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
|
* 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>
|
* <a href="https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-25#section-7">JWT Compact Serialization</a>
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
package io.jsonwebtoken;
|
package io.jsonwebtoken;
|
||||||
|
|
||||||
import java.security.Key;
|
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.
|
* 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 = '.';
|
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
|
* 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.
|
* 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
|
* <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>
|
* (as the {@code alg} header parameter).</p>
|
||||||
*
|
* <p>
|
||||||
* <p>This method overwrites any previously set key.</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
|
* @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
|
* 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.
|
* 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
|
* <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>
|
* (as the {@code alg} header parameter).</p>
|
||||||
*
|
* <p>
|
||||||
* <p>This method overwrites any previously set key.</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
|
* <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>
|
* 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
|
* 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.
|
* 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
|
* <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>
|
* (as the {@code alg} header parameter).</p>
|
||||||
*
|
* <p>
|
||||||
* <p>This method overwrites any previously set key.</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
|
* <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>
|
* 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
|
* 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.
|
* 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
|
* <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
|
* 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
|
* 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>
|
* returned key. For example:</p>
|
||||||
*
|
* <p>
|
||||||
* <pre>
|
* <pre>
|
||||||
* Jws<Claims> jws = Jwts.parser().setSigningKeyResolver(new SigningKeyResolverAdapter() {
|
* Jws<Claims> jws = Jwts.parser().setSigningKeyResolver(new SigningKeyResolverAdapter() {
|
||||||
* @Override
|
* @Override
|
||||||
|
@ -95,9 +193,9 @@ public interface JwtParser {
|
||||||
* }})
|
* }})
|
||||||
* .parseClaimsJws(compact);
|
* .parseClaimsJws(compact);
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
* <p>
|
||||||
* <p>A {@code SigningKeyResolver} is invoked once during parsing before the signature is verified.</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
|
* <p>This method should only be used if a signing key is not provided by the other {@code setSigningKey*} builder
|
||||||
* methods.</p>
|
* methods.</p>
|
||||||
*
|
*
|
||||||
|
@ -107,10 +205,32 @@ public interface JwtParser {
|
||||||
*/
|
*/
|
||||||
JwtParser setSigningKeyResolver(SigningKeyResolver signingKeyResolver);
|
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}
|
* Returns {@code true} if the specified JWT compact string represents a signed JWT (aka a 'JWS'), {@code false}
|
||||||
* otherwise.
|
* otherwise.
|
||||||
*
|
* <p>
|
||||||
* <p>Note that if you are reasonably sure that the token is signed, it is more efficient to attempt to
|
* <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>
|
* 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
|
* Parses the specified compact serialized JWT string based on the builder's current configuration state and
|
||||||
* returns the resulting JWT or JWS instance.
|
* 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
|
* <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
|
* 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
|
* {@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
|
* 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.
|
* 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
|
* <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
|
* {@link io.jsonwebtoken.JwtHandlerAdapter JwtHandlerAdapter} and override only the methods you know are relevant
|
||||||
* for your use case(s), for example:</p>
|
* for your use case(s), for example:</p>
|
||||||
*
|
* <p>
|
||||||
* <pre>
|
* <pre>
|
||||||
* String compactJwt = request.getParameter("jwt"); //we are confident this is a signed JWS
|
* String compactJwt = request.getParameter("jwt"); //we are confident this is a signed JWS
|
||||||
*
|
*
|
||||||
|
@ -165,10 +285,10 @@ public interface JwtParser {
|
||||||
* }
|
* }
|
||||||
* });
|
* });
|
||||||
* </pre>
|
* </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
|
* <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>
|
* following convenience methods instead of this one:</p>
|
||||||
*
|
* <p>
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>{@link #parsePlaintextJwt(String)}</li>
|
* <li>{@link #parsePlaintextJwt(String)}</li>
|
||||||
* <li>{@link #parseClaimsJwt(String)}</li>
|
* <li>{@link #parseClaimsJwt(String)}</li>
|
||||||
|
@ -194,17 +314,17 @@ public interface JwtParser {
|
||||||
* @since 0.2
|
* @since 0.2
|
||||||
*/
|
*/
|
||||||
<T> T parse(String jwt, JwtHandler<T> handler)
|
<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
|
* Parses the specified compact serialized JWT string based on the builder's current configuration state and
|
||||||
* returns
|
* returns
|
||||||
* the resulting unsigned plaintext JWT instance.
|
* 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
|
* <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
|
* unsigned plaintext JWT. An unsigned plaintext JWT has a String (non-JSON) body payload and it is not
|
||||||
* cryptographically signed.</p>
|
* cryptographically signed.</p>
|
||||||
*
|
* <p>
|
||||||
* <p><b>If the compact string presented does not reflect an unsigned plaintext JWT with non-JSON string body,
|
* <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>
|
* an {@link UnsupportedJwtException} will be thrown.</b></p>
|
||||||
*
|
*
|
||||||
|
@ -224,17 +344,17 @@ public interface JwtParser {
|
||||||
* @since 0.2
|
* @since 0.2
|
||||||
*/
|
*/
|
||||||
Jwt<Header, String> parsePlaintextJwt(String plaintextJwt)
|
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
|
* Parses the specified compact serialized JWT string based on the builder's current configuration state and
|
||||||
* returns
|
* returns
|
||||||
* the resulting unsigned plaintext JWT instance.
|
* 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
|
* <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
|
* unsigned Claims JWT. An unsigned Claims JWT has a {@link Claims} body and it is not cryptographically
|
||||||
* signed.</p>
|
* signed.</p>
|
||||||
*
|
* <p>
|
||||||
* <p><b>If the compact string presented does not reflect an unsigned Claims JWT, an
|
* <p><b>If the compact string presented does not reflect an unsigned Claims JWT, an
|
||||||
* {@link UnsupportedJwtException} will be thrown.</b></p>
|
* {@link UnsupportedJwtException} will be thrown.</b></p>
|
||||||
*
|
*
|
||||||
|
@ -255,17 +375,17 @@ public interface JwtParser {
|
||||||
* @since 0.2
|
* @since 0.2
|
||||||
*/
|
*/
|
||||||
Jwt<Header, Claims> parseClaimsJwt(String claimsJwt)
|
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
|
* Parses the specified compact serialized JWS string based on the builder's current configuration state and
|
||||||
* returns
|
* returns
|
||||||
* the resulting plaintext JWS instance.
|
* 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
|
* <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
|
* plaintext JWS. A plaintext JWS is a JWT with a String (non-JSON) body (payload) that has been
|
||||||
* cryptographically signed.</p>
|
* cryptographically signed.</p>
|
||||||
*
|
* <p>
|
||||||
* <p><b>If the compact string presented does not reflect a plaintext JWS, an {@link UnsupportedJwtException}
|
* <p><b>If the compact string presented does not reflect a plaintext JWS, an {@link UnsupportedJwtException}
|
||||||
* will be thrown.</b></p>
|
* will be thrown.</b></p>
|
||||||
*
|
*
|
||||||
|
@ -283,16 +403,16 @@ public interface JwtParser {
|
||||||
* @since 0.2
|
* @since 0.2
|
||||||
*/
|
*/
|
||||||
Jws<String> parsePlaintextJws(String plaintextJws)
|
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
|
* Parses the specified compact serialized JWS string based on the builder's current configuration state and
|
||||||
* returns
|
* returns
|
||||||
* the resulting Claims JWS instance.
|
* 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
|
* <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>
|
* 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
|
* <p><b>If the compact string presented does not reflect a Claims JWS, an {@link UnsupportedJwtException} will be
|
||||||
* thrown.</b></p>
|
* thrown.</b></p>
|
||||||
*
|
*
|
||||||
|
@ -312,5 +432,5 @@ public interface JwtParser {
|
||||||
* @since 0.2
|
* @since 0.2
|
||||||
*/
|
*/
|
||||||
Jws<Claims> parseClaimsJws(String claimsJws)
|
Jws<Claims> parseClaimsJws(String claimsJws)
|
||||||
throws ExpiredJwtException, UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException;
|
throws ExpiredJwtException, UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,6 +16,7 @@
|
||||||
package io.jsonwebtoken.impl;
|
package io.jsonwebtoken.impl;
|
||||||
|
|
||||||
import io.jsonwebtoken.Claims;
|
import io.jsonwebtoken.Claims;
|
||||||
|
import io.jsonwebtoken.RequiredTypeException;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -65,7 +66,7 @@ public class DefaultClaims extends JwtMap implements Claims {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Date getExpiration() {
|
public Date getExpiration() {
|
||||||
return getDate(Claims.EXPIRATION);
|
return get(Claims.EXPIRATION, Date.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -76,7 +77,7 @@ public class DefaultClaims extends JwtMap implements Claims {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Date getNotBefore() {
|
public Date getNotBefore() {
|
||||||
return getDate(Claims.NOT_BEFORE);
|
return get(Claims.NOT_BEFORE, Date.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -87,7 +88,7 @@ public class DefaultClaims extends JwtMap implements Claims {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Date getIssuedAt() {
|
public Date getIssuedAt() {
|
||||||
return getDate(Claims.ISSUED_AT);
|
return get(Claims.ISSUED_AT, Date.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -106,4 +107,27 @@ public class DefaultClaims extends JwtMap implements Claims {
|
||||||
setValue(Claims.ID, jti);
|
setValue(Claims.ID, jti);
|
||||||
return this;
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,4 +51,16 @@ public class DefaultHeader<T extends Header<T>> extends JwtMap implements Header
|
||||||
setValue(CONTENT_TYPE, cty);
|
setValue(CONTENT_TYPE, cty);
|
||||||
return (T)this;
|
return (T)this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCompressionAlgorithm() {
|
||||||
|
return getString(COMPRESSION_ALGORITHM);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T setCompressionAlgorithm(String compressionAlgorithm) {
|
||||||
|
setValue(COMPRESSION_ALGORITHM, compressionAlgorithm);
|
||||||
|
return (T) this;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ package io.jsonwebtoken.impl;
|
||||||
import io.jsonwebtoken.Jws;
|
import io.jsonwebtoken.Jws;
|
||||||
import io.jsonwebtoken.JwsHeader;
|
import io.jsonwebtoken.JwsHeader;
|
||||||
|
|
||||||
public class DefaultJws<B> implements Jws {
|
public class DefaultJws<B> implements Jws<B> {
|
||||||
|
|
||||||
private final JwsHeader header;
|
private final JwsHeader header;
|
||||||
private final B body;
|
private final B body;
|
||||||
|
@ -44,4 +44,9 @@ public class DefaultJws<B> implements Jws {
|
||||||
public String getSignature() {
|
public String getSignature() {
|
||||||
return this.signature;
|
return this.signature;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "header=" + header + ",body=" + body + ",signature=" + signature;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,4 +37,9 @@ public class DefaultJwt<B> implements Jwt<Header,B> {
|
||||||
public B getBody() {
|
public B getBody() {
|
||||||
return body;
|
return body;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "header=" + header + ",body=" + body;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ package io.jsonwebtoken.impl;
|
||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import io.jsonwebtoken.Claims;
|
import io.jsonwebtoken.Claims;
|
||||||
|
import io.jsonwebtoken.CompressionCodec;
|
||||||
import io.jsonwebtoken.Header;
|
import io.jsonwebtoken.Header;
|
||||||
import io.jsonwebtoken.JwsHeader;
|
import io.jsonwebtoken.JwsHeader;
|
||||||
import io.jsonwebtoken.JwtBuilder;
|
import io.jsonwebtoken.JwtBuilder;
|
||||||
|
@ -48,6 +49,8 @@ public class DefaultJwtBuilder implements JwtBuilder {
|
||||||
private Key key;
|
private Key key;
|
||||||
private byte[] keyBytes;
|
private byte[] keyBytes;
|
||||||
|
|
||||||
|
private CompressionCodec compressionCodec;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JwtBuilder setHeader(Header header) {
|
public JwtBuilder setHeader(Header header) {
|
||||||
this.header = header;
|
this.header = header;
|
||||||
|
@ -113,6 +116,13 @@ public class DefaultJwtBuilder implements JwtBuilder {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JwtBuilder compressWith(CompressionCodec compressionCodec) {
|
||||||
|
Assert.notNull(compressionCodec, "compressionCodec cannot be null");
|
||||||
|
this.compressionCodec = compressionCodec;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JwtBuilder setPayload(String payload) {
|
public JwtBuilder setPayload(String payload) {
|
||||||
this.payload = payload;
|
this.payload = payload;
|
||||||
|
@ -279,11 +289,30 @@ public class DefaultJwtBuilder implements JwtBuilder {
|
||||||
jwsHeader.setAlgorithm(SignatureAlgorithm.NONE.getValue());
|
jwsHeader.setAlgorithm(SignatureAlgorithm.NONE.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (compressionCodec != null) {
|
||||||
|
jwsHeader.setCompressionAlgorithm(compressionCodec.getAlgorithmName());
|
||||||
|
}
|
||||||
|
|
||||||
String base64UrlEncodedHeader = base64UrlEncode(jwsHeader, "Unable to serialize header to json.");
|
String base64UrlEncodedHeader = base64UrlEncode(jwsHeader, "Unable to serialize header to json.");
|
||||||
|
|
||||||
String base64UrlEncodedBody = this.payload != null ?
|
String base64UrlEncodedBody;
|
||||||
TextCodec.BASE64URL.encode(this.payload) :
|
|
||||||
base64UrlEncode(claims, "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.");
|
||||||
|
}
|
||||||
|
|
||||||
String jwt = base64UrlEncodedHeader + JwtParser.SEPARATOR_CHAR + base64UrlEncodedBody;
|
String jwt = base64UrlEncodedHeader + JwtParser.SEPARATOR_CHAR + base64UrlEncodedBody;
|
||||||
|
|
||||||
|
@ -311,17 +340,18 @@ public class DefaultJwtBuilder implements JwtBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String base64UrlEncode(Object o, String errMsg) {
|
protected String base64UrlEncode(Object o, String errMsg) {
|
||||||
String s;
|
byte[] bytes;
|
||||||
try {
|
try {
|
||||||
s = toJson(o);
|
bytes = toJson(o);
|
||||||
} catch (JsonProcessingException e) {
|
} catch (JsonProcessingException e) {
|
||||||
throw new IllegalStateException(errMsg, 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,21 +16,28 @@
|
||||||
package io.jsonwebtoken.impl;
|
package io.jsonwebtoken.impl;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import io.jsonwebtoken.ClaimJwtException;
|
||||||
import io.jsonwebtoken.Claims;
|
import io.jsonwebtoken.Claims;
|
||||||
|
import io.jsonwebtoken.CompressionCodec;
|
||||||
|
import io.jsonwebtoken.CompressionCodecResolver;
|
||||||
import io.jsonwebtoken.ExpiredJwtException;
|
import io.jsonwebtoken.ExpiredJwtException;
|
||||||
import io.jsonwebtoken.Header;
|
import io.jsonwebtoken.Header;
|
||||||
|
import io.jsonwebtoken.IncorrectClaimException;
|
||||||
|
import io.jsonwebtoken.InvalidClaimException;
|
||||||
import io.jsonwebtoken.Jws;
|
import io.jsonwebtoken.Jws;
|
||||||
import io.jsonwebtoken.JwsHeader;
|
import io.jsonwebtoken.JwsHeader;
|
||||||
import io.jsonwebtoken.SigningKeyResolver;
|
|
||||||
import io.jsonwebtoken.Jwt;
|
import io.jsonwebtoken.Jwt;
|
||||||
import io.jsonwebtoken.JwtHandler;
|
import io.jsonwebtoken.JwtHandler;
|
||||||
import io.jsonwebtoken.JwtHandlerAdapter;
|
import io.jsonwebtoken.JwtHandlerAdapter;
|
||||||
import io.jsonwebtoken.JwtParser;
|
import io.jsonwebtoken.JwtParser;
|
||||||
import io.jsonwebtoken.MalformedJwtException;
|
import io.jsonwebtoken.MalformedJwtException;
|
||||||
|
import io.jsonwebtoken.MissingClaimException;
|
||||||
import io.jsonwebtoken.PrematureJwtException;
|
import io.jsonwebtoken.PrematureJwtException;
|
||||||
import io.jsonwebtoken.SignatureAlgorithm;
|
import io.jsonwebtoken.SignatureAlgorithm;
|
||||||
import io.jsonwebtoken.SignatureException;
|
import io.jsonwebtoken.SignatureException;
|
||||||
|
import io.jsonwebtoken.SigningKeyResolver;
|
||||||
import io.jsonwebtoken.UnsupportedJwtException;
|
import io.jsonwebtoken.UnsupportedJwtException;
|
||||||
|
import io.jsonwebtoken.impl.compression.DefaultCompressionCodecResolver;
|
||||||
import io.jsonwebtoken.impl.crypto.DefaultJwtSignatureValidator;
|
import io.jsonwebtoken.impl.crypto.DefaultJwtSignatureValidator;
|
||||||
import io.jsonwebtoken.impl.crypto.JwtSignatureValidator;
|
import io.jsonwebtoken.impl.crypto.JwtSignatureValidator;
|
||||||
import io.jsonwebtoken.lang.Assert;
|
import io.jsonwebtoken.lang.Assert;
|
||||||
|
@ -58,6 +65,68 @@ public class DefaultJwtParser implements JwtParser {
|
||||||
|
|
||||||
private SigningKeyResolver signingKeyResolver;
|
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
|
@Override
|
||||||
public JwtParser setSigningKey(byte[] key) {
|
public JwtParser setSigningKey(byte[] key) {
|
||||||
Assert.notEmpty(key, "signing key cannot be null or empty.");
|
Assert.notEmpty(key, "signing key cannot be null or empty.");
|
||||||
|
@ -86,6 +155,13 @@ public class DefaultJwtParser implements JwtParser {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JwtParser setCompressionCodecResolver(CompressionCodecResolver compressionCodecResolver) {
|
||||||
|
Assert.notNull(compressionCodecResolver, "compressionCodecResolver cannot be null.");
|
||||||
|
this.compressionCodecResolver = compressionCodecResolver;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isSigned(String jwt) {
|
public boolean isSigned(String jwt) {
|
||||||
|
|
||||||
|
@ -157,6 +233,8 @@ public class DefaultJwtParser implements JwtParser {
|
||||||
// =============== Header =================
|
// =============== Header =================
|
||||||
Header header = null;
|
Header header = null;
|
||||||
|
|
||||||
|
CompressionCodec compressionCodec = null;
|
||||||
|
|
||||||
if (base64UrlEncodedHeader != null) {
|
if (base64UrlEncodedHeader != null) {
|
||||||
String origValue = TextCodec.BASE64URL.decodeToString(base64UrlEncodedHeader);
|
String origValue = TextCodec.BASE64URL.decodeToString(base64UrlEncodedHeader);
|
||||||
Map<String, Object> m = readValue(origValue);
|
Map<String, Object> m = readValue(origValue);
|
||||||
|
@ -166,10 +244,18 @@ public class DefaultJwtParser implements JwtParser {
|
||||||
} else {
|
} else {
|
||||||
header = new DefaultHeader(m);
|
header = new DefaultHeader(m);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
compressionCodec = compressionCodecResolver.resolveCompressionCodec(header);
|
||||||
}
|
}
|
||||||
|
|
||||||
// =============== Body =================
|
// =============== 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;
|
Claims claims = null;
|
||||||
|
|
||||||
|
@ -298,6 +384,8 @@ public class DefaultJwtParser implements JwtParser {
|
||||||
throw new PrematureJwtException(header, claims, msg);
|
throw new PrematureJwtException(header, claims, msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
validateExpectedClaims(header, claims);
|
||||||
}
|
}
|
||||||
|
|
||||||
Object body = claims != null ? claims : payload;
|
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
|
* @since 0.5 mostly to allow testing overrides
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -150,4 +150,9 @@ public class JwtMap implements Map<String,Object> {
|
||||||
public Set<Entry<String, Object>> entrySet() {
|
public Set<Entry<String, Object>> entrySet() {
|
||||||
return map.entrySet();
|
return map.entrySet();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return map.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -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();
|
||||||
|
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,6 +18,7 @@ package io.jsonwebtoken.impl.crypto;
|
||||||
import io.jsonwebtoken.SignatureAlgorithm;
|
import io.jsonwebtoken.SignatureAlgorithm;
|
||||||
|
|
||||||
import java.security.Key;
|
import java.security.Key;
|
||||||
|
import java.security.MessageDigest;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
public class MacValidator implements SignatureValidator {
|
public class MacValidator implements SignatureValidator {
|
||||||
|
@ -31,6 +32,6 @@ public class MacValidator implements SignatureValidator {
|
||||||
@Override
|
@Override
|
||||||
public boolean isValid(byte[] data, byte[] signature) {
|
public boolean isValid(byte[] data, byte[] signature) {
|
||||||
byte[] computed = this.signer.sign(data);
|
byte[] computed = this.signer.sign(data);
|
||||||
return Arrays.equals(computed, signature);
|
return MessageDigest.isEqual(computed, signature);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,15 +22,17 @@ import java.security.InvalidKeyException;
|
||||||
import java.security.Key;
|
import java.security.Key;
|
||||||
import java.security.PrivateKey;
|
import java.security.PrivateKey;
|
||||||
import java.security.Signature;
|
import java.security.Signature;
|
||||||
import java.security.interfaces.RSAPrivateKey;
|
import java.security.interfaces.RSAKey;
|
||||||
|
|
||||||
public class RsaSigner extends RsaProvider implements Signer {
|
public class RsaSigner extends RsaProvider implements Signer {
|
||||||
|
|
||||||
public RsaSigner(SignatureAlgorithm alg, Key key) {
|
public RsaSigner(SignatureAlgorithm alg, Key key) {
|
||||||
super(alg, key);
|
super(alg, key);
|
||||||
if (!(key instanceof RSAPrivateKey)) {
|
// https://github.com/jwtk/jjwt/issues/68
|
||||||
String msg = "RSA signatures must be computed using an RSAPrivateKey. The specified key of type " +
|
// Instead of checking for an instance of RSAPrivateKey, check for PrivateKey and RSAKey:
|
||||||
key.getClass().getName() + " is not an RSAPrivateKey.";
|
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);
|
throw new IllegalArgumentException(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,9 +15,6 @@
|
||||||
*/
|
*/
|
||||||
package io.jsonwebtoken.lang;
|
package io.jsonwebtoken.lang;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
|
|
||||||
|
@ -26,11 +23,6 @@ import java.lang.reflect.Constructor;
|
||||||
*/
|
*/
|
||||||
public class Classes {
|
public class Classes {
|
||||||
|
|
||||||
/**
|
|
||||||
* Private internal log instance.
|
|
||||||
*/
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(Classes.class);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @since 0.1
|
* @since 0.1
|
||||||
*/
|
*/
|
||||||
|
@ -78,18 +70,10 @@ public class Classes {
|
||||||
Class clazz = THREAD_CL_ACCESSOR.loadClass(fqcn);
|
Class clazz = THREAD_CL_ACCESSOR.loadClass(fqcn);
|
||||||
|
|
||||||
if (clazz == null) {
|
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);
|
clazz = CLASS_CL_ACCESSOR.loadClass(fqcn);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (clazz == null) {
|
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);
|
clazz = SYSTEM_CL_ACCESSOR.loadClass(fqcn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -212,9 +196,7 @@ public class Classes {
|
||||||
try {
|
try {
|
||||||
clazz = cl.loadClass(fqcn);
|
clazz = cl.loadClass(fqcn);
|
||||||
} catch (ClassNotFoundException e) {
|
} catch (ClassNotFoundException e) {
|
||||||
if (log.isTraceEnabled()) {
|
//Class couldn't be found by loader
|
||||||
log.trace("Unable to load clazz named [" + fqcn + "] from class loader [" + cl + "]");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return clazz;
|
return clazz;
|
||||||
|
@ -233,9 +215,7 @@ public class Classes {
|
||||||
try {
|
try {
|
||||||
return doGetClassLoader();
|
return doGetClassLoader();
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
if (log.isDebugEnabled()) {
|
//Unable to get ClassLoader
|
||||||
log.debug("Unable to acquire ClassLoader.", t);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
*/
|
*/
|
||||||
package io.jsonwebtoken.lang;
|
package io.jsonwebtoken.lang;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.IOException;
|
||||||
import java.lang.reflect.Array;
|
import java.lang.reflect.Array;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
@ -905,4 +907,19 @@ public abstract class Objects {
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void nullSafeClose(Closeable... closeables) {
|
||||||
|
if (closeables == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Closeable closeable : closeables) {
|
||||||
|
if (closeable != null) {
|
||||||
|
try {
|
||||||
|
closeable.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
//Ignore the exception during close.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
*/
|
*/
|
||||||
package io.jsonwebtoken.lang;
|
package io.jsonwebtoken.lang;
|
||||||
|
|
||||||
|
import java.nio.charset.Charset;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
@ -40,6 +41,8 @@ public abstract class Strings {
|
||||||
|
|
||||||
private static final char EXTENSION_SEPARATOR = '.';
|
private static final char EXTENSION_SEPARATOR = '.';
|
||||||
|
|
||||||
|
public static final Charset UTF_8 = Charset.forName("UTF-8");
|
||||||
|
|
||||||
//---------------------------------------------------------------------
|
//---------------------------------------------------------------------
|
||||||
// General convenience methods for working with Strings
|
// General convenience methods for working with Strings
|
||||||
//---------------------------------------------------------------------
|
//---------------------------------------------------------------------
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2015 jsonwebtoken.io
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package io.jsonwebtoken
|
||||||
|
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals
|
||||||
|
|
||||||
|
class CompressionExceptionTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDefaultConstructor() {
|
||||||
|
def exception = new CompressionException("my message")
|
||||||
|
|
||||||
|
assertEquals "my message", exception.getMessage()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testConstructorWithCause() {
|
||||||
|
|
||||||
|
def ioException = new IOException("root error")
|
||||||
|
|
||||||
|
def exception = new CompressionException("wrapping", ioException)
|
||||||
|
|
||||||
|
assertEquals "wrapping", exception.getMessage()
|
||||||
|
assertEquals ioException, exception.getCause()
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
@ -19,9 +19,13 @@ import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
import io.jsonwebtoken.impl.DefaultHeader
|
import io.jsonwebtoken.impl.DefaultHeader
|
||||||
import io.jsonwebtoken.impl.DefaultJwsHeader
|
import io.jsonwebtoken.impl.DefaultJwsHeader
|
||||||
import io.jsonwebtoken.impl.TextCodec
|
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.EllipticCurveProvider
|
||||||
import io.jsonwebtoken.impl.crypto.MacProvider
|
import io.jsonwebtoken.impl.crypto.MacProvider
|
||||||
import io.jsonwebtoken.impl.crypto.RsaProvider
|
import io.jsonwebtoken.impl.crypto.RsaProvider
|
||||||
|
import io.jsonwebtoken.lang.Strings
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
import javax.crypto.Mac
|
import javax.crypto.Mac
|
||||||
|
@ -108,6 +112,7 @@ class JwtsTest {
|
||||||
|
|
||||||
def token = Jwts.parser().parse(jwt);
|
def token = Jwts.parser().parse(jwt);
|
||||||
|
|
||||||
|
//noinspection GrEqualsBetweenInconvertibleTypes
|
||||||
assert token.body == claims
|
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
|
@Test
|
||||||
void testConvenienceIssuer() {
|
void testConvenienceIssuer() {
|
||||||
String compact = Jwts.builder().setIssuer("Me").compact();
|
String compact = Jwts.builder().setIssuer("Me").compact();
|
||||||
|
@ -320,6 +335,140 @@ class JwtsTest {
|
||||||
assertNull claims.getId()
|
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
|
@Test
|
||||||
void testHS256() {
|
void testHS256() {
|
||||||
testHmac(SignatureAlgorithm.HS256);
|
testHmac(SignatureAlgorithm.HS256);
|
||||||
|
@ -571,6 +720,7 @@ class JwtsTest {
|
||||||
def token = Jwts.parser().setSigningKey(key).parse(jwt);
|
def token = Jwts.parser().setSigningKey(key).parse(jwt);
|
||||||
|
|
||||||
assert [alg: alg.name()] == token.header
|
assert [alg: alg.name()] == token.header
|
||||||
|
//noinspection GrEqualsBetweenInconvertibleTypes
|
||||||
assert token.body == claims
|
assert token.body == claims
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -585,6 +735,7 @@ class JwtsTest {
|
||||||
def token = Jwts.parser().setSigningKey(key).parse(jwt)
|
def token = Jwts.parser().setSigningKey(key).parse(jwt)
|
||||||
|
|
||||||
assert token.header == [alg: alg.name()]
|
assert token.header == [alg: alg.name()]
|
||||||
|
//noinspection GrEqualsBetweenInconvertibleTypes
|
||||||
assert token.body == claims
|
assert token.body == claims
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -606,6 +757,7 @@ class JwtsTest {
|
||||||
def token = Jwts.parser().setSigningKey(key).parse(jwt);
|
def token = Jwts.parser().setSigningKey(key).parse(jwt);
|
||||||
|
|
||||||
assert token.header == [alg: alg.name()]
|
assert token.header == [alg: alg.name()]
|
||||||
|
//noinspection GrEqualsBetweenInconvertibleTypes
|
||||||
assert token.body == claims
|
assert token.body == claims
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -17,6 +17,8 @@ package io.jsonwebtoken.impl
|
||||||
|
|
||||||
import io.jsonwebtoken.JwsHeader
|
import io.jsonwebtoken.JwsHeader
|
||||||
import io.jsonwebtoken.Jwts
|
import io.jsonwebtoken.Jwts
|
||||||
|
import io.jsonwebtoken.SignatureAlgorithm
|
||||||
|
import io.jsonwebtoken.impl.crypto.MacProvider
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import static org.junit.Assert.*
|
import static org.junit.Assert.*
|
||||||
|
|
||||||
|
@ -32,4 +34,15 @@ class DefaultJwsTest {
|
||||||
assertEquals jws.getBody(), 'foo'
|
assertEquals jws.getBody(), 'foo'
|
||||||
assertEquals jws.getSignature(), 'sig'
|
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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ import com.fasterxml.jackson.core.JsonProcessingException
|
||||||
import com.fasterxml.jackson.databind.JsonMappingException
|
import com.fasterxml.jackson.databind.JsonMappingException
|
||||||
import io.jsonwebtoken.Jwts
|
import io.jsonwebtoken.Jwts
|
||||||
import io.jsonwebtoken.SignatureAlgorithm
|
import io.jsonwebtoken.SignatureAlgorithm
|
||||||
|
import io.jsonwebtoken.impl.compression.CompressionCodecs
|
||||||
import io.jsonwebtoken.impl.crypto.MacProvider
|
import io.jsonwebtoken.impl.crypto.MacProvider
|
||||||
import org.junit.Test
|
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
|
@Test
|
||||||
void testCompactWithBothPayloadAndClaims() {
|
void testCompactWithBothPayloadAndClaims() {
|
||||||
def b = new DefaultJwtBuilder()
|
def b = new DefaultJwtBuilder()
|
||||||
|
@ -162,7 +174,7 @@ class DefaultJwtBuilderTest {
|
||||||
|
|
||||||
def b = new DefaultJwtBuilder() {
|
def b = new DefaultJwtBuilder() {
|
||||||
@Override
|
@Override
|
||||||
protected String toJson(Object o) throws JsonProcessingException {
|
protected byte[] toJson(Object o) throws JsonProcessingException {
|
||||||
throw new JsonMappingException('foo')
|
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
|
@Test
|
||||||
void testSignWithBytesWithoutHmac() {
|
void testSignWithBytesWithoutHmac() {
|
||||||
def bytes = new byte[16];
|
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
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -20,6 +20,12 @@ import static org.junit.Assert.*
|
||||||
|
|
||||||
class JwtMapTest {
|
class JwtMapTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testToDateFromNull() {
|
||||||
|
Date actual = JwtMap.toDate(null, 'foo')
|
||||||
|
assertNull actual
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testToDateFromDate() {
|
void testToDateFromDate() {
|
||||||
|
|
||||||
|
|
|
@ -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]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,6 +22,7 @@ import javax.crypto.spec.SecretKeySpec
|
||||||
import java.security.InvalidKeyException
|
import java.security.InvalidKeyException
|
||||||
import java.security.KeyPair
|
import java.security.KeyPair
|
||||||
import java.security.KeyPairGenerator
|
import java.security.KeyPairGenerator
|
||||||
|
import java.security.MessageDigest
|
||||||
import java.security.PrivateKey
|
import java.security.PrivateKey
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
|
|
||||||
|
@ -48,18 +49,50 @@ class RsaSignerTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testConstructorWithoutRsaPrivateKey() {
|
void testConstructorWithoutPrivateKey() {
|
||||||
|
|
||||||
byte[] bytes = new byte[16]
|
byte[] bytes = new byte[16]
|
||||||
rng.nextBytes(bytes)
|
rng.nextBytes(bytes)
|
||||||
SecretKeySpec key = new SecretKeySpec(bytes, 'HmacSHA256')
|
SecretKeySpec key = new SecretKeySpec(bytes, 'HmacSHA256')
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
//noinspection GroovyResultOfObjectAllocationIgnored
|
||||||
new RsaSigner(SignatureAlgorithm.RS256, key);
|
new RsaSigner(SignatureAlgorithm.RS256, key);
|
||||||
fail('RsaSigner should reject non RSAPrivateKey instances.')
|
fail('RsaSigner should reject non RSAPrivateKey instances.')
|
||||||
} catch (IllegalArgumentException expected) {
|
} catch (IllegalArgumentException expected) {
|
||||||
assertEquals expected.message, "RSA signatures must be computed using an RSAPrivateKey. The specified key of type " +
|
assertEquals expected.message, "RSA signatures must be computed using an RSA PrivateKey. The specified key of type " +
|
||||||
key.getClass().getName() + " is not an RSAPrivateKey.";
|
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
|
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))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue