Merge pull request #5 from jwtk/JwtHandler

Add JwtHandler for JWT/JWS type-specific handling
This commit is contained in:
Les Hazlewood 2014-09-26 22:29:45 -07:00
commit af4bb5a820
10 changed files with 997 additions and 26 deletions

View File

@ -22,7 +22,7 @@ Note: JJWT depends on Jackson 2.x. If you're already using an older version of
## Usage
Most complexity is hidden behind convenient and readable Builder chaining calls. Here's an example:
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:
```java
import io.jsonwebtoken.Jwts;
@ -108,6 +108,86 @@ String compactJwt = Jwts.builder().setSubject("Joe").signWith(HS256, key).compac
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 Object 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 Object 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 Object 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 Object 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 Object 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 Object 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);
```
<a name="olderJackson"></a>
#### Already using an older Jackson dependency?

View File

@ -0,0 +1,68 @@
/*
* 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;
/**
* A JwtHandler is invoked by a {@link io.jsonwebtoken.JwtParser JwtParser} after parsing a JWT to indicate the exact
* type of JWT or JWS parsed.
*
* @param <T> the type of object to return to the parser caller after handling the parsed JWT.
* @since 0.2
*/
public interface JwtHandler<T> {
/**
* This method is invoked when a {@link io.jsonwebtoken.JwtParser JwtParser} determines that the parsed JWT is
* a plaintext JWT. A plaintext JWT has a String (non-JSON) body payload and it is not cryptographically signed.
*
* @param jwt the parsed plaintext JWT
* @return any object to be used after inspecting the JWT, or {@code null} if no return value is necessary.
*/
T onPlaintextJwt(Jwt<Header, String> jwt);
/**
* This method is invoked when a {@link io.jsonwebtoken.JwtParser JwtParser} determines that the parsed JWT is
* a Claims JWT. A Claims JWT has a {@link Claims} body and it is not cryptographically signed.
*
* @param jwt the parsed claims JWT
* @return any object to be used after inspecting the JWT, or {@code null} if no return value is necessary.
*/
T onClaimsJwt(Jwt<Header, Claims> jwt);
/**
* This method is invoked when a {@link io.jsonwebtoken.JwtParser JwtParser} determines that the parsed JWT is
* a plaintext JWS. A plaintext JWS is a JWT with a String (non-JSON) body (payload) that has been
* cryptographically signed.
*
* <p>This method will only be invoked if the cryptographic signature can be successfully verified.</p>
*
* @param jws the parsed plaintext JWS
* @return any object to be used after inspecting the JWS, or {@code null} if no return value is necessary.
*/
T onPlaintextJws(Jws<String> jws);
/**
* This method is invoked when a {@link io.jsonwebtoken.JwtParser JwtParser} determines that the parsed JWT is
* a valid Claims JWS. A Claims JWS is a JWT with a {@link Claims} body that has been cryptographically signed.
*
* <p>This method will only be invoked if the cryptographic signature can be successfully verified.</p>
*
* @param jws the parsed claims JWS
* @return any object to be used after inspecting the JWS, or {@code null} if no return value is necessary.
*/
T onClaimsJws(Jws<Claims> jws);
}

View File

@ -0,0 +1,52 @@
/*
* 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;
/**
* An <a href="http://en.wikipedia.org/wiki/Adapter_pattern">Adapter</a> implementation of the
* {@link JwtHandler} interface that allows for anonymous subclasses to process only the JWT results that are
* known/expected for a particular use case.
*
* <p>All of the methods in this implementation throw exceptions: overridden methods represent
* scenarios expected by calling code in known situations. It would be unexpected to receive a JWS or JWT that did
* not match parsing expectations, so all non-overridden methods throw exceptions to indicate that the JWT
* input was unexpected.</p>
*
* @param <T> the type of object to return to the parser caller after handling the parsed JWT.
* @since 0.2
*/
public class JwtHandlerAdapter<T> implements JwtHandler<T> {
@Override
public T onPlaintextJwt(Jwt<Header, String> jwt) {
throw new UnsupportedJwtException("Unsigned plaintext JWTs are not supported.");
}
@Override
public T onClaimsJwt(Jwt<Header, Claims> jwt) {
throw new UnsupportedJwtException("Unsigned Claims JWTs are not supported.");
}
@Override
public T onPlaintextJws(Jws<String> jws) {
throw new UnsupportedJwtException("Signed plaintext JWSs are not supported.");
}
@Override
public T onClaimsJws(Jws<Claims> jws) {
throw new UnsupportedJwtException("Signed Claims JWSs are not supported.");
}
}

View File

@ -81,6 +81,9 @@ public interface JwtParser {
* Returns {@code true} if the specified JWT compact string represents a signed JWT (aka a 'JWS'), {@code false}
* otherwise.
*
* <p>Note that if you are reasonably sure that the token is signed, it is usually more efficient to attempt to
* parse the token (and catching exceptions if necessary) instead of calling this method first before parsing.</p>
*
* @param jwt the compact serialized JWT to check
* @return {@code true} if the specified JWT compact string represents a signed JWT (aka a 'JWS'), {@code false}
* otherwise.
@ -88,14 +91,188 @@ public interface JwtParser {
boolean isSigned(String jwt);
/**
* Parses the specified compact serialized JWT string based on the builder's current configuration state.
* Parses the specified compact serialized JWT string based on the builder's current configuration state and
* returns the resulting JWT or JWS instance.
*
* <p>This method returns a JWT or JWS based on the parsed string. Because it may be cumbersome to determine if it
* is a JWT or JWS, or if the body/payload is a Claims or String with {@code instanceof} checks, the
* {@link #parse(String, JwtHandler) parse(String,JwtHandler)} method allows for a type-safe callback approach that
* may help reduce code or instanceof checks.</p>
*
* @param jwt the compact serialized JWT to parse
* @return the specified compact serialized JWT string based on the builder's current configuration state.
* @throws MalformedJwtException if the specified JWT was incorrectly constructed (and therefore invalid). Invalid
* JWTs should not be trusted and should be discarded.
* @throws SignatureException if a JWS signature was discovered, but could not be verified. JWTs that fail
* signature validation should not be trusted and should be discarded.
* @throws MalformedJwtException if the specified JWT was incorrectly constructed (and therefore invalid).
* Invalid
* JWTs should not be trusted and should be discarded.
* @throws SignatureException if a JWS signature was discovered, but could not be verified. JWTs that fail
* signature validation should not be trusted and should be discarded.
* @throws IllegalArgumentException if the specified string is {@code null} or empty or only whitespace.
* @see #parse(String, JwtHandler)
* @see #parsePlaintextJwt(String)
* @see #parseClaimsJwt(String)
* @see #parsePlaintextJws(String)
* @see #parseClaimsJws(String)
*/
Jwt parse(String jwt) throws MalformedJwtException, SignatureException;
Jwt parse(String jwt) throws MalformedJwtException, SignatureException, IllegalArgumentException;
/**
* Parses the specified compact serialized JWT string based on the builder's current configuration state and
* invokes the specified {@code handler} with the resulting JWT or JWS instance.
*
* <p>If you are confident of the format of the JWT before parsing, you can create an anonymous subclass using the
* {@link io.jsonwebtoken.JwtHandlerAdapter JwtHandlerAdapter} and override only the methods you know are relevant
* for your use case(s), for example:</p>
*
* <pre>
* String compactJwt = request.getParameter("jwt"); //we are confident this is a signed JWS
*
* String subject = Jwts.parser().setSigningKey(key).parse(compactJwt, new JwtHandlerAdapter&lt;String&gt;() {
* &#64;Override
* public String onClaimsJws(Jws&lt;Claims&gt; jws) {
* return jws.getBody().getSubject();
* }
* });
* </pre>
*
* <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>
*
* <ul>
* <li>{@link #parsePlaintextJwt(String)}</li>
* <li>{@link #parseClaimsJwt(String)}</li>
* <li>{@link #parsePlaintextJws(String)}</li>
* <li>{@link #parseClaimsJws(String)}</li>
* </ul>
*
* @param jwt the compact serialized JWT to parse
* @return the result returned by the {@code JwtHandler}
* @throws MalformedJwtException if the specified JWT was incorrectly constructed (and therefore invalid).
* Invalid JWTs should not be trusted and should be discarded.
* @throws SignatureException if a JWS signature was discovered, but could not be verified. JWTs that fail
* signature validation should not be trusted and should be discarded.
* @throws IllegalArgumentException if the specified string is {@code null} or empty or only whitespace, or if the
* {@code handler} is {@code null}.
* @see #parsePlaintextJwt(String)
* @see #parseClaimsJwt(String)
* @see #parsePlaintextJws(String)
* @see #parseClaimsJws(String)
* @see #parse(String)
* @since 0.2
*/
<T> T parse(String jwt, JwtHandler<T> handler)
throws UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException;
/**
* Parses the specified compact serialized JWT string based on the builder's current configuration state and
* returns
* the resulting unsigned plaintext JWT instance.
*
* <p>This is a convenience method that is usable if you are confident that the compact string argument reflects an
* unsigned plaintext JWT. An unsigned plaintext JWT has a String (non-JSON) body payload and it is not
* cryptographically signed.</p>
*
* <p><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>
*
* @param plaintextJwt a compact serialized unsigned plaintext JWT string.
* @return the {@link Jwt Jwt} instance that reflects the specified compact JWT string.
* @throws UnsupportedJwtException if the {@code plaintextJwt} argument does not represent an unsigned plaintext
* JWT
* @throws MalformedJwtException if the {@code plaintextJwt} string is not a valid JWT
* @throws SignatureException if the {@code plaintextJwt} string is actually a JWS and signature validation
* fails
* @throws IllegalArgumentException if the {@code plaintextJwt} string is {@code null} or empty or only whitespace
* @see #parseClaimsJwt(String)
* @see #parsePlaintextJws(String)
* @see #parseClaimsJws(String)
* @see #parse(String, JwtHandler)
* @see #parse(String)
* @since 0.2
*/
Jwt<Header, String> parsePlaintextJwt(String plaintextJwt)
throws UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException;
/**
* Parses the specified compact serialized JWT string based on the builder's current configuration state and
* returns
* the resulting unsigned plaintext JWT instance.
*
* <p>This is a convenience method that is usable if you are confident that the compact string argument reflects an
* unsigned Claims JWT. An unsigned Claims JWT has a {@link Claims} body and it is not cryptographically
* signed.</p>
*
* <p><b>If the compact string presented does not reflect an unsigned Claims JWT, an
* {@link UnsupportedJwtException} will be thrown.</b></p>
*
* @param claimsJwt a compact serialized unsigned Claims JWT string.
* @return the {@link Jwt Jwt} instance that reflects the specified compact JWT string.
* @throws UnsupportedJwtException if the {@code claimsJwt} argument does not represent an unsigned Claims JWT
* @throws MalformedJwtException if the {@code claimsJwt} string is not a valid JWT
* @throws SignatureException if the {@code claimsJwt} string is actually a JWS and signature validation
* fails
* @throws IllegalArgumentException if the {@code claimsJwt} string is {@code null} or empty or only whitespace
* @see #parsePlaintextJwt(String)
* @see #parsePlaintextJws(String)
* @see #parseClaimsJws(String)
* @see #parse(String, JwtHandler)
* @see #parse(String)
* @since 0.2
*/
Jwt<Header, Claims> parseClaimsJwt(String claimsJwt)
throws UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException;
/**
* Parses the specified compact serialized JWS string based on the builder's current configuration state and
* returns
* the resulting plaintext JWS instance.
*
* <p>This is a convenience method that is usable if you are confident that the compact string argument reflects a
* plaintext JWS. A plaintext JWS is a JWT with a String (non-JSON) body (payload) that has been
* cryptographically signed.</p>
*
* <p><b>If the compact string presented does not reflect a plaintext JWS, an {@link UnsupportedJwtException}
* will be thrown.</b></p>
*
* @param plaintextJws a compact serialized JWS string.
* @return the {@link Jws Jws} instance that reflects the specified compact JWS string.
* @throws UnsupportedJwtException if the {@code plaintextJws} argument does not represent an plaintext JWS
* @throws MalformedJwtException if the {@code plaintextJws} string is not a valid JWS
* @throws SignatureException if the {@code plaintextJws} JWS signature validation fails
* @throws IllegalArgumentException if the {@code plaintextJws} string is {@code null} or empty or only whitespace
* @see #parsePlaintextJwt(String)
* @see #parseClaimsJwt(String)
* @see #parseClaimsJws(String)
* @see #parse(String, JwtHandler)
* @see #parse(String)
* @since 0.2
*/
Jws<String> parsePlaintextJws(String plaintextJws)
throws UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException;
/**
* Parses the specified compact serialized JWS string based on the builder's current configuration state and
* returns
* the resulting Claims JWS instance.
*
* <p>This is a convenience method that is usable if you are confident that the compact string argument reflects a
* Claims JWS. A Claims JWS is a JWT with a {@link Claims} body that has been cryptographically signed.</p>
*
* <p><b>If the compact string presented does not reflect a Claims JWS, an {@link UnsupportedJwtException} will be
* thrown.</b></p>
*
* @param claimsJws a compact serialized Claims JWS string.
* @return the {@link Jws Jws} instance that reflects the specified compact Claims JWS string.
* @throws UnsupportedJwtException if the {@code claimsJws} argument does not represent an Claims JWS
* @throws MalformedJwtException if the {@code claimsJws} string is not a valid JWS
* @throws SignatureException if the {@code claimsJws} JWS signature validation fails
* @throws IllegalArgumentException if the {@code claimsJws} string is {@code null} or empty or only whitespace
* @see #parsePlaintextJwt(String)
* @see #parseClaimsJwt(String)
* @see #parsePlaintextJws(String)
* @see #parse(String, JwtHandler)
* @see #parse(String)
* @since 0.2
*/
Jws<Claims> parseClaimsJws(String claimsJws)
throws UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException;
}

View File

@ -91,9 +91,6 @@ public class Jwts {
* @return a new {@link Claims} instance populated with the specified name/value pairs.
*/
public static Claims claims(Map<String, Object> claims) {
if (claims == null) {
return claims();
}
return new DefaultClaims(claims);
}

View File

@ -13,22 +13,24 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.jsonwebtoken.impl.crypto;
package io.jsonwebtoken;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.lang.Assert;
/**
* Exception thrown when receiving a JWT in a particular format/configuration that does not match the format expected
* by the application.
*
* <p>For example, this exception would be thrown if parsing an unsigned plaintext JWT when the application
* requires a cryptographically signed Claims JWS instead.</p>
*
* @since 0.2
*/
public class UnsupportedJwtException extends JwtException {
import java.security.Key;
public UnsupportedJwtException(String message) {
super(message);
}
public abstract class AbstractSigner implements Signer {
protected final SignatureAlgorithm alg;
protected final Key key;
protected AbstractSigner(SignatureAlgorithm alg, Key key) {
Assert.notNull(alg, "SignatureAlgorithm cannot be null.");
Assert.notNull(key, "Key cannot be null.");
this.alg = alg;
this.key = key;
public UnsupportedJwtException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -18,12 +18,16 @@ package io.jsonwebtoken.impl;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Header;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwt;
import io.jsonwebtoken.JwsHeader;
import io.jsonwebtoken.JwtHandler;
import io.jsonwebtoken.JwtHandlerAdapter;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.SignatureException;
import io.jsonwebtoken.UnsupportedJwtException;
import io.jsonwebtoken.impl.crypto.DefaultJwtSignatureValidator;
import io.jsonwebtoken.impl.crypto.JwtSignatureValidator;
import io.jsonwebtoken.lang.Assert;
@ -218,6 +222,79 @@ public class DefaultJwtParser implements JwtParser {
}
}
@Override
public <T> T parse(String compact, JwtHandler<T> handler) throws MalformedJwtException, SignatureException {
Assert.notNull(handler, "JwtHandler argument cannot be null.");
Assert.hasText(compact, "JWT String argument cannot be null or empty.");
Jwt jwt = parse(compact);
if (jwt instanceof Jws) {
Jws jws = (Jws)jwt;
Object body = jws.getBody();
if (body instanceof Claims) {
return handler.onClaimsJws((Jws<Claims>)jws);
} else {
return handler.onPlaintextJws((Jws<String>)jws);
}
} else {
Object body = jwt.getBody();
if (body instanceof Claims) {
return handler.onClaimsJwt((Jwt<Header, Claims>) jwt);
} else {
return handler.onPlaintextJwt((Jwt<Header, String>) jwt);
}
}
}
@Override
public Jwt<Header, String> parsePlaintextJwt(String plaintextJwt) {
return parse(plaintextJwt, new JwtHandlerAdapter<Jwt<Header, String>>() {
@Override
public Jwt<Header, String> onPlaintextJwt(Jwt<Header, String> jwt) {
return jwt;
}
});
}
@Override
public Jwt<Header, Claims> parseClaimsJwt(String claimsJwt) {
try {
return parse(claimsJwt, new JwtHandlerAdapter<Jwt<Header, Claims>>() {
@Override
public Jwt<Header, Claims> onClaimsJwt(Jwt<Header, Claims> jwt) {
return jwt;
}
});
} catch (IllegalArgumentException iae) {
throw new UnsupportedJwtException("Signed JWSs are not supported.", iae);
}
}
@Override
public Jws<String> parsePlaintextJws(String plaintextJws) {
try {
return parse(plaintextJws, new JwtHandlerAdapter<Jws<String>>() {
@Override
public Jws<String> onPlaintextJws(Jws<String> jws) {
return jws;
}
});
} catch (IllegalArgumentException iae) {
throw new UnsupportedJwtException("Signed JWSs are not supported.", iae);
}
}
@Override
public Jws<Claims> parseClaimsJws(String claimsJws) {
return parse(claimsJws, new JwtHandlerAdapter<Jws<Claims>>() {
@Override
public Jws<Claims> onClaimsJws(Jws<Claims> jws) {
return jws;
}
});
}
@SuppressWarnings("unchecked")
protected Map<String, Object> readValue(String val) {
try {

View File

@ -0,0 +1,67 @@
/*
* 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
import org.testng.annotations.Test
import static org.testng.Assert.*
class JwtHandlerAdapterTest {
@Test
void testOnPlaintextJwt() {
def handler = new JwtHandlerAdapter();
try {
handler.onPlaintextJwt(null)
fail()
} catch (UnsupportedJwtException e) {
assertEquals e.getMessage(), 'Unsigned plaintext JWTs are not supported.'
}
}
@Test
void testOnClaimsJwt() {
def handler = new JwtHandlerAdapter();
try {
handler.onClaimsJwt(null)
fail()
} catch (UnsupportedJwtException e) {
assertEquals e.getMessage(), 'Unsigned Claims JWTs are not supported.'
}
}
@Test
void testOnPlaintextJws() {
def handler = new JwtHandlerAdapter();
try {
handler.onPlaintextJws(null)
fail()
} catch (UnsupportedJwtException e) {
assertEquals e.getMessage(), 'Signed plaintext JWSs are not supported.'
}
}
@Test
void testOnClaimsJws() {
def handler = new JwtHandlerAdapter();
try {
handler.onClaimsJws(null)
fail()
} catch (UnsupportedJwtException e) {
assertEquals e.getMessage(), 'Signed Claims JWSs are not supported.'
}
}
}

View File

@ -0,0 +1,410 @@
/*
* 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
import io.jsonwebtoken.impl.TextCodec
import org.testng.annotations.Test
import javax.crypto.spec.SecretKeySpec
import java.security.SecureRandom
import static org.testng.Assert.*
class JwtParserTest {
private static final SecureRandom random = new SecureRandom(); //doesn't need to be seeded - just testing
protected static byte[] randomKey() {
//create random signing key for testing:
byte[] key = new byte[64];
random.nextBytes(key);
return key;
}
@Test
void testSetDuplicateSigningKeys() {
byte[] keyBytes = randomKey();
SecretKeySpec key = new SecretKeySpec(keyBytes, "HmacSHA256");
String compact = Jwts.builder().setPayload('Hello World!').signWith(SignatureAlgorithm.HS256, keyBytes).compact()
try {
Jwts.parser().setSigningKey(keyBytes).setSigningKey(key).parse(compact)
fail()
} catch (IllegalStateException ise) {
assertEquals ise.getMessage(), 'A key object and key bytes cannot both be specified. Choose either.'
}
}
@Test
void testIsSignedWithNullArgument() {
assertFalse Jwts.parser().isSigned(null)
}
@Test
void testIsSignedWithJunkArgument() {
assertFalse Jwts.parser().isSigned('hello');
}
@Test
void testParseWithJunkArgument() {
String junkPayload = '{;aklsjd;fkajsd;fkjasd;lfkj}'
String bad = TextCodec.BASE64.encode('{"alg":"none"}') + '.' +
TextCodec.BASE64.encode(junkPayload) + '.';
try {
Jwts.parser().parse(bad);
fail()
} catch (MalformedJwtException expected) {
assertEquals expected.getMessage(), 'Unable to read JSON value: ' + junkPayload
}
}
@Test
void testParseJwsWithBadAlgHeader() {
String badAlgorithmName = 'whatever'
String header = "{\"alg\":\"$badAlgorithmName\"}";
String payload = '{"subject":"Joe"}'
String badSig = ";aklsjdf;kajsd;fkjas;dklfj"
String bad = TextCodec.BASE64.encode(header) + '.' +
TextCodec.BASE64.encode(payload) + '.' +
TextCodec.BASE64.encode(badSig);
try {
Jwts.parser().setSigningKey(randomKey()).parse(bad);
fail()
} catch (SignatureException se) {
assertEquals se.getMessage(), "Unsupported signature algorithm '$badAlgorithmName'"
}
}
@Test
void testParseWithInvalidSignature() {
String header = '{"alg":"HS256"}'
String payload = '{"subject":"Joe"}'
String badSig = ";aklsjdf;kajsd;fkjas;dklfj"
String bad = TextCodec.BASE64.encode(header) + '.' +
TextCodec.BASE64.encode(payload) + '.' +
TextCodec.BASE64.encode(badSig);
try {
Jwts.parser().setSigningKey(randomKey()).parse(bad);
fail()
} catch (SignatureException se) {
assertEquals se.getMessage(), 'JWT signature does not match locally computed signature. JWT validity cannot be asserted and should not be trusted.'
}
}
@Test
void testParsePlaintextJwsWithIncorrectAlg() {
String header = '{"alg":"none"}'
String payload = '{"subject":"Joe"}'
String badSig = ";aklsjdf;kajsd;fkjas;dklfj"
String bad = TextCodec.BASE64.encode(header) + '.' +
TextCodec.BASE64.encode(payload) + '.' +
TextCodec.BASE64.encode(badSig);
try {
Jwts.parser().setSigningKey(randomKey()).parse(bad);
fail()
} catch (MalformedJwtException se) {
assertEquals se.getMessage(), 'JWT string has a digest/signature, but the header does not reference a valid signature algorithm.'
}
}
@Test
void testParseWithBase64EncodedSigningKey() {
byte[] key = randomKey();
String base64Encodedkey = TextCodec.BASE64.encode(key);
String payload = 'Hello world!'
String compact = Jwts.builder().setPayload(payload).signWith(SignatureAlgorithm.HS256, base64Encodedkey).compact()
assertTrue Jwts.parser().isSigned(compact)
Jwt<Header,String> jwt = Jwts.parser().setSigningKey(base64Encodedkey).parse(compact)
assertEquals jwt.body, payload
}
// ========================================================================
// parsePlaintextJwt tests
// ========================================================================
@Test
void testParsePlaintextJwt() {
String payload = 'Hello world!'
String compact = Jwts.builder().setPayload(payload).compact()
Jwt<Header,String> jwt = Jwts.parser().parsePlaintextJwt(compact);
assertEquals jwt.getBody(), payload
}
@Test
void testParsePlaintextJwtWithClaimsJwt() {
String compact = Jwts.builder().setSubject('Joe').compact();
try {
Jwts.parser().parsePlaintextJwt(compact);
fail();
} catch (UnsupportedJwtException e) {
assertEquals e.getMessage(), 'Unsigned Claims JWTs are not supported.'
}
}
@Test
void testParsePlaintextJwtWithPlaintextJws() {
String payload = 'Hello world!'
String compact = Jwts.builder().setPayload(payload).signWith(SignatureAlgorithm.HS256, randomKey()).compact()
try {
Jwts.parser().parsePlaintextJws(compact);
fail();
} catch (UnsupportedJwtException e) {
assertEquals e.getMessage(), 'Signed JWSs are not supported.'
}
}
@Test
void testParsePlaintextJwtWithClaimsJws() {
String compact = Jwts.builder().setSubject('Joe').signWith(SignatureAlgorithm.HS256, randomKey()).compact()
try {
Jwts.parser().parsePlaintextJws(compact);
fail();
} catch (UnsupportedJwtException e) {
assertEquals e.getMessage(), 'Signed JWSs are not supported.'
}
}
// ========================================================================
// parseClaimsJwt tests
// ========================================================================
@Test
void testParseClaimsJwt() {
String subject = 'Joe'
String compact = Jwts.builder().setSubject(subject).compact()
Jwt<Header,Claims> jwt = Jwts.parser().parseClaimsJwt(compact);
assertEquals jwt.getBody().getSubject(), subject
}
@Test
void testParseClaimsJwtWithPlaintextJwt() {
String payload = 'Hello world!'
String compact = Jwts.builder().setPayload(payload).compact()
try {
Jwts.parser().parseClaimsJwt(compact);
} catch (UnsupportedJwtException e) {
assertEquals e.getMessage(), 'Unsigned plaintext JWTs are not supported.'
}
}
@Test
void testParseClaimsJwtWithPlaintextJws() {
String payload = 'Hello world!'
String compact = Jwts.builder().setPayload(payload).signWith(SignatureAlgorithm.HS256, randomKey()).compact()
try {
Jwts.parser().parseClaimsJwt(compact);
fail();
} catch (UnsupportedJwtException e) {
assertEquals e.getMessage(), 'Signed JWSs are not supported.'
}
}
@Test
void testParseClaimsJwtWithClaimsJws() {
String compact = Jwts.builder().setSubject('Joe').signWith(SignatureAlgorithm.HS256, randomKey()).compact()
try {
Jwts.parser().parseClaimsJwt(compact);
fail();
} catch (UnsupportedJwtException e) {
assertEquals e.getMessage(), 'Signed JWSs are not supported.'
}
}
// ========================================================================
// parsePlaintextJws tests
// ========================================================================
@Test
void testParsePlaintextJws() {
String payload = 'Hello world!'
byte[] key = randomKey()
String compact = Jwts.builder().setPayload(payload).signWith(SignatureAlgorithm.HS256, key).compact()
Jwt<Header,String> jwt = Jwts.parser().setSigningKey(key).parsePlaintextJws(compact);
assertEquals jwt.getBody(), payload
}
@Test
void testParsePlaintextJwsWithPlaintextJwt() {
String payload = 'Hello world!'
byte[] key = randomKey()
String compact = Jwts.builder().setPayload(payload).compact()
try {
Jwts.parser().setSigningKey(key).parsePlaintextJws(compact);
} catch (UnsupportedJwtException e) {
assertEquals e.getMessage(), 'Unsigned plaintext JWTs are not supported.'
}
}
@Test
void testParsePlaintextJwsWithClaimsJwt() {
String subject = 'Joe'
byte[] key = randomKey()
String compact = Jwts.builder().setSubject(subject).compact()
try {
Jwts.parser().setSigningKey(key).parsePlaintextJws(compact);
} catch (UnsupportedJwtException e) {
assertEquals e.getMessage(), 'Unsigned Claims JWTs are not supported.'
}
}
@Test
void testParsePlaintextJwsWithClaimsJws() {
String subject = 'Joe'
byte[] key = randomKey()
String compact = Jwts.builder().setSubject(subject).signWith(SignatureAlgorithm.HS256, key).compact()
try {
Jwts.parser().setSigningKey(key).parsePlaintextJws(compact);
} catch (UnsupportedJwtException e) {
assertEquals e.getMessage(), 'Signed Claims JWSs are not supported.'
}
}
// ========================================================================
// parseClaimsJws tests
// ========================================================================
@Test
void testParseClaimsJws() {
String sub = 'Joe'
byte[] key = randomKey()
String compact = Jwts.builder().setSubject(sub).signWith(SignatureAlgorithm.HS256, key).compact()
Jwt<Header,Claims> jwt = Jwts.parser().setSigningKey(key).parseClaimsJws(compact);
assertEquals jwt.getBody().getSubject(), sub
}
@Test
void testParseClaimsJwsWithPlaintextJwt() {
String payload = 'Hello world!'
byte[] key = randomKey()
String compact = Jwts.builder().setPayload(payload).compact()
try {
Jwts.parser().setSigningKey(key).parseClaimsJws(compact);
} catch (UnsupportedJwtException e) {
assertEquals e.getMessage(), 'Unsigned plaintext JWTs are not supported.'
}
}
@Test
void testParseClaimsJwsWithClaimsJwt() {
String subject = 'Joe'
byte[] key = randomKey()
String compact = Jwts.builder().setSubject(subject).compact()
try {
Jwts.parser().setSigningKey(key).parseClaimsJws(compact);
} catch (UnsupportedJwtException e) {
assertEquals e.getMessage(), 'Unsigned Claims JWTs are not supported.'
}
}
@Test
void testParseClaimsJwsWithPlaintextJws() {
String subject = 'Joe'
byte[] key = randomKey()
String compact = Jwts.builder().setSubject(subject).signWith(SignatureAlgorithm.HS256, key).compact()
try {
Jwts.parser().setSigningKey(key).parseClaimsJws(compact);
} catch (UnsupportedJwtException e) {
assertEquals e.getMessage(), 'Signed Claims JWSs are not supported.'
}
}
}

View File

@ -15,6 +15,8 @@
*/
package io.jsonwebtoken
import io.jsonwebtoken.impl.DefaultHeader
import io.jsonwebtoken.impl.DefaultJwsHeader
import org.testng.annotations.Test
import java.security.KeyPair
@ -27,6 +29,45 @@ import static org.testng.Assert.*
class JwtsTest {
@Test
void testHeaderWithNoArgs() {
def header = Jwts.header()
assertTrue header instanceof DefaultHeader
}
@Test
void testHeaderWithMapArg() {
def header = Jwts.header([alg: "HS256"])
assertTrue header instanceof DefaultHeader
assertEquals header.alg, 'HS256'
}
@Test
void testJwsHeaderWithNoArgs() {
def header = Jwts.jwsHeader()
assertTrue header instanceof DefaultJwsHeader
}
@Test
void testJwsHeaderWithMapArg() {
def header = Jwts.jwsHeader([alg: "HS256"])
assertTrue header instanceof DefaultJwsHeader
assertEquals header.getAlgorithm(), 'HS256'
}
@Test
void testClaims() {
Claims claims = Jwts.claims()
assertNotNull claims
}
@Test
void testClaimsWithMapArg() {
Claims claims = Jwts.claims([sub: 'Joe'])
assertNotNull claims
assertEquals claims.getSubject(), 'Joe'
}
@Test
void testPlaintextJwtString() {
@ -179,11 +220,11 @@ class JwtsTest {
assertNull claims.getAudience()
}
private Date dateWithOnlySecondPrecision() {
private static Date dateWithOnlySecondPrecision() {
return dateWithOnlySecondPrecision(System.currentTimeMillis());
}
private Date dateWithOnlySecondPrecision(long millis) {
private static Date dateWithOnlySecondPrecision(long millis) {
long seconds = millis / 1000;
long secondOnlyPrecisionMillis = seconds * 1000;
return new Date(secondOnlyPrecisionMillis);