diff --git a/src/main/java/io/jsonwebtoken/JwtParser.java b/src/main/java/io/jsonwebtoken/JwtParser.java
index 0b15736a..00bd706a 100644
--- a/src/main/java/io/jsonwebtoken/JwtParser.java
+++ b/src/main/java/io/jsonwebtoken/JwtParser.java
@@ -78,7 +78,7 @@ public interface JwtParser {
JwtParser setSigningKey(Key key);
/**
- * Sets the {@link JwsSigningKeyResolver} used to resolve the signing key
using the parsed {@link JwsHeader}
+ * Sets the {@link SigningKeyResolver} used to resolve the signing key
using the parsed {@link JwsHeader}
* and/or the {@link Claims}. If the specified JWT string is not a JWS (no signature), this resolver is not used.
*
This method will set the signing key resolver to be used in case a signing key is not provided by any of the other methods.
@@ -87,10 +87,10 @@ public interface JwtParser { * {@link JwsHeader} or the {@link Claims} embedded in the {@link Jws} can be used to resolve the signing key. * * - * @param jwsSigningKeyResolver the signing key resolver used to retrieve the signing key. + * @param signingKeyResolver the signing key resolver used to retrieve the signing key. * @return the parser for method chaining. */ - JwtParser setJwsSigningKeyResolver(JwsSigningKeyResolver jwsSigningKeyResolver); + JwtParser setSigningKeyResolver(SigningKeyResolver signingKeyResolver); /** * Returns {@code true} if the specified JWT compact string represents a signed JWT (aka a 'JWS'), {@code false} diff --git a/src/main/java/io/jsonwebtoken/JwsSigningKeyResolver.java b/src/main/java/io/jsonwebtoken/SigningKeyResolver.java similarity index 67% rename from src/main/java/io/jsonwebtoken/JwsSigningKeyResolver.java rename to src/main/java/io/jsonwebtoken/SigningKeyResolver.java index a7ea8376..7e469b83 100644 --- a/src/main/java/io/jsonwebtoken/JwsSigningKeyResolver.java +++ b/src/main/java/io/jsonwebtoken/SigningKeyResolver.java @@ -17,14 +17,14 @@ package io.jsonwebtoken; /** * A JwsSigningKeyResolver is invoked by a {@link io.jsonwebtoken.JwtParser JwtParser} if it's provided and the - * provided JWT is signed. + * JWT being parsed is signed. * * Implementations of this interfaces must be provided to {@link io.jsonwebtoken.JwtParser JwtParser} when the values * embedded in the JWS need to be used to determine thesigning key
used to sign the JWS.
*
* @since 0.4
*/
-public interface JwsSigningKeyResolver {
+public interface SigningKeyResolver {
/**
* This method is invoked when a {@link io.jsonwebtoken.JwtParser JwtParser} parsed a {@link Jws} and needs
@@ -40,4 +40,19 @@ public interface JwsSigningKeyResolver {
* @return any object to be used after inspecting the JWS, or {@code null} if no return value is necessary.
*/
byte[] resolveSigningKey(JwsHeader header, Claims claims);
+
+ /**
+ * This method is invoked when a {@link io.jsonwebtoken.JwtParser JwtParser} parsed a {@link Jws} and needs
+ * to resolve the signing key, based on a value embedded in the {@link JwsHeader} and/or the plaintext payload.
+ *
+ * This method will only be invoked if an implementation is provided.
+ * + *Note that this key MUST be a valid key for the signature algorithm found in the JWT header + * (as the {@code alg} header parameter).
+ * + * @param header the parsed {@link JwsHeader} + * @param payload the jws plaintext payload. + * @return any object to be used after inspecting the JWS, or {@code null} if no return value is necessary. + */ + byte[] resolveSigningKey(JwsHeader header, String payload); } diff --git a/src/main/java/io/jsonwebtoken/SigningKeyResolverAdapter.java b/src/main/java/io/jsonwebtoken/SigningKeyResolverAdapter.java new file mode 100644 index 00000000..f4af8580 --- /dev/null +++ b/src/main/java/io/jsonwebtoken/SigningKeyResolverAdapter.java @@ -0,0 +1,41 @@ +/* + * 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 SigningKeyResolver} interface that allows subclasses to process only the type of Jws body that + * are known/expected for a particular 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.
+ * + * @since 0.4 + */ +public class SigningKeyResolverAdapter implements SigningKeyResolver { + + @Override + public byte[] resolveSigningKey(JwsHeader header, Claims claims) { + throw new UnsupportedJwtException("Resolving signing keys with claims are not supported."); + } + + @Override + public byte[] resolveSigningKey(JwsHeader header, String payload) { + throw new UnsupportedJwtException("Resolving signing keys with plaintext payload are not supported."); + } +} diff --git a/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java b/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java index a7f708b7..468f8dd7 100644 --- a/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java +++ b/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java @@ -21,7 +21,7 @@ import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.Header; import io.jsonwebtoken.Jws; import io.jsonwebtoken.JwsHeader; -import io.jsonwebtoken.JwsSigningKeyResolver; +import io.jsonwebtoken.SigningKeyResolver; import io.jsonwebtoken.Jwt; import io.jsonwebtoken.JwtHandler; import io.jsonwebtoken.JwtHandlerAdapter; @@ -56,7 +56,7 @@ public class DefaultJwtParser implements JwtParser { private Key key; - private JwsSigningKeyResolver jwsSigningKeyResolver; + private SigningKeyResolver signingKeyResolver; @Override public JwtParser setSigningKey(byte[] key) { @@ -80,9 +80,9 @@ public class DefaultJwtParser implements JwtParser { } @Override - public JwtParser setJwsSigningKeyResolver(JwsSigningKeyResolver jwsSigningKeyResolver) { - Assert.notNull(jwsSigningKeyResolver, "jwsSigningKeyResolver cannot be null."); - this.jwsSigningKeyResolver = jwsSigningKeyResolver; + public JwtParser setSigningKeyResolver(SigningKeyResolver signingKeyResolver) { + Assert.notNull(signingKeyResolver, "jwsSigningKeyResolver cannot be null."); + this.signingKeyResolver = signingKeyResolver; return this; } @@ -244,7 +244,7 @@ public class DefaultJwtParser implements JwtParser { if (key != null && keyBytes != null) { throw new IllegalStateException("A key object and key bytes cannot both be specified. Choose either."); - } else if ((key != null || keyBytes != null) && jwsSigningKeyResolver != null) { + } else if ((key != null || keyBytes != null) && signingKeyResolver != null) { String object = key != null ? " a key object " : " key bytes "; throw new IllegalStateException("A signing key resolver object and" + object + "cannot both be specified. Choose either."); } @@ -256,8 +256,12 @@ public class DefaultJwtParser implements JwtParser { byte[] keyBytes = this.keyBytes; - if (Objects.isEmpty(keyBytes) && jwsSigningKeyResolver != null) { //use the jwsSigningKeyResolver - keyBytes = jwsSigningKeyResolver.resolveSigningKey(jwsHeader, claims); + if (Objects.isEmpty(keyBytes) && signingKeyResolver != null) { //use the signingKeyResolver + if (claims != null) { + keyBytes = signingKeyResolver.resolveSigningKey(jwsHeader, claims); + } else { + keyBytes = signingKeyResolver.resolveSigningKey(jwsHeader, payload); + } } if (!Objects.isEmpty(keyBytes)) { diff --git a/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy b/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy index a853cbf0..43ced211 100644 --- a/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy +++ b/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy @@ -501,8 +501,12 @@ class JwtParserTest { } } + // ======================================================================== + // parseClaimsJws with signingKey resolver. + // ======================================================================== + @Test - void testParseClaimsWithJwsSigningKeyResolver() { + void testParseClaimsWithSigningKeyResolver() { String subject = 'Joe' @@ -510,20 +514,20 @@ class JwtParserTest { String compact = Jwts.builder().setSubject(subject).signWith(SignatureAlgorithm.HS256, key).compact() - def signingKeyResolver = new JwsSigningKeyResolver() { + def signingKeyResolver = new SigningKeyResolverAdapter() { @Override byte[] resolveSigningKey(JwsHeader header, Claims claims) { return key } } - Jws jws = Jwts.parser().setJwsSigningKeyResolver(signingKeyResolver).parseClaimsJws(compact) + Jws jws = Jwts.parser().setSigningKeyResolver(signingKeyResolver).parseClaimsJws(compact) assertEquals jws.getBody().getSubject(), subject } @Test - void testParseClaimsWithJwsSigningKeyResolverInvalidKey() { + void testParseClaimsWithSigningKeyResolverInvalidKey() { String subject = 'Joe' @@ -531,7 +535,7 @@ class JwtParserTest { String compact = Jwts.builder().setSubject(subject).signWith(SignatureAlgorithm.HS256, key).compact() - def signingKeyResolver = new JwsSigningKeyResolver() { + def signingKeyResolver = new SigningKeyResolverAdapter() { @Override byte[] resolveSigningKey(JwsHeader header, Claims claims) { return randomKey() @@ -539,7 +543,7 @@ class JwtParserTest { } try { - Jwts.parser().setJwsSigningKeyResolver(signingKeyResolver).parseClaimsJws(compact); + Jwts.parser().setSigningKeyResolver(signingKeyResolver).parseClaimsJws(compact); 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.' @@ -547,7 +551,7 @@ class JwtParserTest { } @Test - void testParseClaimsWithJwsSigningKeyResolverAndKey() { + void testParseClaimsWithSigningKeyResolverAndKey() { String subject = 'Joe' @@ -555,7 +559,7 @@ class JwtParserTest { String compact = Jwts.builder().setSubject(subject).signWith(SignatureAlgorithm.HS256, key).compact() - def signingKeyResolver = new JwsSigningKeyResolver() { + def signingKeyResolver = new SigningKeyResolverAdapter() { @Override byte[] resolveSigningKey(JwsHeader header, Claims claims) { return randomKey() @@ -563,7 +567,7 @@ class JwtParserTest { } try { - Jwts.parser().setSigningKey(key).setJwsSigningKeyResolver(signingKeyResolver).parseClaimsJws(compact); + Jwts.parser().setSigningKey(key).setSigningKeyResolver(signingKeyResolver).parseClaimsJws(compact); fail() } catch (IllegalStateException ise) { assertEquals ise.getMessage(), 'A signing key resolver object and a key object cannot both be specified. Choose either.' @@ -571,7 +575,7 @@ class JwtParserTest { } @Test - void testParseClaimsWithJwsSigningKeyResolverAndKeyBytes() { + void testParseClaimsWithSigningKeyResolverAndKeyBytes() { String subject = 'Joe' @@ -579,7 +583,7 @@ class JwtParserTest { String compact = Jwts.builder().setSubject(subject).signWith(SignatureAlgorithm.HS256, key).compact() - def signingKeyResolver = new JwsSigningKeyResolver() { + def signingKeyResolver = new SigningKeyResolverAdapter() { @Override byte[] resolveSigningKey(JwsHeader header, Claims claims) { return randomKey() @@ -587,7 +591,7 @@ class JwtParserTest { } try { - Jwts.parser().setSigningKey(key).setJwsSigningKeyResolver(signingKeyResolver).parseClaimsJws(compact); + Jwts.parser().setSigningKey(key).setSigningKeyResolver(signingKeyResolver).parseClaimsJws(compact); fail() } catch (IllegalStateException ise) { assertEquals ise.getMessage(), 'A signing key resolver object and key bytes cannot both be specified. Choose either.' @@ -595,7 +599,7 @@ class JwtParserTest { } @Test - void testParseClaimsWithNullJwsSigningKeyResolver() { + void testParseClaimsWithNullSigningKeyResolver() { String subject = 'Joe' @@ -604,10 +608,97 @@ class JwtParserTest { String compact = Jwts.builder().setSubject(subject).signWith(SignatureAlgorithm.HS256, key).compact() try { - Jwts.parser().setJwsSigningKeyResolver(null).parseClaimsJws(compact); + Jwts.parser().setSigningKeyResolver(null).parseClaimsJws(compact); fail() } catch (IllegalArgumentException iae) { assertEquals iae.getMessage(), 'jwsSigningKeyResolver cannot be null.' } } + + @Test + void testParseClaimsWithInvalidSigningKeyResolverAdapter() { + + String subject = 'Joe' + + byte[] key = randomKey() + + String compact = Jwts.builder().setSubject(subject).signWith(SignatureAlgorithm.HS256, key).compact() + + def signingKeyResolver = new SigningKeyResolverAdapter() + + try { + Jwts.parser().setSigningKeyResolver(signingKeyResolver).parseClaimsJws(compact); + fail() + } catch (UnsupportedJwtException ex) { + assertEquals ex.getMessage(), 'Resolving signing keys with claims are not supported.' + } + } + + // ======================================================================== + // parsePlaintextJws with signingKey resolver. + // ======================================================================== + + @Test + void testParsePlaintextJwsWithSigningKeyResolverAdapter() { + + String inputPayload = 'Hello world!' + + byte[] key = randomKey() + + String compact = Jwts.builder().setPayload(inputPayload).signWith(SignatureAlgorithm.HS256, key).compact() + + def signingKeyResolver = new SigningKeyResolverAdapter() { + @Override + byte[] resolveSigningKey(JwsHeader header, String payload) { + return key + } + } + + Jws