Issue #8 Add ability to resolve signing key based on Jws embedded values before its signature is verified

This commit is contained in:
josebarrueta 2014-11-17 13:56:48 -08:00
parent 3e35e4e226
commit 3eb4f01c3f
3 changed files with 78 additions and 1 deletions

View File

@ -0,0 +1,43 @@
/*
* 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 JwsSigningKeyResolver is invoked by a {@link io.jsonwebtoken.JwtParser JwtParser} if it's provided and the
* provided JWT is signed.
* <p/>
* 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.
*
* @since 0.4
*/
public interface JwsSigningKeyResolver {
/**
* 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 {@link Claims}
* <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 claims the parsed {@link Claims}
* @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);
}

View File

@ -77,6 +77,21 @@ 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}
* and/or the {@link Claims}. If the specified JWT string is not a JWS (no signature), this resolver is not used.
* <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/>
* <p>This is a convenience method: the {@code jwsSignatureKeyResolver} is used after a Jws has been parsed and either the
* {@link JwsHeader} or the {@link Claims} embedded in the {@link Jws} can be used to resolve the signing key.
* </p>
*
* @param jwsSigningKeyResolver the signing key resolver used to retrieve the signing key.
* @return the parser for method chaining.
*/
JwtParser setJwsSigningKeyResolver(JwsSigningKeyResolver jwsSigningKeyResolver);
/** /**
* 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}
* otherwise. * otherwise.

View File

@ -21,6 +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.Jwt; import io.jsonwebtoken.Jwt;
import io.jsonwebtoken.JwtHandler; import io.jsonwebtoken.JwtHandler;
import io.jsonwebtoken.JwtHandlerAdapter; import io.jsonwebtoken.JwtHandlerAdapter;
@ -55,6 +56,8 @@ public class DefaultJwtParser implements JwtParser {
private Key key; private Key key;
private JwsSigningKeyResolver jwsSigningKeyResolver;
@Override @Override
public JwtParser setSigningKey(byte[] key) { public JwtParser setSigningKey(byte[] key) {
Assert.notEmpty(key, "signing key cannot be null or empty."); Assert.notEmpty(key, "signing key cannot be null or empty.");
@ -76,6 +79,13 @@ public class DefaultJwtParser implements JwtParser {
return this; return this;
} }
@Override
public JwtParser setJwsSigningKeyResolver(JwsSigningKeyResolver jwsSigningKeyResolver) {
Assert.notNull(jwsSigningKeyResolver, "jwsSigningKeyResolver cannot be null.");
this.jwsSigningKeyResolver = jwsSigningKeyResolver;
return this;
}
@Override @Override
public boolean isSigned(String jwt) { public boolean isSigned(String jwt) {
@ -234,6 +244,9 @@ 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) {
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.");
} }
//digitally signed, let's assert the signature: //digitally signed, let's assert the signature:
@ -241,7 +254,13 @@ public class DefaultJwtParser implements JwtParser {
if (key == null) { //fall back to keyBytes if (key == null) { //fall back to keyBytes
if (!Objects.isEmpty(this.keyBytes)) { byte[] keyBytes = this.keyBytes;
if (Objects.isEmpty(keyBytes) && jwsSigningKeyResolver != null) { //use the jwsSigningKeyResolver
keyBytes = jwsSigningKeyResolver.resolveSigningKey(jwsHeader, claims);
}
if (!Objects.isEmpty(keyBytes)) {
Assert.isTrue(!algorithm.isRsa(), Assert.isTrue(!algorithm.isRsa(),
"Key bytes cannot be specified for RSA signatures. Please specify a PublicKey or PrivateKey instance."); "Key bytes cannot be specified for RSA signatures. Please specify a PublicKey or PrivateKey instance.");