mirror of https://github.com/jwtk/jjwt.git
Merge pull request #1 from jwtk/master
Merge Updates from Upstream Master
This commit is contained in:
commit
007b82c6ad
|
@ -2,13 +2,12 @@
|
|||
language: java
|
||||
|
||||
jdk:
|
||||
- openjdk7
|
||||
- oraclejdk7
|
||||
- oraclejdk8
|
||||
|
||||
before_install:
|
||||
- export BUILD_COVERAGE="$([ $TRAVIS_JDK_VERSION == 'openjdk7' ] && echo 'true')"
|
||||
- export BUILD_COVERAGE="$([ $TRAVIS_JDK_VERSION == 'oraclejdk8' ] && echo 'true')"
|
||||
install: echo "No need to run mvn install -DskipTests then mvn install. Running mvn install."
|
||||
script: mvn install
|
||||
after_success:
|
||||
- test -z "$BUILD_COVERAGE" || mvn clean cobertura:cobertura coveralls:report
|
||||
- test -z "$BUILD_COVERAGE" || mvn clean test jacoco:report coveralls:report
|
||||
|
|
|
@ -0,0 +1,244 @@
|
|||
## Release Notes
|
||||
|
||||
### 0.6.0
|
||||
|
||||
#### Enforce JWT Claims when Parsing
|
||||
|
||||
You can now enforce that JWT claims have expected values when parsing a compact JWT string.
|
||||
|
||||
For example, let's say that you require that the JWT you are parsing has a specific `sub` (subject) value,
|
||||
otherwise you may not trust the token. You can do that by using one of the `require` methods on the parser builder:
|
||||
|
||||
```java
|
||||
try {
|
||||
Jwts.parser().requireSubject("jsmith").setSigningKey(key).parseClaimsJws(s);
|
||||
} catch(InvalidClaimException ice) {
|
||||
// the sub field was missing or did not have a 'jsmith' value
|
||||
}
|
||||
```
|
||||
|
||||
If it is important to react to a missing vs an incorrect value, instead of catching `InvalidClaimException`, you can catch either `MissingClaimException` or `IncorrectClaimException`:
|
||||
|
||||
```java
|
||||
try {
|
||||
Jwts.parser().requireSubject("jsmith").setSigningKey(key).parseClaimsJws(s);
|
||||
} catch(MissingClaimException mce) {
|
||||
// the parsed JWT did not have the sub field
|
||||
} catch(IncorrectClaimException ice) {
|
||||
// the parsed JWT had a sub field, but its value was not equal to 'jsmith'
|
||||
}
|
||||
```
|
||||
|
||||
You can also require custom fields by using the `require(fieldName, requiredFieldValue)` method - for example:
|
||||
|
||||
```java
|
||||
try {
|
||||
Jwts.parser().require("myfield", "myRequiredValue").setSigningKey(key).parseClaimsJws(s);
|
||||
} catch(InvalidClaimException ice) {
|
||||
// the 'myfield' field was missing or did not have a 'myRequiredValue' value
|
||||
}
|
||||
```
|
||||
(or, again, you could catch either MissingClaimException or IncorrectClaimException instead)
|
||||
|
||||
#### Body Compression
|
||||
|
||||
**This feature is NOT JWT specification compliant**, *but it can be very useful when you parse your own tokens*.
|
||||
|
||||
If your JWT body is large and you have size restrictions (for example, if embedding a JWT in a URL and the URL must be under a certain length for legacy browsers or mail user agents), you may now compress the JWT body using a `CompressionCodec`:
|
||||
|
||||
```java
|
||||
Jwts.builder().claim("foo", "someReallyLongDataString...")
|
||||
.compressWith(CompressionCodecs.DEFLATE) // or CompressionCodecs.GZIP
|
||||
.signWith(SignatureAlgorithm.HS256, key)
|
||||
.compact();
|
||||
```
|
||||
|
||||
This will set a new `calg` header with the name of the compression algorithm used so that parsers can see that value and decompress accordingly.
|
||||
|
||||
The default parser implementation will automatically decompress DEFLATE or GZIP compressed bodies, so you don't need to set anything on the parser - it looks like normal:
|
||||
|
||||
```java
|
||||
Jwts.parser().setSigningKey(key).parseClaimsJws(compact);
|
||||
```
|
||||
|
||||
##### Custom Compression Algorithms
|
||||
|
||||
If the DEFLATE or GZIP algorithms are not sufficient for your needs, you can specify your own Compression algorithms by implementing the `CompressionCodec` interface and setting it on the parser:
|
||||
|
||||
```java
|
||||
Jwts.builder().claim("foo", "someReallyLongDataString...")
|
||||
.compressWith(new MyCompressionCodec())
|
||||
.signWith(SignatureAlgorithm.HS256, key)
|
||||
.compact();
|
||||
```
|
||||
|
||||
You will then need to specify a `CompressionCodecResolver` on the parser, so you can inspect the `calg` header and return your custom codec when discovered:
|
||||
|
||||
```java
|
||||
Jwts.parser().setSigningKey(key)
|
||||
.setCompressionCodecResolver(new MyCustomCompressionCodecResolver())
|
||||
.parseClaimsJws(compact);
|
||||
```
|
||||
|
||||
*NOTE*: Because body compression is not JWT specification compliant, you should only enable compression if both your JWT builder and parser are JJWT versions >= 0.6.0, or if you're using another library that implements the exact same functionality. This feature is best reserved for your own use cases - where you both create and later parse the tokens. It will likely cause problems if you compressed a token and expected a 3rd party (who doesn't use JJWT) to parse the token.
|
||||
|
||||
### 0.5.1
|
||||
|
||||
- Minor [bug](https://github.com/jwtk/jjwt/issues/31) fix [release](https://github.com/jwtk/jjwt/issues?q=milestone%3A0.5.1+is%3Aclosed) that ensures correct Base64 padding in Android runtimes.
|
||||
|
||||
### 0.5
|
||||
|
||||
- Android support! Android's built-in Base64 codec will be used if JJWT detects it is running in an Android environment. Other than Base64, all other parts of JJWT were already Android-compliant. Now it is fully compliant.
|
||||
|
||||
- Elliptic Curve signature algorithms! `SignatureAlgorithm.ES256`, `ES384` and `ES512` are now supported.
|
||||
|
||||
- Super convenient key generation methods, so you don't have to worry how to do this safely:
|
||||
- `MacProvider.generateKey(); //or generateKey(SignatureAlgorithm)`
|
||||
- `RsaProvider.generateKeyPair(); //or generateKeyPair(sizeInBits)`
|
||||
- `EllipticCurveProvider.generateKeyPair(); //or generateKeyPair(SignatureAlgorithm)`
|
||||
|
||||
The `generate`* methods that accept an `SignatureAlgorithm` argument know to generate a key of sufficient strength that reflects the specified algorithm strength.
|
||||
|
||||
Please see the full [0.5 closed issues list](https://github.com/jwtk/jjwt/issues?q=milestone%3A0.5+is%3Aclosed) for more information.
|
||||
|
||||
### 0.4
|
||||
|
||||
- [Issue 8](https://github.com/jwtk/jjwt/issues/8): Add ability to find signing key by inspecting the JWS values before verifying the signature.
|
||||
|
||||
This is a handy little feature. If you need to parse a signed JWT (a JWS) and you don't know which signing key was used to sign it, you can now use the new `SigningKeyResolver` concept.
|
||||
|
||||
A `SigningKeyresolver` can inspect the JWS header and body (Claims or String) _before_ the JWS signature is verified. By inspecting the data, you can find the key and return it, and the parser will use the returned key to validate the signature. For example:
|
||||
|
||||
```java
|
||||
SigningKeyResolver resolver = new MySigningKeyResolver();
|
||||
|
||||
Jws<Claims> jws = Jwts.parser().setSigningKeyResolver(resolver).parseClaimsJws(compact);
|
||||
```
|
||||
|
||||
The signature is still validated, and the JWT instance will still not be returned if the jwt string is invalid, as expected. You just get to 'see' the JWT data for key discovery before the parser validates. Nice.
|
||||
|
||||
This of course requires that you put some sort of information in the JWS when you create it so that your `SigningKeyResolver` implementation can look at it later and look up the key. The *standard* way to do this is to use the JWS `kid` ('key id') field, for example:
|
||||
|
||||
```java
|
||||
Jwts.builder().setHeaderParam("kid", your_signing_key_id_NOT_THE_SECRET).build();
|
||||
```
|
||||
|
||||
You could of course set any other header parameter or claims parameter instead of setting `kid` if you want - that's just the default field reserved for signing key identification. If you can locate the signing key based on other information in the header or claims, you don't need to set the `kid` field - just make sure your resolver implementation knows how to look up the key.
|
||||
|
||||
Finally, a nice `SigningKeyResolverAdapter` is provided to allow you to write quick and simple subclasses or anonymous classes instead of having to implement the `SigningKeyResolver` interface directly. For example:
|
||||
|
||||
```java
|
||||
Jws<Claims> jws = Jwts.parser().setSigningKeyResolver(new SigningKeyResolverAdapter() {
|
||||
@Override
|
||||
public byte[] resolveSigningKeyBytes(JwsHeader header, Claims claims) {
|
||||
//inspect the header or claims, lookup and return the signing key
|
||||
String keyId = header.getKeyId(); //or any other field that you need to inspect
|
||||
return getSigningKey(keyId); //implement me
|
||||
}})
|
||||
.parseClaimsJws(compact);
|
||||
```
|
||||
|
||||
### 0.3
|
||||
|
||||
- [Issue 6](https://github.com/jwtk/jjwt/issues/6): Parsing an expired Claims JWT or JWS (as determined by the `exp` claims field) will now throw an `ExpiredJwtException`.
|
||||
- [Issue 7](https://github.com/jwtk/jjwt/issues/7): Parsing a premature Claims JWT or JWS (as determined by the `nbf` claims field) will now throw a `PrematureJwtException`.
|
||||
|
||||
### 0.2
|
||||
|
||||
#### More convenient Claims building
|
||||
|
||||
This release adds convenience methods to the `JwtBuilder` interface so you can set claims directly on the builder without having to create a separate Claims instance/builder, reducing the amount of code you have to write. For example, this:
|
||||
|
||||
```java
|
||||
Claims claims = Jwts.claims().setSubject("Joe");
|
||||
|
||||
String compactJwt = Jwts.builder().setClaims(claims).signWith(HS256, key).compact();
|
||||
```
|
||||
|
||||
can now be written as:
|
||||
|
||||
```java
|
||||
String compactJwt = Jwts.builder().setSubject("Joe").signWith(HS256, key).compact();
|
||||
```
|
||||
|
||||
A Claims instance based on the specified claims will be created and set as the JWT's payload automatically.
|
||||
|
||||
#### Type-safe handling for JWT and JWS with generics
|
||||
|
||||
The following < 0.2 code produced a JWT as expected:
|
||||
|
||||
```java
|
||||
Jwt jwt = Jwts.parser().setSigningKey(key).parse(compact);
|
||||
```
|
||||
|
||||
But you couldn't easily determine if the `jwt` was a `JWT` or `JWS` instance or if the body was a `Claims` instance or a plaintext `String` without resorting to a bunch of yucky `instanceof` checks. In 0.2, we introduce the `JwtHandler` when you don't know the exact format of the compact JWT string ahead of time, and parsing convenience methods when you do.
|
||||
|
||||
##### JwtHandler
|
||||
|
||||
If you do not know the format of the compact JWT string at the time you try to parse it, you can determine what type it is after parsing by providing a `JwtHandler` instance to the `JwtParser` with the new `parse(String compactJwt, JwtHandler handler)` method. For example:
|
||||
|
||||
```java
|
||||
T returnVal = Jwts.parser().setSigningKey(key).parse(compact, new JwtHandler<T>() {
|
||||
@Override
|
||||
public T onPlaintextJwt(Jwt<Header, String> jwt) {
|
||||
//the JWT parsed was an unsigned plaintext JWT
|
||||
//inspect it, then return an instance of T (see returnVal above)
|
||||
}
|
||||
|
||||
@Override
|
||||
public T onClaimsJwt(Jwt<Header, Claims> jwt) {
|
||||
//the JWT parsed was an unsigned Claims JWT
|
||||
//inspect it, then return an instance of T (see returnVal above)
|
||||
}
|
||||
|
||||
@Override
|
||||
public T onPlaintextJws(Jws<String> jws) {
|
||||
//the JWT parsed was a signed plaintext JWS
|
||||
//inspect it, then return an instance of T (see returnVal above)
|
||||
}
|
||||
|
||||
@Override
|
||||
public T onClaimsJws(Jws<Claims> jws) {
|
||||
//the JWT parsed was a signed Claims JWS
|
||||
//inspect it, then return an instance of T (see returnVal above)
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
Of course, if you know you'll only have to parse a subset of the above, you can use the `JwtHandlerAdapter` and implement only the methods you need. For example:
|
||||
|
||||
```java
|
||||
T returnVal = Jwts.parser().setSigningKey(key).parse(plaintextJwt, new JwtHandlerAdapter<Jwt<Header, T>>() {
|
||||
@Override
|
||||
public T onPlaintextJws(Jws<String> jws) {
|
||||
//the JWT parsed was a signed plaintext JWS
|
||||
//inspect it, then return an instance of T (see returnVal above)
|
||||
}
|
||||
|
||||
@Override
|
||||
public T onClaimsJws(Jws<Claims> jws) {
|
||||
//the JWT parsed was a signed Claims JWS
|
||||
//inspect it, then return an instance of T (see returnVal above)
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
##### Known Type convenience parse methods
|
||||
|
||||
If, unlike above, you are confident of the compact string format and know which type of JWT or JWS it will produce, you can just use one of the 4 new convenience parsing methods to get exactly the type of JWT or JWS you know exists. For example:
|
||||
|
||||
```java
|
||||
|
||||
//for a known plaintext jwt string:
|
||||
Jwt<Header,String> jwt = Jwts.parser().parsePlaintextJwt(compact);
|
||||
|
||||
//for a known Claims JWT string:
|
||||
Jwt<Header,Claims> jwt = Jwts.parser().parseClaimsJwt(compact);
|
||||
|
||||
//for a known signed plaintext JWT (aka a plaintext JWS):
|
||||
Jws<String> jws = Jwts.parser().setSigningKey(key).parsePlaintextJws(compact);
|
||||
|
||||
//for a known signed Claims JWT (aka a Claims JWS):
|
||||
Jws<Claims> jws = Jwts.parser().setSigningKey(key).parseClaimsJws(compact);
|
||||
|
||||
```
|
373
README.md
373
README.md
|
@ -1,11 +1,60 @@
|
|||
[![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)
|
||||
[![Coverage Status](https://coveralls.io/repos/github/jwtk/jjwt/badge.svg?branch=master)](https://coveralls.io/github/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 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.
|
||||
JJWT is a Java implementation based 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.
|
||||
|
||||
The library was created by [Stormpath's](http://www.stormpath.com) CTO, [Les Hazlewood](https://github.com/lhazlewood)
|
||||
and is now maintained by a [community](https://github.com/jwtk/jjwt/graphs/contributors) of contributors.
|
||||
|
||||
We've also added some convenience extensions that are not part of the specification, such as JWT compression and claim enforcement.
|
||||
|
||||
## What's a JSON Web Token?
|
||||
|
||||
Don't know what a JSON Web Token is? Read on. Otherwise, jump on down to the [Installation](#installation) section.
|
||||
|
||||
JWT is a means of transmitting information between two parties in a compact, verifiable form.
|
||||
|
||||
The bits of information encoded in the body of a JWT are called `claims`. The expanded form of the JWT is in a JSON format, so each `claim` is a key in the JSON object.
|
||||
|
||||
JWTs can be cryptographically signed (making it a [JWS](https://tools.ietf.org/html/rfc7515)) or encrypted (making it a [JWE](https://tools.ietf.org/html/rfc7516)).
|
||||
|
||||
This adds a powerful layer of verifiability to the user of JWTs. The receiver has a high degree of confidence that the JWT has not been tampered with by verifying the signature, for instance.
|
||||
|
||||
The compacted representation of a signed JWT is a string that has three parts, each separated by a `.`:
|
||||
|
||||
```
|
||||
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJKb2UifQ.ipevRNuRP6HflG8cFKnmUPtypruRC4fb1DWtoLL62SY
|
||||
```
|
||||
|
||||
Each section is [base 64](https://en.wikipedia.org/wiki/Base64) encoded. The first section is the header, which at a minimum needs to specify the algorithm used to sign the JWT. The second section is the body. This section has all the claims of this JWT encoded in it. The final section is the signature. It's computed by passing a combination of the header and body through the algorithm specified in the header.
|
||||
|
||||
If you pass the first two sections through a base 64 decoder, you'll get the following (formatting added for clarity):
|
||||
|
||||
`header`
|
||||
```
|
||||
{
|
||||
"alg": "HS256"
|
||||
}
|
||||
```
|
||||
|
||||
`body`
|
||||
```
|
||||
{
|
||||
"sub": "Joe"
|
||||
}
|
||||
```
|
||||
|
||||
In this case, the information we have is that the HMAC using SHA-256 algorithm was used to sign the JWT. And, the body has a single claim, `sub` with value `Joe`.
|
||||
|
||||
There are a number of standard claims, called [Registered Claims](https://tools.ietf.org/html/rfc7519#section-4.1), in the specification and `sub` (for subject) is one of them.
|
||||
|
||||
To compute the signature, you must know the secret that was used to sign it. In this case, it was the word `secret`. You can see the signature creation is action [here](https://jsfiddle.net/dogeared/2fy2y0yd/11/) (Note: Trailing `=` are lopped off the signature for the JWT).
|
||||
|
||||
Now you know (just about) all you need to know about JWTs.
|
||||
|
||||
## Installation
|
||||
|
||||
|
@ -31,7 +80,7 @@ dependencies {
|
|||
|
||||
Note: JJWT depends on Jackson 2.x. If you're already using an older version of Jackson in your app, [read this](#olderJackson)
|
||||
|
||||
## Usage
|
||||
## Quickstart
|
||||
|
||||
Most complexity is hidden behind a convenient and readable builder-based [fluent interface](http://en.wikipedia.org/wiki/Fluent_interface), great for relying on IDE auto-completion to write code quickly. Here's an example:
|
||||
|
||||
|
@ -45,25 +94,38 @@ import java.security.Key;
|
|||
// the key would be read from your application configuration instead.
|
||||
Key key = MacProvider.generateKey();
|
||||
|
||||
String s = Jwts.builder().setSubject("Joe").signWith(SignatureAlgorithm.HS512, key).compact();
|
||||
String compactJws = Jwts.builder()
|
||||
.setSubject("Joe")
|
||||
.signWith(SignatureAlgorithm.HS512, key)
|
||||
.compact();
|
||||
```
|
||||
|
||||
How easy was that!?
|
||||
|
||||
In this case, we are *building* a JWT that will have the [registered claim](https://tools.ietf.org/html/rfc7519#section-4.1) `sub` (subject) set to `Joe`. We are signing the JWT using the HMAC using SHA-512 algorithm. finally, we are compacting it into its `String` form.
|
||||
|
||||
The resultant `String` looks like this:
|
||||
|
||||
```
|
||||
eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJKb2UifQ.yiV1GWDrQyCeoOswYTf_xvlgsnaVVYJM0mU6rkmRBf2T1MBl3Xh2kZii0Q9BdX5-G0j25Qv2WF4lA6jPl5GKuA
|
||||
```
|
||||
|
||||
Now let's verify the JWT (you should always discard JWTs that don't match an expected signature):
|
||||
|
||||
```java
|
||||
assert Jwts.parser().setSigningKey(key).parseClaimsJws(s).getBody().getSubject().equals("Joe");
|
||||
assert Jwts.parser().setSigningKey(key).parseClaimsJws(compactJws).getBody().getSubject().equals("Joe");
|
||||
```
|
||||
|
||||
You have to love one-line code snippets!
|
||||
There are two things going on here. The `key` from before is being used to validate the signature of the JWT. If it fails to verify the JWT, a `SignatureException` is thrown. Assuming the JWT is validated, we parse out the claims and assert that that subject is set to `Joe`.
|
||||
|
||||
You have to love code one-liners that pack a punch!
|
||||
|
||||
But what if signature validation failed? You can catch `SignatureException` and react accordingly:
|
||||
|
||||
```java
|
||||
try {
|
||||
|
||||
Jwts.parser().setSigningKey(key).parseClaimsJws(compactJwt);
|
||||
Jwts.parser().setSigningKey(key).parseClaimsJws(compactJws);
|
||||
|
||||
//OK, we can trust this JWT
|
||||
|
||||
|
@ -75,6 +137,8 @@ try {
|
|||
|
||||
## Supported Features
|
||||
|
||||
### Specification Compliant:
|
||||
|
||||
* Creating and parsing plaintext compact JWTs
|
||||
|
||||
* Creating, parsing and verifying digitally signed compact JWTs (aka JWSs) with all standard JWS algorithms:
|
||||
|
@ -91,12 +155,55 @@ try {
|
|||
* ES384: ECDSA using P-384 and SHA-384
|
||||
* ES512: ECDSA using P-512 and SHA-512
|
||||
|
||||
### Enhancements Beyond the Specification:
|
||||
|
||||
* **Body compression.** If the JWT body is large, you can use a `CompressionCodec` to compress it. Best of all, the JJWT library will automtically decompress and parse the JWT without additional coding.
|
||||
|
||||
```java
|
||||
String compactJws = Jwts.builder()
|
||||
.setSubject("Joe")
|
||||
.compressWith(CompressionCodecs.DEFLATE)
|
||||
.signWith(SignatureAlgorithm.HS512, key)
|
||||
.compact();
|
||||
```
|
||||
|
||||
If you examine the header section of the `compactJws`, it decodes to this:
|
||||
|
||||
```
|
||||
{
|
||||
"alg": "HS512",
|
||||
"zip": "DEF"
|
||||
}
|
||||
```
|
||||
|
||||
JJWT automatically detects that compression was used by examining the header and will automatically decompress when parsing. No extra coding is needed on your part for decompression.
|
||||
|
||||
* **Require Claims.** When parsing, you can specify that certain calims *must* be present and set to a certain value.
|
||||
|
||||
```java
|
||||
try {
|
||||
Jws<Claims> claims = Jwts.parser()
|
||||
.requireSubject("Joe")
|
||||
.require("hasMotorcycle", true)
|
||||
.setSigningKey(key)
|
||||
.parseClaimsJws(compactJws);
|
||||
} catch (MissingClaimException e) {
|
||||
|
||||
// we get here if the required claim is not present
|
||||
|
||||
} catch (IncorrectClaimException) {
|
||||
|
||||
// we get here if ther required claim has the wrong value
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
## Currently Unsupported Features
|
||||
|
||||
* [Non-compact](https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-31#section-7.2) serialization and parsing.
|
||||
* JWE (Encryption for JWT)
|
||||
|
||||
These feature sets will be implemented in a future release when possible. Community contributions are welcome!
|
||||
These feature sets will be implemented in a future release. Community contributions are welcome!
|
||||
|
||||
## Learn More
|
||||
|
||||
|
@ -105,251 +212,7 @@ These feature sets will be implemented in a future release when possible. Commu
|
|||
- [Where to Store Your JWTs - Cookies vs HTML5 Web Storage](https://stormpath.com/blog/where-to-store-your-jwts-cookies-vs-html5-web-storage/)
|
||||
- [Use JWT the Right Way!](https://stormpath.com/blog/jwt-the-right-way/)
|
||||
- [Token Authentication for Java Applications](https://stormpath.com/blog/token-auth-for-java/)
|
||||
|
||||
## Release Notes
|
||||
|
||||
### 0.6.0
|
||||
|
||||
#### Enforce JWT Claims when Parsing
|
||||
|
||||
You can now enforce that JWT claims have expected values when parsing a compact JWT string.
|
||||
|
||||
For example, let's say that you require that the JWT you are parsing has a specific `sub` (subject) value,
|
||||
otherwise you may not trust the token. You can do that by using one of the `require` methods on the parser builder:
|
||||
|
||||
```java
|
||||
try {
|
||||
Jwts.parser().requireSubject("jsmith").setSigningKey(key).parseClaimsJws(s);
|
||||
} catch(InvalidClaimException ice) {
|
||||
// the sub field was missing or did not have a 'jsmith' value
|
||||
}
|
||||
```
|
||||
|
||||
If it is important to react to a missing vs an incorrect value, instead of catching `InvalidClaimException`, you can catch either `MissingClaimException` or `IncorrectClaimException`:
|
||||
|
||||
```java
|
||||
try {
|
||||
Jwts.parser().requireSubject("jsmith").setSigningKey(key).parseClaimsJws(s);
|
||||
} catch(MissingClaimException mce) {
|
||||
// the parsed JWT did not have the sub field
|
||||
} catch(IncorrectClaimException ice) {
|
||||
// the parsed JWT had a sub field, but its value was not equal to 'jsmith'
|
||||
}
|
||||
```
|
||||
|
||||
You can also require custom fields by using the `require(fieldName, requiredFieldValue)` method - for example:
|
||||
|
||||
```java
|
||||
try {
|
||||
Jwts.parser().require("myfield", "myRequiredValue").setSigningKey(key).parseClaimsJws(s);
|
||||
} catch(InvalidClaimException ice) {
|
||||
// the 'myfield' field was missing or did not have a 'myRequiredValue' value
|
||||
}
|
||||
```
|
||||
(or, again, you could catch either MissingClaimException or IncorrectClaimException instead)
|
||||
|
||||
#### Body Compression
|
||||
|
||||
**This feature is NOT JWT specification compliant**, *but it can be very useful when you parse your own tokens*.
|
||||
|
||||
If your JWT body is large and you have size restrictions (for example, if embedding a JWT in a URL and the URL must be under a certain length for legacy browsers or mail user agents), you may now compress the JWT body using a `CompressionCodec`:
|
||||
|
||||
```java
|
||||
Jwts.builder().claim("foo", "someReallyLongDataString...")
|
||||
.compressWith(CompressionCodecs.DEFLATE) // or CompressionCodecs.GZIP
|
||||
.signWith(SignatureAlgorithm.HS256, key)
|
||||
.compact();
|
||||
```
|
||||
|
||||
This will set a new `calg` header with the name of the compression algorithm used so that parsers can see that value and decompress accordingly.
|
||||
|
||||
The default parser implementation will automatically decompress DEFLATE or GZIP compressed bodies, so you don't need to set anything on the parser - it looks like normal:
|
||||
|
||||
```java
|
||||
Jwts.parser().setSigningKey(key).parseClaimsJws(compact);
|
||||
```
|
||||
|
||||
##### Custom Compression Algorithms
|
||||
|
||||
If the DEFLATE or GZIP algorithms are not sufficient for your needs, you can specify your own Compression algorithms by implementing the `CompressionCodec` interface and setting it on the parser:
|
||||
|
||||
```java
|
||||
Jwts.builder().claim("foo", "someReallyLongDataString...")
|
||||
.compressWith(new MyCompressionCodec())
|
||||
.signWith(SignatureAlgorithm.HS256, key)
|
||||
.compact();
|
||||
```
|
||||
|
||||
You will then need to specify a `CompressionCodecResolver` on the parser, so you can inspect the `calg` header and return your custom codec when discovered:
|
||||
|
||||
```java
|
||||
Jwts.parser().setSigningKey(key)
|
||||
.setCompressionCodecResolver(new MyCustomCompressionCodecResolver())
|
||||
.parseClaimsJws(compact);
|
||||
```
|
||||
|
||||
*NOTE*: Because body compression is not JWT specification compliant, you should only enable compression if both your JWT builder and parser are JJWT versions >= 0.6.0, or if you're using another library that implements the exact same functionality. This feature is best reserved for your own use cases - where you both create and later parse the tokens. It will likely cause problems if you compressed a token and expected a 3rd party (who doesn't use JJWT) to parse the token.
|
||||
|
||||
### 0.5.1
|
||||
|
||||
- Minor [bug](https://github.com/jwtk/jjwt/issues/31) fix [release](https://github.com/jwtk/jjwt/issues?q=milestone%3A0.5.1+is%3Aclosed) that ensures correct Base64 padding in Android runtimes.
|
||||
|
||||
### 0.5
|
||||
|
||||
- Android support! Android's built-in Base64 codec will be used if JJWT detects it is running in an Android environment. Other than Base64, all other parts of JJWT were already Android-compliant. Now it is fully compliant.
|
||||
|
||||
- Elliptic Curve signature algorithms! `SignatureAlgorithm.ES256`, `ES384` and `ES512` are now supported.
|
||||
|
||||
- Super convenient key generation methods, so you don't have to worry how to do this safely:
|
||||
- `MacProvider.generateKey(); //or generateKey(SignatureAlgorithm)`
|
||||
- `RsaProvider.generateKeyPair(); //or generateKeyPair(sizeInBits)`
|
||||
- `EllipticCurveProvider.generateKeyPair(); //or generateKeyPair(SignatureAlgorithm)`
|
||||
|
||||
The `generate`* methods that accept an `SignatureAlgorithm` argument know to generate a key of sufficient strength that reflects the specified algorithm strength.
|
||||
|
||||
Please see the full [0.5 closed issues list](https://github.com/jwtk/jjwt/issues?q=milestone%3A0.5+is%3Aclosed) for more information.
|
||||
|
||||
### 0.4
|
||||
|
||||
- [Issue 8](https://github.com/jwtk/jjwt/issues/8): Add ability to find signing key by inspecting the JWS values before verifying the signature.
|
||||
|
||||
This is a handy little feature. If you need to parse a signed JWT (a JWS) and you don't know which signing key was used to sign it, you can now use the new `SigningKeyResolver` concept.
|
||||
|
||||
A `SigningKeyresolver` can inspect the JWS header and body (Claims or String) _before_ the JWS signature is verified. By inspecting the data, you can find the key and return it, and the parser will use the returned key to validate the signature. For example:
|
||||
|
||||
```java
|
||||
SigningKeyResolver resolver = new MySigningKeyResolver();
|
||||
|
||||
Jws<Claims> jws = Jwts.parser().setSigningKeyResolver(resolver).parseClaimsJws(compact);
|
||||
```
|
||||
|
||||
The signature is still validated, and the JWT instance will still not be returned if the jwt string is invalid, as expected. You just get to 'see' the JWT data for key discovery before the parser validates. Nice.
|
||||
|
||||
This of course requires that you put some sort of information in the JWS when you create it so that your `SigningKeyResolver` implementation can look at it later and look up the key. The *standard* way to do this is to use the JWS `kid` ('key id') field, for example:
|
||||
|
||||
```java
|
||||
Jwts.builder().setHeaderParam("kid", your_signing_key_id_NOT_THE_SECRET).build();
|
||||
```
|
||||
|
||||
You could of course set any other header parameter or claims parameter instead of setting `kid` if you want - that's just the default field reserved for signing key identification. If you can locate the signing key based on other information in the header or claims, you don't need to set the `kid` field - just make sure your resolver implementation knows how to look up the key.
|
||||
|
||||
Finally, a nice `SigningKeyResolverAdapter` is provided to allow you to write quick and simple subclasses or anonymous classes instead of having to implement the `SigningKeyResolver` interface directly. For example:
|
||||
|
||||
```java
|
||||
Jws<Claims> jws = Jwts.parser().setSigningKeyResolver(new SigningKeyResolverAdapter() {
|
||||
@Override
|
||||
public byte[] resolveSigningKeyBytes(JwsHeader header, Claims claims) {
|
||||
//inspect the header or claims, lookup and return the signing key
|
||||
String keyId = header.getKeyId(); //or any other field that you need to inspect
|
||||
return getSigningKey(keyId); //implement me
|
||||
}})
|
||||
.parseClaimsJws(compact);
|
||||
```
|
||||
|
||||
### 0.3
|
||||
|
||||
- [Issue 6](https://github.com/jwtk/jjwt/issues/6): Parsing an expired Claims JWT or JWS (as determined by the `exp` claims field) will now throw an `ExpiredJwtException`.
|
||||
- [Issue 7](https://github.com/jwtk/jjwt/issues/7): Parsing a premature Claims JWT or JWS (as determined by the `nbf` claims field) will now throw a `PrematureJwtException`.
|
||||
|
||||
### 0.2
|
||||
|
||||
#### More convenient Claims building
|
||||
|
||||
This release adds convenience methods to the `JwtBuilder` interface so you can set claims directly on the builder without having to create a separate Claims instance/builder, reducing the amount of code you have to write. For example, this:
|
||||
|
||||
```java
|
||||
Claims claims = Jwts.claims().setSubject("Joe");
|
||||
|
||||
String compactJwt = Jwts.builder().setClaims(claims).signWith(HS256, key).compact();
|
||||
```
|
||||
|
||||
can now be written as:
|
||||
|
||||
```java
|
||||
String compactJwt = Jwts.builder().setSubject("Joe").signWith(HS256, key).compact();
|
||||
```
|
||||
|
||||
A Claims instance based on the specified claims will be created and set as the JWT's payload automatically.
|
||||
|
||||
#### Type-safe handling for JWT and JWS with generics
|
||||
|
||||
The following < 0.2 code produced a JWT as expected:
|
||||
|
||||
```java
|
||||
Jwt jwt = Jwts.parser().setSigningKey(key).parse(compact);
|
||||
```
|
||||
|
||||
But you couldn't easily determine if the `jwt` was a `JWT` or `JWS` instance or if the body was a `Claims` instance or a plaintext `String` without resorting to a bunch of yucky `instanceof` checks. In 0.2, we introduce the `JwtHandler` when you don't know the exact format of the compact JWT string ahead of time, and parsing convenience methods when you do.
|
||||
|
||||
##### JwtHandler
|
||||
|
||||
If you do not know the format of the compact JWT string at the time you try to parse it, you can determine what type it is after parsing by providing a `JwtHandler` instance to the `JwtParser` with the new `parse(String compactJwt, JwtHandler handler)` method. For example:
|
||||
|
||||
```java
|
||||
T returnVal = Jwts.parser().setSigningKey(key).parse(compact, new JwtHandler<T>() {
|
||||
@Override
|
||||
public T onPlaintextJwt(Jwt<Header, String> jwt) {
|
||||
//the JWT parsed was an unsigned plaintext JWT
|
||||
//inspect it, then return an instance of T (see returnVal above)
|
||||
}
|
||||
|
||||
@Override
|
||||
public T onClaimsJwt(Jwt<Header, Claims> jwt) {
|
||||
//the JWT parsed was an unsigned Claims JWT
|
||||
//inspect it, then return an instance of T (see returnVal above)
|
||||
}
|
||||
|
||||
@Override
|
||||
public T onPlaintextJws(Jws<String> jws) {
|
||||
//the JWT parsed was a signed plaintext JWS
|
||||
//inspect it, then return an instance of T (see returnVal above)
|
||||
}
|
||||
|
||||
@Override
|
||||
public T onClaimsJws(Jws<Claims> jws) {
|
||||
//the JWT parsed was a signed Claims JWS
|
||||
//inspect it, then return an instance of T (see returnVal above)
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
Of course, if you know you'll only have to parse a subset of the above, you can use the `JwtHandlerAdapter` and implement only the methods you need. For example:
|
||||
|
||||
```java
|
||||
T returnVal = Jwts.parser().setSigningKey(key).parse(plaintextJwt, new JwtHandlerAdapter<Jwt<Header, T>>() {
|
||||
@Override
|
||||
public T onPlaintextJws(Jws<String> jws) {
|
||||
//the JWT parsed was a signed plaintext JWS
|
||||
//inspect it, then return an instance of T (see returnVal above)
|
||||
}
|
||||
|
||||
@Override
|
||||
public T onClaimsJws(Jws<Claims> jws) {
|
||||
//the JWT parsed was a signed Claims JWS
|
||||
//inspect it, then return an instance of T (see returnVal above)
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
##### Known Type convenience parse methods
|
||||
|
||||
If, unlike above, you are confident of the compact string format and know which type of JWT or JWS it will produce, you can just use one of the 4 new convenience parsing methods to get exactly the type of JWT or JWS you know exists. For example:
|
||||
|
||||
```java
|
||||
|
||||
//for a known plaintext jwt string:
|
||||
Jwt<Header,String> jwt = Jwts.parser().parsePlaintextJwt(compact);
|
||||
|
||||
//for a known Claims JWT string:
|
||||
Jwt<Header,Claims> jwt = Jwts.parser().parseClaimsJwt(compact);
|
||||
|
||||
//for a known signed plaintext JWT (aka a plaintext JWS):
|
||||
Jws<String> jws = Jwts.parser().setSigningKey(key).parsePlaintextJws(compact);
|
||||
|
||||
//for a known signed Claims JWT (aka a Claims JWS):
|
||||
Jws<Claims> jws = Jwts.parser().setSigningKey(key).parseClaimsJws(compact);
|
||||
|
||||
```
|
||||
- [JJWT Changelog](CHANGELOG.md)
|
||||
|
||||
<a name="olderJackson"></a>
|
||||
#### Already using an older Jackson dependency?
|
||||
|
@ -370,4 +233,4 @@ Maintained by [Stormpath](https://stormpath.com/)
|
|||
|
||||
## Licensing
|
||||
|
||||
This project is open-source via the [Apache 2.0 License](http://www.apache.org/licenses/LICENSE-2.0).
|
||||
This project is open-source via the [Apache 2.0 License](http://www.apache.org/licenses/LICENSE-2.0).
|
||||
|
|
80
pom.xml
80
pom.xml
|
@ -61,7 +61,7 @@
|
|||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<buildNumber>${user.name}-${maven.build.timestamp}</buildNumber>
|
||||
|
||||
<jackson.version>2.4.2</jackson.version>
|
||||
<jackson.version>2.7.0</jackson.version>
|
||||
|
||||
<!-- Optional Runtime Dependencies: -->
|
||||
<bouncycastle.version>1.51</bouncycastle.version>
|
||||
|
@ -270,66 +270,19 @@
|
|||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>cobertura-maven-plugin</artifactId>
|
||||
<version>2.7</version>
|
||||
<groupId>org.jacoco</groupId>
|
||||
<artifactId>jacoco-maven-plugin</artifactId>
|
||||
<version>0.7.6.201602180812</version>
|
||||
<configuration>
|
||||
<maxmem>256m</maxmem>
|
||||
<aggregate>true</aggregate>
|
||||
<instrumentation>
|
||||
<excludes>
|
||||
<exclude>io/jsonwebtoken/lang/*.class</exclude>
|
||||
</excludes>
|
||||
<ignores>
|
||||
<ignore>io.jsonwebtoken.lang.*</ignore>
|
||||
</ignores>
|
||||
</instrumentation>
|
||||
<check>
|
||||
<lineRate>100</lineRate>
|
||||
<branchRate>100</branchRate>
|
||||
<packageLineRate>100</packageLineRate>
|
||||
<packageBranchRate>100</packageBranchRate>
|
||||
<haltOnFailure>true</haltOnFailure>
|
||||
<regexes>
|
||||
<regex>
|
||||
<!-- Cannot get to 100% on DefaultClaims because of Cobertura bug w/ generics:
|
||||
https://github.com/cobertura/cobertura/issues/207 -->
|
||||
<pattern>io.jsonwebtoken.impl.DefaultClaims</pattern>
|
||||
<lineRate>96</lineRate>
|
||||
<branchRate>100</branchRate>
|
||||
</regex>
|
||||
<regex>
|
||||
<pattern>io.jsonwebtoken.impl.DefaultJwtBuilder</pattern>
|
||||
<lineRate>91</lineRate>
|
||||
<branchRate>85</branchRate>
|
||||
</regex>
|
||||
<regex>
|
||||
<pattern>io.jsonwebtoken.impl.DefaultJwtParser</pattern>
|
||||
<lineRate>97</lineRate>
|
||||
<branchRate>88</branchRate>
|
||||
</regex>
|
||||
<regex>
|
||||
<pattern>io.jsonwebtoken.impl.crypto.EllipticCurveSignatureValidator</pattern>
|
||||
<lineRate>95</lineRate>
|
||||
<branchRate>100</branchRate>
|
||||
</regex>
|
||||
<regex>
|
||||
<pattern>io.jsonwebtoken.impl.crypto.RsaSignatureValidator</pattern>
|
||||
<lineRate>93</lineRate>
|
||||
<branchRate>100</branchRate>
|
||||
</regex>
|
||||
</regexes>
|
||||
</check>
|
||||
<formats>
|
||||
<format>xml</format>
|
||||
<format>html</format>
|
||||
</formats>
|
||||
<excludes>
|
||||
<exclude>**/io/jsonwebtoken/lang/*</exclude>
|
||||
</excludes>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>prepare-agent</id>
|
||||
<goals>
|
||||
<goal>clean</goal>
|
||||
<goal>check</goal>
|
||||
<goal>prepare-agent</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
|
@ -355,7 +308,7 @@
|
|||
<plugin>
|
||||
<groupId>org.apache.felix</groupId>
|
||||
<artifactId>maven-bundle-plugin</artifactId>
|
||||
<version>2.3.5</version>
|
||||
<version>3.0.1</version>
|
||||
<extensions>true</extensions>
|
||||
<executions>
|
||||
<execution>
|
||||
|
@ -366,6 +319,17 @@
|
|||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<instructions>
|
||||
<Import-Package><![CDATA[
|
||||
android.util;resolution:=optional,
|
||||
org.bouncycastle.jce;resolution:=optional,
|
||||
org.bouncycastle.jce.spec;resolution:=optional,
|
||||
*
|
||||
]]>
|
||||
</Import-Package>
|
||||
</instructions>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.eluder.coveralls</groupId>
|
||||
|
@ -374,7 +338,6 @@
|
|||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>jdk8</id>
|
||||
|
@ -441,5 +404,4 @@
|
|||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
</project>
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
package io.jsonwebtoken;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* A clock represents a time source that can be used when creating and verifying JWTs.
|
||||
*
|
||||
* @since 0.7.0
|
||||
*/
|
||||
public interface Clock {
|
||||
|
||||
/**
|
||||
* Returns the clock's current timestamp at the instant the method is invoked.
|
||||
*
|
||||
* @return the clock's current timestamp at the instant the method is invoked.
|
||||
*/
|
||||
Date now();
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package io.jsonwebtoken;
|
||||
|
||||
import io.jsonwebtoken.impl.compression.DeflateCompressionCodec;
|
||||
import io.jsonwebtoken.impl.compression.GzipCompressionCodec;
|
||||
|
||||
/**
|
||||
* Provides default implementations of the {@link CompressionCodec} interface.
|
||||
*
|
||||
* @see #DEFLATE
|
||||
* @see #GZIP
|
||||
* @since 0.7.0
|
||||
*/
|
||||
public final class CompressionCodecs {
|
||||
|
||||
private static final CompressionCodecs INSTANCE = new CompressionCodecs();
|
||||
|
||||
private CompressionCodecs() {} //prevent external instantiation
|
||||
|
||||
/**
|
||||
* Codec implementing the <a href="https://tools.ietf.org/html/rfc7518">JWA</a> standard
|
||||
* <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.
|
||||
* <h5>Compatibility Warning</h5>
|
||||
* <p><b>This is not a standard JWA compression algorithm</b>. Be sure to use this only when you are confident
|
||||
* that all parties accessing the token support the gzip algorithm.</p>
|
||||
* <p>If you're concerned about compatibility, the {@link #DEFLATE DEFLATE} code is JWA standards-compliant.</p>
|
||||
*/
|
||||
public static final CompressionCodec GZIP = new GzipCompressionCodec();
|
||||
|
||||
}
|
|
@ -48,8 +48,13 @@ public interface Header<T extends Header<T>> extends Map<String,Object> {
|
|||
/** JWT {@code Content Type} header parameter name: <code>"cty"</code> */
|
||||
public static final String CONTENT_TYPE = "cty";
|
||||
|
||||
/** JWT {@code Compression Algorithm} header parameter name: <code>"calg"</code> */
|
||||
public static final String COMPRESSION_ALGORITHM = "calg";
|
||||
/** JWT {@code Compression Algorithm} header parameter name: <code>"zip"</code> */
|
||||
public static final String COMPRESSION_ALGORITHM = "zip";
|
||||
|
||||
/** JJWT legacy/deprecated compression algorithm header parameter name: <code>"calg"</code>
|
||||
* @deprecated use {@link #COMPRESSION_ALGORITHM} instead. */
|
||||
@Deprecated
|
||||
public static final String DEPRECATED_COMPRESSION_ALGORITHM = "calg";
|
||||
|
||||
/**
|
||||
* Returns the <a href="https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-25#section-5.1">
|
||||
|
|
|
@ -356,12 +356,18 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
|
|||
* 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>
|
||||
* <h5>Compatibility Warning</h5>
|
||||
*
|
||||
* @see io.jsonwebtoken.impl.compression.CompressionCodecs
|
||||
* <p>The JWT family of specifications defines compression only for JWE (Json Web Encryption)
|
||||
* tokens. Even so, JJWT will also support compression for JWS tokens as well if you choose to use it.
|
||||
* However, be aware that <b>if you use compression when creating a JWS token, other libraries may not be able to
|
||||
* parse that JWS token</b>. When using compression for JWS tokens, be sure that that all parties accessing the
|
||||
* JWS token support compression for JWS.</p>
|
||||
*
|
||||
* <p>Compression when creating JWE tokens however should be universally accepted for any
|
||||
* library that supports JWE.</p>
|
||||
*
|
||||
* @see io.jsonwebtoken.CompressionCodecs
|
||||
*
|
||||
* @param codec implementation of the {@link CompressionCodec} to be used.
|
||||
* @return the builder for method chaining.
|
||||
|
|
|
@ -15,6 +15,8 @@
|
|||
*/
|
||||
package io.jsonwebtoken;
|
||||
|
||||
import io.jsonwebtoken.impl.DefaultClock;
|
||||
|
||||
import java.security.Key;
|
||||
import java.util.Date;
|
||||
|
||||
|
@ -124,6 +126,16 @@ public interface JwtParser {
|
|||
*/
|
||||
JwtParser require(String claimName, Object value);
|
||||
|
||||
/**
|
||||
* Sets the {@link Clock} that determines the timestamp to use when validating the parsed JWT.
|
||||
* The parser uses a {@link DefaultClock DefaultClock} instance by default.
|
||||
*
|
||||
* @param clock a {@code Clock} object to return the timestamp to use when validating the parsed JWT.
|
||||
* @return the builder instance for method chaining.
|
||||
* @since 0.7.0
|
||||
*/
|
||||
JwtParser setClock(Clock clock);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
package io.jsonwebtoken.impl;
|
||||
|
||||
import io.jsonwebtoken.Clock;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Default {@link Clock} implementation.
|
||||
*
|
||||
* @since 0.7.0
|
||||
*/
|
||||
public class DefaultClock implements Clock {
|
||||
|
||||
/**
|
||||
* Default static instance that may be shared. It is thread-safe.
|
||||
*/
|
||||
public static final Clock INSTANCE = new DefaultClock();
|
||||
|
||||
/**
|
||||
* Simply returns <code>new {@link Date}()</code>.
|
||||
*
|
||||
* @return a new {@link Date} instance.
|
||||
*/
|
||||
@Override
|
||||
public Date now() {
|
||||
return new Date();
|
||||
}
|
||||
}
|
|
@ -16,6 +16,7 @@
|
|||
package io.jsonwebtoken.impl;
|
||||
|
||||
import io.jsonwebtoken.Header;
|
||||
import io.jsonwebtoken.lang.Strings;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
|
@ -52,9 +53,14 @@ public class DefaultHeader<T extends Header<T>> extends JwtMap implements Header
|
|||
return (T)this;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public String getCompressionAlgorithm() {
|
||||
return getString(COMPRESSION_ALGORITHM);
|
||||
String alg = getString(COMPRESSION_ALGORITHM);
|
||||
if (!Strings.hasText(alg)) {
|
||||
alg = getString(DEPRECATED_COMPRESSION_ALGORITHM);
|
||||
}
|
||||
return alg;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -18,6 +18,7 @@ package io.jsonwebtoken.impl;
|
|||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.jsonwebtoken.ClaimJwtException;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.Clock;
|
||||
import io.jsonwebtoken.CompressionCodec;
|
||||
import io.jsonwebtoken.CompressionCodecResolver;
|
||||
import io.jsonwebtoken.ExpiredJwtException;
|
||||
|
@ -69,6 +70,8 @@ public class DefaultJwtParser implements JwtParser {
|
|||
|
||||
Claims expectedClaims = new DefaultClaims();
|
||||
|
||||
private Clock clock = DefaultClock.INSTANCE;
|
||||
|
||||
@Override
|
||||
public JwtParser requireIssuedAt(Date issuedAt) {
|
||||
expectedClaims.setIssuedAt(issuedAt);
|
||||
|
@ -127,6 +130,13 @@ public class DefaultJwtParser implements JwtParser {
|
|||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JwtParser setClock(Clock clock) {
|
||||
Assert.notNull(clock, "Clock instance cannot be null.");
|
||||
this.clock = clock;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JwtParser setSigningKey(byte[] key) {
|
||||
Assert.notEmpty(key, "signing key cannot be null or empty.");
|
||||
|
@ -309,8 +319,8 @@ public class DefaultJwtParser implements JwtParser {
|
|||
|
||||
if (!Objects.isEmpty(keyBytes)) {
|
||||
|
||||
Assert.isTrue(!algorithm.isRsa(),
|
||||
"Key bytes cannot be specified for RSA signatures. Please specify a PublicKey or PrivateKey instance.");
|
||||
Assert.isTrue(algorithm.isHmac(),
|
||||
"Key bytes can only be specified for HMAC signatures. Please specify a PublicKey or PrivateKey instance.");
|
||||
|
||||
key = new SecretKeySpec(keyBytes, algorithm.getJcaName());
|
||||
}
|
||||
|
@ -346,16 +356,15 @@ public class DefaultJwtParser implements JwtParser {
|
|||
//since 0.3:
|
||||
if (claims != null) {
|
||||
|
||||
Date now = null;
|
||||
SimpleDateFormat sdf;
|
||||
|
||||
final Date now = this.clock.now();
|
||||
|
||||
//https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-30#section-4.1.4
|
||||
//token MUST NOT be accepted on or after any specified exp time:
|
||||
Date exp = claims.getExpiration();
|
||||
if (exp != null) {
|
||||
|
||||
now = new Date();
|
||||
|
||||
if (now.equals(exp) || now.after(exp)) {
|
||||
sdf = new SimpleDateFormat(ISO_8601_FORMAT);
|
||||
String expVal = sdf.format(exp);
|
||||
|
@ -371,10 +380,6 @@ public class DefaultJwtParser implements JwtParser {
|
|||
Date nbf = claims.getNotBefore();
|
||||
if (nbf != null) {
|
||||
|
||||
if (now == null) {
|
||||
now = new Date();
|
||||
}
|
||||
|
||||
if (now.before(nbf)) {
|
||||
sdf = new SimpleDateFormat(ISO_8601_FORMAT);
|
||||
String nbfVal = sdf.format(nbf);
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
package io.jsonwebtoken.impl;
|
||||
|
||||
import io.jsonwebtoken.Clock;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* A {@code Clock} implementation that is constructed with a seed timestamp and always reports that same
|
||||
* timestamp.
|
||||
*
|
||||
* @since 0.7.0
|
||||
*/
|
||||
public class FixedClock implements Clock {
|
||||
|
||||
private final Date now;
|
||||
|
||||
/**
|
||||
* Creates a new fixed clock using <code>new {@link Date Date}()</code> as the seed timestamp. All calls to
|
||||
* {@link #now now()} will always return this seed Date.
|
||||
*/
|
||||
public FixedClock() {
|
||||
this(new Date());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new fixed clock using the specified seed timestamp. All calls to
|
||||
* {@link #now now()} will always return this seed Date.
|
||||
*
|
||||
* @param now the specified Date to always return from all calls to {@link #now now()}.
|
||||
*/
|
||||
public FixedClock(Date now) {
|
||||
this.now = now;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date now() {
|
||||
return this.now;
|
||||
}
|
||||
}
|
|
@ -24,7 +24,9 @@ import io.jsonwebtoken.CompressionCodec;
|
|||
* @see #GZIP
|
||||
*
|
||||
* @since 0.6.0
|
||||
* @deprecated use {@link io.jsonwebtoken.CompressionCodecs} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public final class CompressionCodecs {
|
||||
|
||||
private static final CompressionCodecs I = new CompressionCodecs();
|
||||
|
@ -33,12 +35,16 @@ public final class CompressionCodecs {
|
|||
|
||||
/**
|
||||
* Codec implementing the <a href="https://en.wikipedia.org/wiki/DEFLATE">deflate</a> compression algorithm
|
||||
* @deprecated use {@link io.jsonwebtoken.CompressionCodecs#DEFLATE} instead.
|
||||
*/
|
||||
public static final CompressionCodec DEFLATE = new DeflateCompressionCodec();
|
||||
@Deprecated
|
||||
public static final CompressionCodec DEFLATE = io.jsonwebtoken.CompressionCodecs.DEFLATE;
|
||||
|
||||
/**
|
||||
* Codec implementing the <a href="https://en.wikipedia.org/wiki/Gzip">gzip</a> compression algorithm
|
||||
* @deprecated use {@link io.jsonwebtoken.CompressionCodecs#GZIP} instead.
|
||||
*/
|
||||
public static final CompressionCodec GZIP = new GzipCompressionCodec();
|
||||
@Deprecated
|
||||
public static final CompressionCodec GZIP = io.jsonwebtoken.CompressionCodecs.GZIP;
|
||||
|
||||
}
|
||||
|
|
|
@ -15,9 +15,6 @@
|
|||
*/
|
||||
package io.jsonwebtoken.impl.crypto;
|
||||
|
||||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
import io.jsonwebtoken.lang.Assert;
|
||||
|
||||
import java.security.Key;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
|
@ -25,6 +22,10 @@ import java.security.SecureRandom;
|
|||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import io.jsonwebtoken.JwtException;
|
||||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
import io.jsonwebtoken.lang.Assert;
|
||||
|
||||
/**
|
||||
* ElliptiCurve crypto provider.
|
||||
*
|
||||
|
@ -135,4 +136,166 @@ public abstract class EllipticCurveProvider extends SignatureProvider {
|
|||
throw new IllegalStateException("Unable to generate Elliptic Curve KeyPair: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the expected signature byte array length (R + S parts) for
|
||||
* the specified ECDSA algorithm.
|
||||
*
|
||||
* @param alg The ECDSA algorithm. Must be supported and not
|
||||
* {@code null}.
|
||||
*
|
||||
* @return The expected byte array length for the signature.
|
||||
*
|
||||
* @throws JwtException If the algorithm is not supported.
|
||||
*/
|
||||
public static int getSignatureByteArrayLength(final SignatureAlgorithm alg)
|
||||
throws JwtException {
|
||||
|
||||
switch (alg) {
|
||||
case ES256: return 64;
|
||||
case ES384: return 96;
|
||||
case ES512: return 132;
|
||||
default:
|
||||
throw new JwtException("Unsupported Algorithm: " + alg.name());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Transcodes the JCA ASN.1/DER-encoded signature into the concatenated
|
||||
* R + S format expected by ECDSA JWS.
|
||||
*
|
||||
* @param derSignature The ASN1./DER-encoded. Must not be {@code null}.
|
||||
* @param outputLength The expected length of the ECDSA JWS signature.
|
||||
*
|
||||
* @return The ECDSA JWS encoded signature.
|
||||
*
|
||||
* @throws JwtException If the ASN.1/DER signature format is invalid.
|
||||
*/
|
||||
public static byte[] transcodeSignatureToConcat(final byte[] derSignature, int outputLength)
|
||||
throws JwtException {
|
||||
|
||||
if (derSignature.length < 8 || derSignature[0] != 48) {
|
||||
throw new JwtException("Invalid ECDSA signature format");
|
||||
}
|
||||
|
||||
int offset;
|
||||
if (derSignature[1] > 0) {
|
||||
offset = 2;
|
||||
} else if (derSignature[1] == (byte) 0x81) {
|
||||
offset = 3;
|
||||
} else {
|
||||
throw new JwtException("Invalid ECDSA signature format");
|
||||
}
|
||||
|
||||
byte rLength = derSignature[offset + 1];
|
||||
|
||||
int i = rLength;
|
||||
while ((i > 0)
|
||||
&& (derSignature[(offset + 2 + rLength) - i] == 0))
|
||||
i--;
|
||||
|
||||
byte sLength = derSignature[offset + 2 + rLength + 1];
|
||||
|
||||
int j = sLength;
|
||||
while ((j > 0)
|
||||
&& (derSignature[(offset + 2 + rLength + 2 + sLength) - j] == 0))
|
||||
j--;
|
||||
|
||||
int rawLen = Math.max(i, j);
|
||||
rawLen = Math.max(rawLen, outputLength / 2);
|
||||
|
||||
if ((derSignature[offset - 1] & 0xff) != derSignature.length - offset
|
||||
|| (derSignature[offset - 1] & 0xff) != 2 + rLength + 2 + sLength
|
||||
|| derSignature[offset] != 2
|
||||
|| derSignature[offset + 2 + rLength] != 2) {
|
||||
throw new JwtException("Invalid ECDSA signature format");
|
||||
}
|
||||
|
||||
final byte[] concatSignature = new byte[2 * rawLen];
|
||||
|
||||
System.arraycopy(derSignature, (offset + 2 + rLength) - i, concatSignature, rawLen - i, i);
|
||||
System.arraycopy(derSignature, (offset + 2 + rLength + 2 + sLength) - j, concatSignature, 2 * rawLen - j, j);
|
||||
|
||||
return concatSignature;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Transcodes the ECDSA JWS signature into ASN.1/DER format for use by
|
||||
* the JCA verifier.
|
||||
*
|
||||
* @param jwsSignature The JWS signature, consisting of the
|
||||
* concatenated R and S values. Must not be
|
||||
* {@code null}.
|
||||
*
|
||||
* @return The ASN.1/DER encoded signature.
|
||||
*
|
||||
* @throws JwtException If the ECDSA JWS signature format is invalid.
|
||||
*/
|
||||
public static byte[] transcodeSignatureToDER(byte[] jwsSignature)
|
||||
throws JwtException {
|
||||
|
||||
int rawLen = jwsSignature.length / 2;
|
||||
|
||||
int i = rawLen;
|
||||
|
||||
while((i > 0)
|
||||
&& (jwsSignature[rawLen - i] == 0))
|
||||
i--;
|
||||
|
||||
int j = i;
|
||||
|
||||
if (jwsSignature[rawLen - i] < 0) {
|
||||
j += 1;
|
||||
}
|
||||
|
||||
int k = rawLen;
|
||||
|
||||
while ((k > 0)
|
||||
&& (jwsSignature[2 * rawLen - k] == 0))
|
||||
k--;
|
||||
|
||||
int l = k;
|
||||
|
||||
if (jwsSignature[2 * rawLen - k] < 0) {
|
||||
l += 1;
|
||||
}
|
||||
|
||||
int len = 2 + j + 2 + l;
|
||||
|
||||
if (len > 255) {
|
||||
throw new JwtException("Invalid ECDSA signature format");
|
||||
}
|
||||
|
||||
int offset;
|
||||
|
||||
final byte derSignature[];
|
||||
|
||||
if (len < 128) {
|
||||
derSignature = new byte[2 + 2 + j + 2 + l];
|
||||
offset = 1;
|
||||
} else {
|
||||
derSignature = new byte[3 + 2 + j + 2 + l];
|
||||
derSignature[1] = (byte) 0x81;
|
||||
offset = 2;
|
||||
}
|
||||
|
||||
derSignature[0] = 48;
|
||||
derSignature[offset++] = (byte) len;
|
||||
derSignature[offset++] = 2;
|
||||
derSignature[offset++] = (byte) j;
|
||||
|
||||
System.arraycopy(jwsSignature, rawLen - i, derSignature, (offset + j) - i, i);
|
||||
|
||||
offset += j;
|
||||
|
||||
derSignature[offset++] = 2;
|
||||
derSignature[offset++] = (byte) l;
|
||||
|
||||
System.arraycopy(jwsSignature, 2 * rawLen - k, derSignature, (offset + l) - k, k);
|
||||
|
||||
return derSignature;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,16 +15,16 @@
|
|||
*/
|
||||
package io.jsonwebtoken.impl.crypto;
|
||||
|
||||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
import io.jsonwebtoken.SignatureException;
|
||||
import io.jsonwebtoken.lang.Assert;
|
||||
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.Key;
|
||||
import java.security.PublicKey;
|
||||
import java.security.Signature;
|
||||
import java.security.interfaces.ECPublicKey;
|
||||
|
||||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
import io.jsonwebtoken.SignatureException;
|
||||
import io.jsonwebtoken.lang.Assert;
|
||||
|
||||
public class EllipticCurveSignatureValidator extends EllipticCurveProvider implements SignatureValidator {
|
||||
|
||||
private static final String EC_PUBLIC_KEY_REQD_MSG =
|
||||
|
@ -40,7 +40,16 @@ public class EllipticCurveSignatureValidator extends EllipticCurveProvider imple
|
|||
Signature sig = createSignatureInstance();
|
||||
PublicKey publicKey = (PublicKey) key;
|
||||
try {
|
||||
return doVerify(sig, publicKey, data, signature);
|
||||
int expectedSize = getSignatureByteArrayLength(alg);
|
||||
/**
|
||||
*
|
||||
* If the expected size is not valid for JOSE, fall back to ASN.1 DER signature.
|
||||
* This fallback is for backwards compatibility ONLY (to support tokens generated by previous versions of jjwt)
|
||||
* and backwards compatibility will possibly be removed in a future version of this library.
|
||||
*
|
||||
* **/
|
||||
byte[] derSignature = expectedSize != signature.length && signature[0] == 0x30 ? signature : EllipticCurveProvider.transcodeSignatureToDER(signature);
|
||||
return doVerify(sig, publicKey, data, derSignature);
|
||||
} catch (Exception e) {
|
||||
String msg = "Unable to verify Elliptic Curve signature using configured ECPublicKey. " + e.getMessage();
|
||||
throw new SignatureException(msg, e);
|
||||
|
|
|
@ -15,15 +15,16 @@
|
|||
*/
|
||||
package io.jsonwebtoken.impl.crypto;
|
||||
|
||||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
import io.jsonwebtoken.SignatureException;
|
||||
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.Key;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.Signature;
|
||||
import java.security.interfaces.ECPrivateKey;
|
||||
|
||||
import io.jsonwebtoken.JwtException;
|
||||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
import io.jsonwebtoken.SignatureException;
|
||||
|
||||
public class EllipticCurveSigner extends EllipticCurveProvider implements Signer {
|
||||
|
||||
public EllipticCurveSigner(SignatureAlgorithm alg, Key key) {
|
||||
|
@ -43,14 +44,16 @@ public class EllipticCurveSigner extends EllipticCurveProvider implements Signer
|
|||
throw new SignatureException("Invalid Elliptic Curve PrivateKey. " + e.getMessage(), e);
|
||||
} catch (java.security.SignatureException e) {
|
||||
throw new SignatureException("Unable to calculate signature using Elliptic Curve PrivateKey. " + e.getMessage(), e);
|
||||
} catch (JwtException e) {
|
||||
throw new SignatureException("Unable to convert signature to JOSE format. " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
protected byte[] doSign(byte[] data) throws InvalidKeyException, java.security.SignatureException {
|
||||
protected byte[] doSign(byte[] data) throws InvalidKeyException, java.security.SignatureException, JwtException {
|
||||
PrivateKey privateKey = (PrivateKey)key;
|
||||
Signature sig = createSignatureInstance();
|
||||
sig.initSign(privateKey);
|
||||
sig.update(data);
|
||||
return sig.sign();
|
||||
return transcodeSignatureToConcat(sig.sign(), getSignatureByteArrayLength(alg));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -69,7 +69,7 @@ public abstract class MacProvider extends SignatureProvider {
|
|||
*
|
||||
* <table> <thead> <tr> <th>Signature Algorithm</th> <th>Generated Key Size</th> </tr> </thead> <tbody> <tr>
|
||||
* <td>HS256</td> <td>256 bits (32 bytes)</td> </tr> <tr> <td>HS384</td> <td>384 bits (48 bytes)</td> </tr> <tr>
|
||||
* <td>HS512</td> <td>256 bits (64 bytes)</td> </tr> </tbody> </table>
|
||||
* <td>HS512</td> <td>512 bits (64 bytes)</td> </tr> </tbody> </table>
|
||||
*
|
||||
* @param alg the signature algorithm that will be used with the generated key
|
||||
* @param random the secure random number generator used during key generation
|
||||
|
|
|
@ -15,16 +15,16 @@
|
|||
*/
|
||||
package io.jsonwebtoken.impl.crypto;
|
||||
|
||||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
import io.jsonwebtoken.SignatureException;
|
||||
import io.jsonwebtoken.lang.Assert;
|
||||
import io.jsonwebtoken.lang.RuntimeEnvironment;
|
||||
|
||||
import java.security.Key;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.Signature;
|
||||
|
||||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
import io.jsonwebtoken.SignatureException;
|
||||
import io.jsonwebtoken.lang.Assert;
|
||||
import io.jsonwebtoken.lang.RuntimeEnvironment;
|
||||
|
||||
abstract class SignatureProvider {
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
package io.jsonwebtoken.lang;
|
||||
|
||||
/**
|
||||
* @since 0.6
|
||||
*/
|
||||
public final class Arrays {
|
||||
|
||||
//for code coverage
|
||||
private static final Arrays INSTANCE = new Arrays();
|
||||
|
||||
private Arrays(){}
|
||||
|
||||
public static int length(byte[] bytes) {
|
||||
return bytes != null ? bytes.length : 0;
|
||||
}
|
||||
|
||||
public static byte[] clean(byte[] bytes) {
|
||||
return length(bytes) > 0 ? bytes : null;
|
||||
}
|
||||
}
|
|
@ -18,7 +18,12 @@ package io.jsonwebtoken.lang;
|
|||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
public abstract class Assert {
|
||||
public final class Assert {
|
||||
|
||||
//for code coverage
|
||||
private static final Assert INSTANCE = new Assert();
|
||||
|
||||
private Assert(){}
|
||||
|
||||
/**
|
||||
* Assert a boolean expression, throwing <code>IllegalArgumentException</code>
|
||||
|
|
|
@ -21,7 +21,11 @@ import java.lang.reflect.Constructor;
|
|||
/**
|
||||
* @since 0.1
|
||||
*/
|
||||
public class Classes {
|
||||
public final class Classes {
|
||||
|
||||
private static final Classes INSTANCE = new Classes();
|
||||
|
||||
private Classes() {}
|
||||
|
||||
/**
|
||||
* @since 0.1
|
||||
|
@ -79,7 +83,7 @@ public class Classes {
|
|||
|
||||
if (clazz == null) {
|
||||
String msg = "Unable to load class named [" + fqcn + "] from the thread context, current, or " +
|
||||
"system/application ClassLoaders. All heuristics have been exhausted. Class could not be found.";
|
||||
"system/application ClassLoaders. All heuristics have been exhausted. Class could not be found.";
|
||||
|
||||
if (fqcn != null && fqcn.startsWith("com.stormpath.sdk.impl")) {
|
||||
msg += " Have you remembered to include the stormpath-sdk-impl .jar in your runtime classpath?";
|
||||
|
@ -100,7 +104,7 @@ public class Classes {
|
|||
*
|
||||
* @param name the name of the resource to acquire from the classloader(s).
|
||||
* @return the InputStream of the resource found, or <code>null</code> if the resource cannot be found from any
|
||||
* of the three mentioned ClassLoaders.
|
||||
* of the three mentioned ClassLoaders.
|
||||
* @since 0.8
|
||||
*/
|
||||
public static InputStream getResourceAsStream(String name) {
|
||||
|
@ -181,6 +185,7 @@ public class Classes {
|
|||
*/
|
||||
private static interface ClassLoaderAccessor {
|
||||
Class loadClass(String fqcn);
|
||||
|
||||
InputStream getResourceStream(String name);
|
||||
}
|
||||
|
||||
|
|
|
@ -24,7 +24,12 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
public abstract class Collections {
|
||||
public final class Collections {
|
||||
|
||||
//for code coverage
|
||||
private static final Collections INSTANCE = new Collections();
|
||||
|
||||
private Collections(){}
|
||||
|
||||
/**
|
||||
* Return <code>true</code> if the supplied Collection is <code>null</code>
|
||||
|
|
|
@ -20,7 +20,12 @@ import java.io.IOException;
|
|||
import java.lang.reflect.Array;
|
||||
import java.util.Arrays;
|
||||
|
||||
public abstract class Objects {
|
||||
public final class Objects {
|
||||
|
||||
//for code coverage
|
||||
private static final Objects INSTANCE = new Objects();
|
||||
|
||||
private Objects(){}
|
||||
|
||||
private static final int INITIAL_HASH = 7;
|
||||
private static final int MULTIPLIER = 31;
|
||||
|
|
|
@ -19,7 +19,11 @@ import java.security.Provider;
|
|||
import java.security.Security;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class RuntimeEnvironment {
|
||||
public final class RuntimeEnvironment {
|
||||
|
||||
private static final RuntimeEnvironment INSTANCE = new RuntimeEnvironment();
|
||||
|
||||
private RuntimeEnvironment(){}
|
||||
|
||||
private static final String BC_PROVIDER_CLASS_NAME = "org.bouncycastle.jce.provider.BouncyCastleProvider";
|
||||
|
||||
|
|
|
@ -29,7 +29,9 @@ import java.util.Set;
|
|||
import java.util.StringTokenizer;
|
||||
import java.util.TreeSet;
|
||||
|
||||
public abstract class Strings {
|
||||
public final class Strings {
|
||||
|
||||
private static final Strings INSTANCE = new Strings(); //for code coverage
|
||||
|
||||
private static final String FOLDER_SEPARATOR = "/";
|
||||
|
||||
|
@ -43,6 +45,8 @@ public abstract class Strings {
|
|||
|
||||
public static final Charset UTF_8 = Charset.forName("UTF-8");
|
||||
|
||||
private Strings(){}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// General convenience methods for working with Strings
|
||||
//---------------------------------------------------------------------
|
||||
|
|
|
@ -23,12 +23,13 @@ package io.jsonwebtoken.lang;
|
|||
*/
|
||||
public class UnknownClassException extends RuntimeException {
|
||||
|
||||
/*
|
||||
/**
|
||||
* Creates a new UnknownClassException.
|
||||
*/
|
||||
*
|
||||
public UnknownClassException() {
|
||||
super();
|
||||
}
|
||||
}*/
|
||||
|
||||
/**
|
||||
* Constructs a new UnknownClassException.
|
||||
|
@ -39,11 +40,11 @@ public class UnknownClassException extends RuntimeException {
|
|||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
/*
|
||||
* Constructs a new UnknownClassException.
|
||||
*
|
||||
* @param cause the underlying Throwable that caused this exception to be thrown.
|
||||
*/
|
||||
*
|
||||
public UnknownClassException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
@ -53,9 +54,10 @@ public class UnknownClassException extends RuntimeException {
|
|||
*
|
||||
* @param message the reason for the exception
|
||||
* @param cause the underlying Throwable that caused this exception to be thrown.
|
||||
*/
|
||||
*
|
||||
public UnknownClassException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
|
@ -15,6 +15,8 @@
|
|||
*/
|
||||
package io.jsonwebtoken
|
||||
|
||||
import io.jsonwebtoken.impl.DefaultClock
|
||||
import io.jsonwebtoken.impl.FixedClock
|
||||
import io.jsonwebtoken.impl.TextCodec
|
||||
import org.junit.Test
|
||||
|
||||
|
@ -1392,4 +1394,39 @@ class JwtParserTest {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseClockManipulationWithFixedClock() {
|
||||
def then = System.currentTimeMillis() - 1000
|
||||
Date expiry = new Date(then)
|
||||
Date beforeExpiry = new Date(then - 1000)
|
||||
|
||||
String compact = Jwts.builder().setSubject('Joe').setExpiration(expiry).compact()
|
||||
|
||||
Jwts.parser().setClock(new FixedClock(beforeExpiry)).parse(compact)
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseClockManipulationWithNullClock() {
|
||||
JwtParser parser = Jwts.parser();
|
||||
try {
|
||||
parser.setClock(null)
|
||||
fail()
|
||||
} catch (IllegalArgumentException expected) {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseClockManipulationWithDefaultClock() {
|
||||
Date expiry = new Date(System.currentTimeMillis() - 1000)
|
||||
|
||||
String compact = Jwts.builder().setSubject('Joe').setExpiration(expiry).compact()
|
||||
|
||||
try {
|
||||
Jwts.parser().setClock(new DefaultClock()).parse(compact)
|
||||
fail()
|
||||
} catch (ExpiredJwtException e) {
|
||||
assertTrue e.getMessage().startsWith('JWT expired at ')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
*/
|
||||
package io.jsonwebtoken.impl
|
||||
|
||||
import io.jsonwebtoken.Header
|
||||
import org.junit.Test
|
||||
import static org.junit.Assert.*
|
||||
|
||||
|
@ -37,4 +38,18 @@ class DefaultHeaderTest {
|
|||
h.setContentType('bar')
|
||||
assertEquals h.getContentType(), 'bar'
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetCompressionAlgorithm() {
|
||||
def h = new DefaultHeader()
|
||||
h.setCompressionAlgorithm("DEF")
|
||||
assertEquals "DEF", h.getCompressionAlgorithm()
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBackwardsCompatibleCompressionHeader() {
|
||||
def h = new DefaultHeader()
|
||||
h.put(Header.DEPRECATED_COMPRESSION_ALGORITHM, "DEF")
|
||||
assertEquals "DEF", h.getCompressionAlgorithm()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -279,4 +279,25 @@ class DefaultJwtBuilderTest {
|
|||
assertNull b.claims
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetNullExpirationWithNullClaims() {
|
||||
def b = new DefaultJwtBuilder()
|
||||
b.setExpiration(null)
|
||||
assertNull b.claims
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetNullNotBeforeWithNullClaims() {
|
||||
def b = new DefaultJwtBuilder()
|
||||
b.setNotBefore(null)
|
||||
assertNull b.claims
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetNullIssuedAtWithNullClaims() {
|
||||
def b = new DefaultJwtBuilder()
|
||||
b.setIssuedAt(null)
|
||||
assertNull b.claims
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
package io.jsonwebtoken.impl
|
||||
|
||||
import org.junit.Test
|
||||
import static org.junit.Assert.*
|
||||
|
||||
class FixedClockTest {
|
||||
|
||||
@Test
|
||||
void testFixedClockDefaultConstructor() {
|
||||
|
||||
def clock = new FixedClock()
|
||||
|
||||
def date1 = clock.now()
|
||||
Thread.sleep(100)
|
||||
def date2 = clock.now()
|
||||
|
||||
assertSame date1, date2
|
||||
}
|
||||
}
|
|
@ -15,18 +15,24 @@
|
|||
*/
|
||||
package io.jsonwebtoken.impl.crypto
|
||||
|
||||
import io.jsonwebtoken.JwtException
|
||||
import io.jsonwebtoken.SignatureAlgorithm
|
||||
import io.jsonwebtoken.SignatureException
|
||||
|
||||
import java.security.InvalidKeyException
|
||||
import java.security.PublicKey
|
||||
import java.security.Signature
|
||||
|
||||
import io.jsonwebtoken.impl.TextCodec
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
||||
import org.junit.Test
|
||||
|
||||
import java.security.*
|
||||
import java.security.spec.X509EncodedKeySpec
|
||||
|
||||
import static org.junit.Assert.*
|
||||
|
||||
class EllipticCurveSignatureValidatorTest {
|
||||
|
||||
static {
|
||||
Security.addProvider(new BouncyCastleProvider())
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDoVerifyWithInvalidKeyException() {
|
||||
|
||||
|
@ -53,4 +59,146 @@ class EllipticCurveSignatureValidatorTest {
|
|||
assertSame se.cause, ex
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void ecdsaSignatureComplianceTest() {
|
||||
def fact = KeyFactory.getInstance("ECDSA", "BC");
|
||||
def publicKey = "MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQASisgweVL1tAtIvfmpoqvdXF8sPKTV9YTKNxBwkdkm+/auh4pR8TbaIfsEzcsGUVv61DFNFXb0ozJfurQ59G2XcgAn3vROlSSnpbIvuhKrzL5jwWDTaYa5tVF1Zjwia/5HUhKBkcPuWGXg05nMjWhZfCuEetzMLoGcHmtvabugFrqsAg="
|
||||
def pub = fact.generatePublic(new X509EncodedKeySpec(TextCodec.BASE64.decode(publicKey)))
|
||||
def v = new EllipticCurveSignatureValidator(SignatureAlgorithm.ES512, pub)
|
||||
def verifier = { token ->
|
||||
def signatureStart = token.lastIndexOf('.')
|
||||
def withoutSignature = token.substring(0, signatureStart)
|
||||
def signature = token.substring(signatureStart + 1)
|
||||
assert v.isValid(withoutSignature.getBytes("US-ASCII"), TextCodec.BASE64URL.decode(signature)), "Signature do not match that of other implementations"
|
||||
}
|
||||
//Test verification for token created using https://github.com/auth0/node-jsonwebtoken/tree/v7.0.1
|
||||
verifier("eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJ0ZXN0IjoidGVzdCIsImlhdCI6MTQ2NzA2NTgyN30.Aab4x7HNRzetjgZ88AMGdYV2Ml7kzFbl8Ql2zXvBores7iRqm2nK6810ANpVo5okhHa82MQf2Q_Zn4tFyLDR9z4GAcKFdcAtopxq1h8X58qBWgNOc0Bn40SsgUc8wOX4rFohUCzEtnUREePsvc9EfXjjAH78WD2nq4tn-N94vf14SncQ")
|
||||
//Test verification for token created using https://github.com/jwt/ruby-jwt/tree/v1.5.4
|
||||
verifier("eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzUxMiJ9.eyJ0ZXN0IjoidGVzdCJ9.AV26tERbSEwcoDGshneZmhokg-tAKUk0uQBoHBohveEd51D5f6EIs6cskkgwtfzs4qAGfx2rYxqQXr7LTXCNquKiAJNkTIKVddbPfped3_TQtmHZTmMNiqmWjiFj7Y9eTPMMRRu26w4gD1a8EQcBF-7UGgeH4L_1CwHJWAXGbtu7uMUn")
|
||||
}
|
||||
|
||||
@Test
|
||||
void legacySignatureCompatTest() {
|
||||
def withoutSignature = "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJ0ZXN0IjoidGVzdCIsImlhdCI6MTQ2NzA2NTgyN30"
|
||||
def keypair = EllipticCurveProvider.generateKeyPair()
|
||||
def signature = Signature.getInstance(SignatureAlgorithm.ES512.jcaName, "BC")
|
||||
def data = withoutSignature.getBytes("US-ASCII")
|
||||
signature.initSign(keypair.private)
|
||||
signature.update(data)
|
||||
def signed = signature.sign()
|
||||
assert new EllipticCurveSignatureValidator(SignatureAlgorithm.ES512, keypair.public).isValid(data, signed)
|
||||
}
|
||||
|
||||
@Test
|
||||
void invalidAlgorithmTest() {
|
||||
def invalidAlgorithm = SignatureAlgorithm.HS256
|
||||
try {
|
||||
EllipticCurveProvider.getSignatureByteArrayLength(invalidAlgorithm)
|
||||
fail()
|
||||
} catch (JwtException e) {
|
||||
assertEquals e.message, 'Unsupported Algorithm: ' + invalidAlgorithm.name()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void invalidECDSASignatureFormatTest() {
|
||||
try {
|
||||
def signature = new byte[257]
|
||||
SignatureProvider.DEFAULT_SECURE_RANDOM.nextBytes(signature)
|
||||
EllipticCurveProvider.transcodeSignatureToDER(signature)
|
||||
fail()
|
||||
} catch (JwtException e) {
|
||||
assertEquals e.message, 'Invalid ECDSA signature format'
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void invalidDERSignatureToJoseFormatTest() {
|
||||
def verify = { signature ->
|
||||
try {
|
||||
EllipticCurveProvider.transcodeSignatureToConcat(signature, 132)
|
||||
fail()
|
||||
} catch (JwtException e) {
|
||||
assertEquals e.message, 'Invalid ECDSA signature format'
|
||||
}
|
||||
}
|
||||
def signature = new byte[257]
|
||||
SignatureProvider.DEFAULT_SECURE_RANDOM.nextBytes(signature)
|
||||
//invalid type
|
||||
signature[0] = 34
|
||||
verify(signature)
|
||||
def shortSignature = new byte[7]
|
||||
SignatureProvider.DEFAULT_SECURE_RANDOM.nextBytes(shortSignature)
|
||||
verify(shortSignature)
|
||||
signature[0] = 48
|
||||
// signature[1] = 0x81
|
||||
signature[1] = -10
|
||||
verify(signature)
|
||||
}
|
||||
|
||||
@Test
|
||||
void edgeCaseSignatureLengthTest() {
|
||||
def signature = new byte[1]
|
||||
EllipticCurveProvider.transcodeSignatureToDER(signature)
|
||||
}
|
||||
|
||||
@Test
|
||||
void edgeCaseSignatureToConcatLengthTest() {
|
||||
try {
|
||||
def signature = TextCodec.BASE64.decode("MIEAAGg3OVb/ZeX12cYrhK3c07TsMKo7Kc6SiqW++4CAZWCX72DkZPGTdCv2duqlupsnZL53hiG3rfdOLj8drndCU+KHGrn5EotCATdMSLCXJSMMJoHMM/ZPG+QOHHPlOWnAvpC1v4lJb32WxMFNz1VAIWrl9Aa6RPG1GcjCTScKjvEE")
|
||||
EllipticCurveProvider.transcodeSignatureToConcat(signature, 132)
|
||||
fail()
|
||||
} catch (JwtException e) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void edgeCaseSignatureToConcatInvalidSignatureTest() {
|
||||
try {
|
||||
def signature = TextCodec.BASE64.decode("MIGBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
|
||||
EllipticCurveProvider.transcodeSignatureToConcat(signature, 132)
|
||||
fail()
|
||||
} catch (JwtException e) {
|
||||
assertEquals e.message, 'Invalid ECDSA signature format'
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void edgeCaseSignatureToConcatInvalidSignatureBranchTest() {
|
||||
try {
|
||||
def signature = TextCodec.BASE64.decode("MIGBAD4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
|
||||
EllipticCurveProvider.transcodeSignatureToConcat(signature, 132)
|
||||
fail()
|
||||
} catch (JwtException e) {
|
||||
assertEquals e.message, 'Invalid ECDSA signature format'
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void edgeCaseSignatureToConcatInvalidSignatureBranch2Test() {
|
||||
try {
|
||||
def signature = TextCodec.BASE64.decode("MIGBAj4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
|
||||
EllipticCurveProvider.transcodeSignatureToConcat(signature, 132)
|
||||
fail()
|
||||
} catch (JwtException e) {
|
||||
assertEquals e.message, 'Invalid ECDSA signature format'
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void verifySwarmTest() {
|
||||
for (SignatureAlgorithm algorithm : [SignatureAlgorithm.ES256, SignatureAlgorithm.ES384, SignatureAlgorithm.ES512]) {
|
||||
int i = 0
|
||||
while(i < 10) {
|
||||
i++
|
||||
def withoutSignature = "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJ0ZXN0IjoidGVzdCIsImlhdCI6MTQ2NzA2NTgyN30"
|
||||
def keypair = EllipticCurveProvider.generateKeyPair()
|
||||
def data = withoutSignature.getBytes("US-ASCII")
|
||||
def signature = new EllipticCurveSigner(algorithm, keypair.private).sign(data)
|
||||
assert new EllipticCurveSignatureValidator(algorithm, keypair.public).isValid(data, signature)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,15 +15,16 @@
|
|||
*/
|
||||
package io.jsonwebtoken.impl.crypto
|
||||
|
||||
import io.jsonwebtoken.JwtException
|
||||
import io.jsonwebtoken.SignatureAlgorithm
|
||||
import io.jsonwebtoken.SignatureException
|
||||
import org.junit.Test
|
||||
|
||||
import java.security.InvalidKeyException
|
||||
import java.security.KeyPair
|
||||
import java.security.PrivateKey
|
||||
import java.security.PublicKey
|
||||
|
||||
import org.junit.Test
|
||||
import static org.junit.Assert.*
|
||||
|
||||
class EllipticCurveSignerTest {
|
||||
|
@ -79,6 +80,35 @@ class EllipticCurveSignerTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDoSignWithJoseSignatureFormatException() {
|
||||
|
||||
KeyPair kp = EllipticCurveProvider.generateKeyPair()
|
||||
PublicKey publicKey = kp.getPublic();
|
||||
PrivateKey privateKey = kp.getPrivate();
|
||||
|
||||
String msg = 'foo'
|
||||
final JwtException ex = new JwtException(msg)
|
||||
|
||||
def signer = new EllipticCurveSigner(SignatureAlgorithm.ES256, privateKey) {
|
||||
@Override
|
||||
protected byte[] doSign(byte[] data) throws InvalidKeyException, java.security.SignatureException, JwtException {
|
||||
throw ex
|
||||
}
|
||||
}
|
||||
|
||||
byte[] bytes = new byte[16]
|
||||
SignatureProvider.DEFAULT_SECURE_RANDOM.nextBytes(bytes)
|
||||
|
||||
try {
|
||||
signer.sign(bytes)
|
||||
fail();
|
||||
} catch (SignatureException se) {
|
||||
assertEquals se.message, 'Unable to convert signature to JOSE format. ' + msg
|
||||
assertSame se.cause, ex
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDoSignWithJdkSignatureException() {
|
||||
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
package io.jsonwebtoken.lang
|
||||
|
||||
import org.junit.Test
|
||||
|
||||
import static org.junit.Assert.*
|
||||
|
||||
class StringsTest {
|
||||
|
||||
@Test
|
||||
void testHasText() {
|
||||
assertFalse Strings.hasText(null)
|
||||
assertFalse Strings.hasText("")
|
||||
assertFalse Strings.hasText(" ")
|
||||
assertTrue Strings.hasText(" foo ");
|
||||
assertTrue Strings.hasText("foo")
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue