diff --git a/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtBuilder.java b/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtBuilder.java index df2d2215..2c7ab117 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtBuilder.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtBuilder.java @@ -301,7 +301,7 @@ public class DefaultJwtBuilder implements JwtBuilder { } if (payload == null && Collections.isEmpty(claims)) { - throw new IllegalStateException("Either 'payload' or 'claims' must be specified."); + payload = ""; } if (payload != null && !Collections.isEmpty(claims)) { diff --git a/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java b/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java index 497c66b9..57e621c1 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java @@ -257,6 +257,11 @@ public class DefaultJwtParser implements JwtParser { Assert.hasText(jwt, "JWT String argument cannot be null or empty."); + if ("..".equals(jwt)) { + String msg = "JWT string '..' is missing a header."; + throw new MalformedJwtException(msg); + } + String base64UrlEncodedHeader = null; String base64UrlEncodedPayload = null; String base64UrlEncodedDigest = null; @@ -293,9 +298,6 @@ public class DefaultJwtParser implements JwtParser { base64UrlEncodedDigest = sb.toString(); } - if (base64UrlEncodedPayload == null) { - throw new MalformedJwtException("JWT string '" + jwt + "' is missing a body/payload."); - } // =============== Header ================= Header header = null; @@ -317,15 +319,18 @@ public class DefaultJwtParser implements JwtParser { } // =============== Body ================= - byte[] bytes = base64UrlDecoder.decode(base64UrlEncodedPayload); - if (compressionCodec != null) { - bytes = compressionCodec.decompress(bytes); + String payload = ""; // https://github.com/jwtk/jjwt/pull/540 + if (base64UrlEncodedPayload != null) { + byte[] bytes = base64UrlDecoder.decode(base64UrlEncodedPayload); + if (compressionCodec != null) { + bytes = compressionCodec.decompress(bytes); + } + payload = new String(bytes, Strings.UTF_8); } - String payload = new String(bytes, Strings.UTF_8); Claims claims = null; - if (payload.charAt(0) == '{' && payload.charAt(payload.length() - 1) == '}') { //likely to be json, parse it: + if (!payload.isEmpty() && payload.charAt(0) == '{' && payload.charAt(payload.length() - 1) == '}') { //likely to be json, parse it: Map claimsMap = (Map) readValue(payload); claims = new DefaultClaims(claimsMap); } @@ -385,7 +390,10 @@ public class DefaultJwtParser implements JwtParser { Assert.notNull(key, "A signing key must be specified if the specified JWT is digitally signed."); //re-create the jwt part without the signature. This is what needs to be signed for verification: - String jwtWithoutSignature = base64UrlEncodedHeader + SEPARATOR_CHAR + base64UrlEncodedPayload; + String jwtWithoutSignature = base64UrlEncodedHeader + SEPARATOR_CHAR; + if (base64UrlEncodedPayload != null) { + jwtWithoutSignature += base64UrlEncodedPayload; + } JwtSignatureValidator validator; try { diff --git a/impl/src/test/groovy/io/jsonwebtoken/DeprecatedJwtParserTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/DeprecatedJwtParserTest.groovy index 87feba22..e7d5c677 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/DeprecatedJwtParserTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/DeprecatedJwtParserTest.groovy @@ -28,7 +28,6 @@ import java.security.SecureRandom import static ClaimJwtException.INCORRECT_EXPECTED_CLAIM_MESSAGE_TEMPLATE import static ClaimJwtException.MISSING_EXPECTED_CLAIM_MESSAGE_TEMPLATE import static org.junit.Assert.* -import static io.jsonwebtoken.DateTestUtils.truncateMillis class DeprecatedJwtParserTest { diff --git a/impl/src/test/groovy/io/jsonwebtoken/DeprecatedJwtsTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/DeprecatedJwtsTest.groovy index 5d928595..d537ba4b 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/DeprecatedJwtsTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/DeprecatedJwtsTest.groovy @@ -167,18 +167,15 @@ class DeprecatedJwtsTest { Jwts.parser().parse('..') fail() } catch (MalformedJwtException e) { - assertEquals e.message, "JWT string '..' is missing a body/payload." + assertEquals e.message, "JWT string '..' is missing a header." } } @Test void testParseWithHeaderOnly() { - try { - Jwts.parser().parse('foo..') - fail() - } catch (MalformedJwtException e) { - assertEquals e.message, "JWT string 'foo..' is missing a body/payload." - } + String unsecuredJwt = base64Url("{\"alg\":\"none\"}") + ".." + Jwt jwt = Jwts.parser().parse(unsecuredJwt) + assertEquals("none", jwt.getHeader().get("alg")) } @Test @@ -187,17 +184,7 @@ class DeprecatedJwtsTest { Jwts.parser().parse('..bar') fail() } catch (MalformedJwtException e) { - assertEquals e.message, "JWT string '..bar' is missing a body/payload." - } - } - - @Test - void testParseWithHeaderAndSignatureOnly() { - try { - Jwts.parser().parse('foo..bar') - fail() - } catch (MalformedJwtException e) { - assertEquals e.message, "JWT string 'foo..bar' is missing a body/payload." + assertEquals e.message, "JWT string has a digest/signature, but the header does not reference a valid signature algorithm." } } diff --git a/impl/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy index f5319491..39d8f00c 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy @@ -19,9 +19,11 @@ import io.jsonwebtoken.impl.DefaultClock import io.jsonwebtoken.impl.FixedClock import io.jsonwebtoken.io.Encoders import io.jsonwebtoken.lang.Strings +import io.jsonwebtoken.security.Keys import io.jsonwebtoken.security.SignatureException import org.junit.Test +import javax.crypto.SecretKey import javax.crypto.spec.SecretKeySpec import java.security.SecureRandom @@ -168,6 +170,44 @@ class JwtParserTest { assertEquals jwt.body, payload } + @Test + void testParseEmptyPayload() { + SecretKey key = Keys.secretKeyFor(SignatureAlgorithm.HS256) + + String payload = '' + + String compact = Jwts.builder().setPayload(payload).signWith(key).compact() + + assertTrue Jwts.parserBuilder().build().isSigned(compact) + + Jwt jwt = Jwts.parserBuilder().setSigningKey(key).build().parse(compact) + + assertEquals payload, jwt.body + } + + @Test + void testParseNullPayload() { + SecretKey key = Keys.secretKeyFor(SignatureAlgorithm.HS256) + + String compact = Jwts.builder().signWith(key).compact() + + assertTrue Jwts.parserBuilder().build().isSigned(compact) + + Jwt jwt = Jwts.parserBuilder().setSigningKey(key).build().parse(compact) + + assertEquals '', jwt.body + } + + @Test + void testParseNullPayloadWithoutKey() { + String compact = Jwts.builder().compact() + + Jwt jwt = Jwts.parserBuilder().build().parse(compact) + + assertEquals 'none', jwt.header.alg + assertEquals '', jwt.body + } + @Test void testParseWithExpiredJwt() { diff --git a/impl/src/test/groovy/io/jsonwebtoken/JwtsTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/JwtsTest.groovy index 25ef25c6..caef2c85 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/JwtsTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/JwtsTest.groovy @@ -167,18 +167,15 @@ class JwtsTest { Jwts.parserBuilder().build().parse('..') fail() } catch (MalformedJwtException e) { - assertEquals e.message, "JWT string '..' is missing a body/payload." + assertEquals e.message, "JWT string '..' is missing a header." } } @Test void testParseWithHeaderOnly() { - try { - Jwts.parserBuilder().build().parse('foo..') - fail() - } catch (MalformedJwtException e) { - assertEquals e.message, "JWT string 'foo..' is missing a body/payload." - } + String unsecuredJwt = base64Url("{\"alg\":\"none\"}") + ".." + Jwt jwt = Jwts.parserBuilder().build().parse(unsecuredJwt) + assertEquals("none", jwt.getHeader().get("alg")) } @Test @@ -187,17 +184,7 @@ class JwtsTest { Jwts.parserBuilder().build().parse('..bar') fail() } catch (MalformedJwtException e) { - assertEquals e.message, "JWT string '..bar' is missing a body/payload." - } - } - - @Test - void testParseWithHeaderAndSignatureOnly() { - try { - Jwts.parserBuilder().build().parse('foo..bar') - fail() - } catch (MalformedJwtException e) { - assertEquals e.message, "JWT string 'foo..bar' is missing a body/payload." + assertEquals e.message, "JWT string has a digest/signature, but the header does not reference a valid signature algorithm." } } diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/DefaultJwtBuilderTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/DefaultJwtBuilderTest.groovy index 47e34f9b..4c998423 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/DefaultJwtBuilderTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/DefaultJwtBuilderTest.groovy @@ -138,26 +138,11 @@ class DefaultJwtBuilderTest { assertNull b.claims.foo } - @Test - void testCompactWithoutBody() { - def b = new DefaultJwtBuilder() - try { - b.compact() - fail() - } catch (IllegalStateException ise) { - assertEquals ise.message, "Either 'payload' or 'claims' must be specified." - } - } - @Test void testCompactWithoutPayloadOrClaims() { - def b = new DefaultJwtBuilder() - try { - b.compact() - fail() - } catch (IllegalStateException ise) { - assertEquals ise.message, "Either 'payload' or 'claims' must be specified." - } + def compact = new DefaultJwtBuilder().compact() + + assertTrue compact.endsWith("..") } @Test