mirror of https://github.com/jwtk/jjwt.git
Add DefaultJwtParser functionality to parse JWSs with empty body. (#540)
* Add DefaultJwtParser functionality to parse JWSs with empty body. * Review Fix: Change allowEmptyBody(boolean) to requirePayload(boolean). Set payloadRequired true for each require*() method in JwtParser and JwtParserBuilder. * Add missing ImmutableJwtParserTest. * Review changes: Moving to solution without payload requirement flag. * Review changes: Allow empty Jwt payload * Remove unused imports Co-authored-by: Philipp Zormeier <philipp.zormeier@thoughtworks.com>
This commit is contained in:
parent
82b870e283
commit
2b00ed1819
|
@ -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)) {
|
||||
|
|
|
@ -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<String, Object> claimsMap = (Map<String, Object>) 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 {
|
||||
|
|
|
@ -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 {
|
||||
|
||||
|
|
|
@ -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."
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<Header, String> 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<Header, String> jwt = Jwts.parserBuilder().setSigningKey(key).build().parse(compact)
|
||||
|
||||
assertEquals '', jwt.body
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseNullPayloadWithoutKey() {
|
||||
String compact = Jwts.builder().compact()
|
||||
|
||||
Jwt<Header, String> jwt = Jwts.parserBuilder().build().parse(compact)
|
||||
|
||||
assertEquals 'none', jwt.header.alg
|
||||
assertEquals '', jwt.body
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseWithExpiredJwt() {
|
||||
|
||||
|
|
|
@ -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."
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue