From f8aff1de0e8085426739501c68277a1c9dc27f17 Mon Sep 17 00:00:00 2001 From: Les Hazlewood Date: Fri, 26 Sep 2014 19:52:50 -0700 Subject: [PATCH 1/9] Initial implementation using a JwtHandler approach with additional convenience methods --- src/main/java/io/jsonwebtoken/JwtHandler.java | 68 +++++++ .../io/jsonwebtoken/JwtHandlerAdapter.java | 52 +++++ src/main/java/io/jsonwebtoken/JwtParser.java | 189 +++++++++++++++++- .../jsonwebtoken/UnsupportedJwtException.java | 36 ++++ .../jsonwebtoken/impl/DefaultJwtParser.java | 53 +++++ 5 files changed, 392 insertions(+), 6 deletions(-) create mode 100644 src/main/java/io/jsonwebtoken/JwtHandler.java create mode 100644 src/main/java/io/jsonwebtoken/JwtHandlerAdapter.java create mode 100644 src/main/java/io/jsonwebtoken/UnsupportedJwtException.java diff --git a/src/main/java/io/jsonwebtoken/JwtHandler.java b/src/main/java/io/jsonwebtoken/JwtHandler.java new file mode 100644 index 00000000..0e23f833 --- /dev/null +++ b/src/main/java/io/jsonwebtoken/JwtHandler.java @@ -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 the type of object to return to the parser caller after handling the parsed JWT. + * @since 0.2 + */ +public interface JwtHandler { + + /** + * 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 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 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. + * + *

This method will only be invoked if the cryptographic signature can be successfully verified.

+ * + * @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 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. + * + *

This method will only be invoked if the cryptographic signature can be successfully verified.

+ * + * @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 jws); + +} diff --git a/src/main/java/io/jsonwebtoken/JwtHandlerAdapter.java b/src/main/java/io/jsonwebtoken/JwtHandlerAdapter.java new file mode 100644 index 00000000..74948837 --- /dev/null +++ b/src/main/java/io/jsonwebtoken/JwtHandlerAdapter.java @@ -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 Adapter 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. + * + *

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.

+ * + * @param the type of object to return to the parser caller after handling the parsed JWT. + * @since 0.2 + */ +public class JwtHandlerAdapter implements JwtHandler { + + @Override + public T onPlaintextJwt(Jwt jwt) { + throw new UnsupportedJwtException("Unsigned plaintext JWTs are not supported."); + } + + @Override + public T onClaimsJwt(Jwt jwt) { + throw new UnsupportedJwtException("Unsigned Claims JWTs are not supported."); + } + + @Override + public T onPlaintextJws(Jws jws) { + throw new UnsupportedJwtException("Signed plaintext JWSs are not supported."); + } + + @Override + public T onClaimsJws(Jws jws) { + throw new UnsupportedJwtException("Signed Claims JWSs are not supported."); + } +} diff --git a/src/main/java/io/jsonwebtoken/JwtParser.java b/src/main/java/io/jsonwebtoken/JwtParser.java index cc4d00da..9b91ad33 100644 --- a/src/main/java/io/jsonwebtoken/JwtParser.java +++ b/src/main/java/io/jsonwebtoken/JwtParser.java @@ -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. * + *

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.

+ * * @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. + * + *

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.

* * @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. + * + *

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:

+ * + *
+     * String compactJwt = request.getParameter("jwt"); //we are confident this is a signed JWS
+     *
+     * String subject = Jwts.parser().setSigningKey(key).parse(compactJwt, new JwtHandlerAdapter<String>() {
+     *     @Override
+     *     public String onClaimsJws(Jws<Claims> jws) {
+     *         return jws.getBody().getSubject();
+     *     }
+     * });
+     * 
+ * + *

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:

+ * + *
    + *
  • {@link #parsePlaintextJwt(String)}
  • + *
  • {@link #parseClaimsJwt(String)}
  • + *
  • {@link #parsePlaintextJws(String)}
  • + *
  • {@link #parseClaimsJws(String)}
  • + *
+ * + * @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 parse(String jwt, JwtHandler 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. + * + *

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.

+ * + *

If the compact string presented does not reflect an unsigned plaintext JWT with non-JSON string body, + * an {@link UnsupportedJwtException} will be thrown.

+ * + * @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 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. + * + *

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.

+ * + *

If the compact string presented does not reflect an unsigned Claims JWT, an + * {@link UnsupportedJwtException} will be thrown.

+ * + * @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 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. + * + *

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.

+ * + *

If the compact string presented does not reflect a plaintext JWS, an {@link UnsupportedJwtException} + * will be thrown.

+ * + * @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 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. + * + *

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.

+ * + *

If the compact string presented does not reflect a Claims JWS, an {@link UnsupportedJwtException} will be + * thrown.

+ * + * @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 parseClaimsJws(String claimsJws) + throws UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException; } diff --git a/src/main/java/io/jsonwebtoken/UnsupportedJwtException.java b/src/main/java/io/jsonwebtoken/UnsupportedJwtException.java new file mode 100644 index 00000000..3735f7d4 --- /dev/null +++ b/src/main/java/io/jsonwebtoken/UnsupportedJwtException.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2014 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken; + +/** + * Exception thrown when receiving a JWT in a particular format/configuration that does not match the format expected + * by the application. + * + *

For example, this exception would be thrown if parsing an unsigned plaintext JWT when the application + * requires a cryptographically signed Claims JWS instead.

+ * + * @since 0.2 + */ +public class UnsupportedJwtException extends JwtException { + + public UnsupportedJwtException(String message) { + super(message); + } + + public UnsupportedJwtException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java b/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java index db6b4e31..91558a97 100644 --- a/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java +++ b/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java @@ -18,8 +18,11 @@ 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; @@ -218,6 +221,56 @@ public class DefaultJwtParser implements JwtParser { } } + @Override + public T parse(String compact, JwtHandler 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); + } else { + return handler.onPlaintextJws(jws); + } + } else { + Object body = jwt.getBody(); + if (body instanceof Claims) { + return handler.onPlaintextJwt(jwt); + } else { + return handler.onClaimsJwt(jwt); + } + } + } + + @Override + public Jwt parsePlaintextJwt(String plaintextJwt) { + return parse(plaintextJwt, new JwtHandlerAdapter>() { + @Override + public Jwt onPlaintextJwt(Jwt jwt) { + return jwt; + } + }); + } + + @Override + public Jwt parseClaimsJwt(String claimsJwt) { + return null; + } + + @Override + public Jws parsePlaintextJws(String plaintextJws) { + return null; + } + + @Override + public Jws parseClaimsJws(String claimsJws) { + return null; + } + @SuppressWarnings("unchecked") protected Map readValue(String val) { try { From 52c953655057124a4be81a8187d2b727399cca2b Mon Sep 17 00:00:00 2001 From: Les Hazlewood Date: Fri, 26 Sep 2014 20:06:14 -0700 Subject: [PATCH 2/9] Added missing implementations. Test cases to follow. --- .../jsonwebtoken/impl/DefaultJwtParser.java | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java b/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java index 91558a97..2159e8af 100644 --- a/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java +++ b/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java @@ -258,17 +258,32 @@ public class DefaultJwtParser implements JwtParser { @Override public Jwt parseClaimsJwt(String claimsJwt) { - return null; + return parse(claimsJwt, new JwtHandlerAdapter>() { + @Override + public Jwt onClaimsJwt(Jwt jwt) { + return jwt; + } + }); } @Override public Jws parsePlaintextJws(String plaintextJws) { - return null; + return parse(plaintextJws, new JwtHandlerAdapter>() { + @Override + public Jws onPlaintextJws(Jws jws) { + return jws; + } + }); } @Override public Jws parseClaimsJws(String claimsJws) { - return null; + return parse(claimsJws, new JwtHandlerAdapter>() { + @Override + public Jws onClaimsJws(Jws jws) { + return jws; + } + }); } @SuppressWarnings("unchecked") From 1c207002028964ba50d7ab5bf1f2eacc10149a17 Mon Sep 17 00:00:00 2001 From: Les Hazlewood Date: Fri, 26 Sep 2014 20:19:49 -0700 Subject: [PATCH 3/9] Fixed JDK 8 compile errors --- src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java b/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java index 2159e8af..b674da1f 100644 --- a/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java +++ b/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java @@ -232,16 +232,16 @@ public class DefaultJwtParser implements JwtParser { Jws jws = (Jws)jwt; Object body = jws.getBody(); if (body instanceof Claims) { - return handler.onClaimsJws(jws); + return handler.onClaimsJws((Jws)jws); } else { - return handler.onPlaintextJws(jws); + return handler.onPlaintextJws((Jws)jws); } } else { Object body = jwt.getBody(); if (body instanceof Claims) { - return handler.onPlaintextJwt(jwt); + return handler.onClaimsJwt((Jwt)jwt); } else { - return handler.onClaimsJwt(jwt); + return handler.onPlaintextJwt((Jwt)jwt); } } } From 962e2284285e3a88e172faf320d63de589b01e80 Mon Sep 17 00:00:00 2001 From: Les Hazlewood Date: Fri, 26 Sep 2014 21:55:30 -0700 Subject: [PATCH 4/9] Filling out test cases --- src/main/java/io/jsonwebtoken/Jwts.java | 3 - .../jsonwebtoken/impl/DefaultJwtParser.java | 37 +- .../impl/crypto/AbstractSigner.java | 34 -- .../jsonwebtoken/JwtHandlerAdapterTest.groovy | 67 +++ .../io/jsonwebtoken/JwtParserTest.groovy | 410 ++++++++++++++++++ .../groovy/io/jsonwebtoken/JwtsTest.groovy | 45 +- 6 files changed, 543 insertions(+), 53 deletions(-) delete mode 100644 src/main/java/io/jsonwebtoken/impl/crypto/AbstractSigner.java create mode 100644 src/test/groovy/io/jsonwebtoken/JwtHandlerAdapterTest.groovy create mode 100644 src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy diff --git a/src/main/java/io/jsonwebtoken/Jwts.java b/src/main/java/io/jsonwebtoken/Jwts.java index 982c8f44..9d45e2cf 100644 --- a/src/main/java/io/jsonwebtoken/Jwts.java +++ b/src/main/java/io/jsonwebtoken/Jwts.java @@ -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 claims) { - if (claims == null) { - return claims(); - } return new DefaultClaims(claims); } diff --git a/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java b/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java index b674da1f..b6748cd3 100644 --- a/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java +++ b/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java @@ -27,6 +27,7 @@ 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; @@ -239,9 +240,9 @@ public class DefaultJwtParser implements JwtParser { } else { Object body = jwt.getBody(); if (body instanceof Claims) { - return handler.onClaimsJwt((Jwt)jwt); + return handler.onClaimsJwt((Jwt) jwt); } else { - return handler.onPlaintextJwt((Jwt)jwt); + return handler.onPlaintextJwt((Jwt) jwt); } } } @@ -258,22 +259,30 @@ public class DefaultJwtParser implements JwtParser { @Override public Jwt parseClaimsJwt(String claimsJwt) { - return parse(claimsJwt, new JwtHandlerAdapter>() { - @Override - public Jwt onClaimsJwt(Jwt jwt) { - return jwt; - } - }); + try { + return parse(claimsJwt, new JwtHandlerAdapter>() { + @Override + public Jwt onClaimsJwt(Jwt jwt) { + return jwt; + } + }); + } catch (IllegalArgumentException iae) { + throw new UnsupportedJwtException("Signed JWSs are not supported.", iae); + } } @Override public Jws parsePlaintextJws(String plaintextJws) { - return parse(plaintextJws, new JwtHandlerAdapter>() { - @Override - public Jws onPlaintextJws(Jws jws) { - return jws; - } - }); + try { + return parse(plaintextJws, new JwtHandlerAdapter>() { + @Override + public Jws onPlaintextJws(Jws jws) { + return jws; + } + }); + } catch (IllegalArgumentException iae) { + throw new UnsupportedJwtException("Signed JWSs are not supported.", iae); + } } @Override diff --git a/src/main/java/io/jsonwebtoken/impl/crypto/AbstractSigner.java b/src/main/java/io/jsonwebtoken/impl/crypto/AbstractSigner.java deleted file mode 100644 index 849ab255..00000000 --- a/src/main/java/io/jsonwebtoken/impl/crypto/AbstractSigner.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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.impl.crypto; - -import io.jsonwebtoken.SignatureAlgorithm; -import io.jsonwebtoken.lang.Assert; - -import java.security.Key; - -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; - } -} diff --git a/src/test/groovy/io/jsonwebtoken/JwtHandlerAdapterTest.groovy b/src/test/groovy/io/jsonwebtoken/JwtHandlerAdapterTest.groovy new file mode 100644 index 00000000..27ec062f --- /dev/null +++ b/src/test/groovy/io/jsonwebtoken/JwtHandlerAdapterTest.groovy @@ -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.' + } + } +} diff --git a/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy b/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy new file mode 100644 index 00000000..581d8a68 --- /dev/null +++ b/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy @@ -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 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 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 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 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 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.' + } + } + +} diff --git a/src/test/groovy/io/jsonwebtoken/JwtsTest.groovy b/src/test/groovy/io/jsonwebtoken/JwtsTest.groovy index 44f3f583..3d5c370e 100644 --- a/src/test/groovy/io/jsonwebtoken/JwtsTest.groovy +++ b/src/test/groovy/io/jsonwebtoken/JwtsTest.groovy @@ -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); From 704ea4228694e53a2cde3c933bedc652658d5af5 Mon Sep 17 00:00:00 2001 From: Les Hazlewood Date: Fri, 26 Sep 2014 22:18:32 -0700 Subject: [PATCH 5/9] Update README.md Added release notes/docs around JwtHandler functionality and new convenience parsing methods --- README.md | 80 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/README.md b/README.md index d191181d..b3a9c128 100644 --- a/README.md +++ b/README.md @@ -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`. 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() { + @Override + public Object onPlaintextJwt(Jwt 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 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 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 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>() { + @Override + public Object onPlaintextJws(Jws 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 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 type of the compact JWT string, 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 jwt = Jwts.parser().parsePlaintextJwt(compact); + +//for a known Claims JWT string: +Jwt jwt = Jwts.parser().parseClaimsJwt(compact); + +//for a known signed plaintext JWT (aka a plaintext JWS): +Jws jws = Jwts.parser().setSigningKey(key).parsePlaintextJws(compact); + +//for a known signed Claims JWT (aka a Claims JWS): +Jws jws = Jwts.parser().setSigningKey(key).parseClaimsJws(compact); + +``` + #### Already using an older Jackson dependency? From 5b7cd5e952726ee6ee1990bc04afe1051b7f7309 Mon Sep 17 00:00:00 2001 From: Les Hazlewood Date: Fri, 26 Sep 2014 22:19:45 -0700 Subject: [PATCH 6/9] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b3a9c128..adc46efb 100644 --- a/README.md +++ b/README.md @@ -116,7 +116,7 @@ The following < 0.2 code produced a JWT as expected: 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`. 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. +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 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 From b8d06862adbd56e7b45c457604f196cb6e1421c0 Mon Sep 17 00:00:00 2001 From: Les Hazlewood Date: Fri, 26 Sep 2014 22:20:40 -0700 Subject: [PATCH 7/9] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index adc46efb..d75c3eed 100644 --- a/README.md +++ b/README.md @@ -116,7 +116,7 @@ The following < 0.2 code produced a JWT as expected: 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 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. +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 From e124710c8c89bd339447d438922cdf27ff5d682f Mon Sep 17 00:00:00 2001 From: Les Hazlewood Date: Fri, 26 Sep 2014 22:22:41 -0700 Subject: [PATCH 8/9] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d75c3eed..57217299 100644 --- a/README.md +++ b/README.md @@ -170,7 +170,7 @@ T returnVal = Jwts.parser().setSigningKey(key).parse(plaintextJwt, new JwtHandle ##### Known Type convenience parse methods -If, unlike above, you are confident of the type of the compact JWT string, 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: +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 From 2f2ee03132baa592f0eeb0956d45ccd18abeec27 Mon Sep 17 00:00:00 2001 From: Les Hazlewood Date: Fri, 26 Sep 2014 22:26:36 -0700 Subject: [PATCH 9/9] Update README.md Doc cleanup before the 0.2 release --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 57217299..2bfd3c00 100644 --- a/README.md +++ b/README.md @@ -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;