Issue #8 Added ability to resolve signing keys with String payload. Added SigningKeyResolverAdapter.

This commit is contained in:
josebarrueta 2014-11-18 22:42:03 -08:00
parent e1be49020c
commit 44965c7b9f
5 changed files with 178 additions and 27 deletions

View File

@ -78,7 +78,7 @@ public interface JwtParser {
JwtParser setSigningKey(Key key); JwtParser setSigningKey(Key key);
/** /**
* Sets the {@link JwsSigningKeyResolver} used to resolve the <code>signing key</code> using the parsed {@link JwsHeader} * Sets the {@link SigningKeyResolver} used to resolve the <code>signing key</code> 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. * and/or the {@link Claims}. If the specified JWT string is not a JWS (no signature), this resolver is not used.
* <p/> * <p/>
* <p>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.</p> * <p>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.</p>
@ -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. * {@link JwsHeader} or the {@link Claims} embedded in the {@link Jws} can be used to resolve the signing key.
* </p> * </p>
* *
* @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. * @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} * Returns {@code true} if the specified JWT compact string represents a signed JWT (aka a 'JWS'), {@code false}

View File

@ -17,14 +17,14 @@ package io.jsonwebtoken;
/** /**
* A JwsSigningKeyResolver is invoked by a {@link io.jsonwebtoken.JwtParser JwtParser} if it's provided and the * 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.
* <p/> * <p/>
* Implementations of this interfaces must be provided to {@link io.jsonwebtoken.JwtParser JwtParser} when the values * 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 the <code>signing key</code> used to sign the JWS. * embedded in the JWS need to be used to determine the <code>signing key</code> used to sign the JWS.
* *
* @since 0.4 * @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 * 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. * @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); 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.
* <p/>
* <p>This method will only be invoked if an implementation is provided.</p>
* <p/>
* <p>Note that this key <em>MUST</em> be a valid key for the signature algorithm found in the JWT header
* (as the {@code alg} header parameter).</p>
*
* @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);
} }

View File

@ -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 <a href="http://en.wikipedia.org/wiki/Adapter_pattern">Adapter</a> 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.
*
* <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>
*
* @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.");
}
}

View File

@ -21,7 +21,7 @@ import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Header; import io.jsonwebtoken.Header;
import io.jsonwebtoken.Jws; import io.jsonwebtoken.Jws;
import io.jsonwebtoken.JwsHeader; import io.jsonwebtoken.JwsHeader;
import io.jsonwebtoken.JwsSigningKeyResolver; import io.jsonwebtoken.SigningKeyResolver;
import io.jsonwebtoken.Jwt; import io.jsonwebtoken.Jwt;
import io.jsonwebtoken.JwtHandler; import io.jsonwebtoken.JwtHandler;
import io.jsonwebtoken.JwtHandlerAdapter; import io.jsonwebtoken.JwtHandlerAdapter;
@ -56,7 +56,7 @@ public class DefaultJwtParser implements JwtParser {
private Key key; private Key key;
private JwsSigningKeyResolver jwsSigningKeyResolver; private SigningKeyResolver signingKeyResolver;
@Override @Override
public JwtParser setSigningKey(byte[] key) { public JwtParser setSigningKey(byte[] key) {
@ -80,9 +80,9 @@ public class DefaultJwtParser implements JwtParser {
} }
@Override @Override
public JwtParser setJwsSigningKeyResolver(JwsSigningKeyResolver jwsSigningKeyResolver) { public JwtParser setSigningKeyResolver(SigningKeyResolver signingKeyResolver) {
Assert.notNull(jwsSigningKeyResolver, "jwsSigningKeyResolver cannot be null."); Assert.notNull(signingKeyResolver, "jwsSigningKeyResolver cannot be null.");
this.jwsSigningKeyResolver = jwsSigningKeyResolver; this.signingKeyResolver = signingKeyResolver;
return this; return this;
} }
@ -244,7 +244,7 @@ public class DefaultJwtParser implements JwtParser {
if (key != null && keyBytes != null) { if (key != null && keyBytes != null) {
throw new IllegalStateException("A key object and key bytes cannot both be specified. Choose either."); 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 "; 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."); 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; byte[] keyBytes = this.keyBytes;
if (Objects.isEmpty(keyBytes) && jwsSigningKeyResolver != null) { //use the jwsSigningKeyResolver if (Objects.isEmpty(keyBytes) && signingKeyResolver != null) { //use the signingKeyResolver
keyBytes = jwsSigningKeyResolver.resolveSigningKey(jwsHeader, claims); if (claims != null) {
keyBytes = signingKeyResolver.resolveSigningKey(jwsHeader, claims);
} else {
keyBytes = signingKeyResolver.resolveSigningKey(jwsHeader, payload);
}
} }
if (!Objects.isEmpty(keyBytes)) { if (!Objects.isEmpty(keyBytes)) {

View File

@ -501,8 +501,12 @@ class JwtParserTest {
} }
} }
// ========================================================================
// parseClaimsJws with signingKey resolver.
// ========================================================================
@Test @Test
void testParseClaimsWithJwsSigningKeyResolver() { void testParseClaimsWithSigningKeyResolver() {
String subject = 'Joe' String subject = 'Joe'
@ -510,20 +514,20 @@ class JwtParserTest {
String compact = Jwts.builder().setSubject(subject).signWith(SignatureAlgorithm.HS256, key).compact() String compact = Jwts.builder().setSubject(subject).signWith(SignatureAlgorithm.HS256, key).compact()
def signingKeyResolver = new JwsSigningKeyResolver() { def signingKeyResolver = new SigningKeyResolverAdapter() {
@Override @Override
byte[] resolveSigningKey(JwsHeader header, Claims claims) { byte[] resolveSigningKey(JwsHeader header, Claims claims) {
return key return key
} }
} }
Jws jws = Jwts.parser().setJwsSigningKeyResolver(signingKeyResolver).parseClaimsJws(compact) Jws jws = Jwts.parser().setSigningKeyResolver(signingKeyResolver).parseClaimsJws(compact)
assertEquals jws.getBody().getSubject(), subject assertEquals jws.getBody().getSubject(), subject
} }
@Test @Test
void testParseClaimsWithJwsSigningKeyResolverInvalidKey() { void testParseClaimsWithSigningKeyResolverInvalidKey() {
String subject = 'Joe' String subject = 'Joe'
@ -531,7 +535,7 @@ class JwtParserTest {
String compact = Jwts.builder().setSubject(subject).signWith(SignatureAlgorithm.HS256, key).compact() String compact = Jwts.builder().setSubject(subject).signWith(SignatureAlgorithm.HS256, key).compact()
def signingKeyResolver = new JwsSigningKeyResolver() { def signingKeyResolver = new SigningKeyResolverAdapter() {
@Override @Override
byte[] resolveSigningKey(JwsHeader header, Claims claims) { byte[] resolveSigningKey(JwsHeader header, Claims claims) {
return randomKey() return randomKey()
@ -539,7 +543,7 @@ class JwtParserTest {
} }
try { try {
Jwts.parser().setJwsSigningKeyResolver(signingKeyResolver).parseClaimsJws(compact); Jwts.parser().setSigningKeyResolver(signingKeyResolver).parseClaimsJws(compact);
fail() fail()
} catch (SignatureException se) { } catch (SignatureException se) {
assertEquals se.getMessage(), 'JWT signature does not match locally computed signature. JWT validity cannot be asserted and should not be trusted.' 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 @Test
void testParseClaimsWithJwsSigningKeyResolverAndKey() { void testParseClaimsWithSigningKeyResolverAndKey() {
String subject = 'Joe' String subject = 'Joe'
@ -555,7 +559,7 @@ class JwtParserTest {
String compact = Jwts.builder().setSubject(subject).signWith(SignatureAlgorithm.HS256, key).compact() String compact = Jwts.builder().setSubject(subject).signWith(SignatureAlgorithm.HS256, key).compact()
def signingKeyResolver = new JwsSigningKeyResolver() { def signingKeyResolver = new SigningKeyResolverAdapter() {
@Override @Override
byte[] resolveSigningKey(JwsHeader header, Claims claims) { byte[] resolveSigningKey(JwsHeader header, Claims claims) {
return randomKey() return randomKey()
@ -563,7 +567,7 @@ class JwtParserTest {
} }
try { try {
Jwts.parser().setSigningKey(key).setJwsSigningKeyResolver(signingKeyResolver).parseClaimsJws(compact); Jwts.parser().setSigningKey(key).setSigningKeyResolver(signingKeyResolver).parseClaimsJws(compact);
fail() fail()
} catch (IllegalStateException ise) { } catch (IllegalStateException ise) {
assertEquals ise.getMessage(), 'A signing key resolver object and a key object cannot both be specified. Choose either.' 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 @Test
void testParseClaimsWithJwsSigningKeyResolverAndKeyBytes() { void testParseClaimsWithSigningKeyResolverAndKeyBytes() {
String subject = 'Joe' String subject = 'Joe'
@ -579,7 +583,7 @@ class JwtParserTest {
String compact = Jwts.builder().setSubject(subject).signWith(SignatureAlgorithm.HS256, key).compact() String compact = Jwts.builder().setSubject(subject).signWith(SignatureAlgorithm.HS256, key).compact()
def signingKeyResolver = new JwsSigningKeyResolver() { def signingKeyResolver = new SigningKeyResolverAdapter() {
@Override @Override
byte[] resolveSigningKey(JwsHeader header, Claims claims) { byte[] resolveSigningKey(JwsHeader header, Claims claims) {
return randomKey() return randomKey()
@ -587,7 +591,7 @@ class JwtParserTest {
} }
try { try {
Jwts.parser().setSigningKey(key).setJwsSigningKeyResolver(signingKeyResolver).parseClaimsJws(compact); Jwts.parser().setSigningKey(key).setSigningKeyResolver(signingKeyResolver).parseClaimsJws(compact);
fail() fail()
} catch (IllegalStateException ise) { } catch (IllegalStateException ise) {
assertEquals ise.getMessage(), 'A signing key resolver object and key bytes cannot both be specified. Choose either.' assertEquals ise.getMessage(), 'A signing key resolver object and key bytes cannot both be specified. Choose either.'
@ -595,7 +599,7 @@ class JwtParserTest {
} }
@Test @Test
void testParseClaimsWithNullJwsSigningKeyResolver() { void testParseClaimsWithNullSigningKeyResolver() {
String subject = 'Joe' String subject = 'Joe'
@ -604,10 +608,97 @@ class JwtParserTest {
String compact = Jwts.builder().setSubject(subject).signWith(SignatureAlgorithm.HS256, key).compact() String compact = Jwts.builder().setSubject(subject).signWith(SignatureAlgorithm.HS256, key).compact()
try { try {
Jwts.parser().setJwsSigningKeyResolver(null).parseClaimsJws(compact); Jwts.parser().setSigningKeyResolver(null).parseClaimsJws(compact);
fail() fail()
} catch (IllegalArgumentException iae) { } catch (IllegalArgumentException iae) {
assertEquals iae.getMessage(), 'jwsSigningKeyResolver cannot be null.' 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<String> jws = Jwts.parser().setSigningKeyResolver(signingKeyResolver).parsePlaintextJws(compact);
assertEquals jws.getBody(), inputPayload
}
@Test
void testParsePlaintextJwsWithSigningKeyResolverInvalidKey() {
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 randomKey()
}
}
try {
Jwts.parser().setSigningKeyResolver(signingKeyResolver).parsePlaintextJws(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.'
}
}
@Test
void testParsePlaintextJwsWithInvalidSigningKeyResolverAdapter() {
String payload = 'Hello world!'
byte[] key = randomKey()
String compact = Jwts.builder().setPayload(payload).signWith(SignatureAlgorithm.HS256, key).compact()
def signingKeyResolver = new SigningKeyResolverAdapter()
try {
Jwts.parser().setSigningKeyResolver(signingKeyResolver).parsePlaintextJws(compact);
fail()
} catch (UnsupportedJwtException ex) {
assertEquals ex.getMessage(), 'Resolving signing keys with plaintext payload are not supported.'
}
}
} }