mirror of https://github.com/jwtk/jjwt.git
updated SigningKeyResolver concept to handle standard java.security.Key instances, and updated the SigningKeyResolverAdapter to include bytes convenience methods.
This commit is contained in:
parent
f59684488f
commit
ec643b4556
|
@ -78,17 +78,32 @@ public interface JwtParser {
|
|||
JwtParser setSigningKey(Key key);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* <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>
|
||||
* Sets the {@link SigningKeyResolver} used to acquire the <code>signing key</code> that should be used to verify
|
||||
* a JWS's signature. If the parsed String is not a JWS (no signature), this resolver is not used.
|
||||
*
|
||||
* <p>Specifying a {@code SigningKeyResolver} is necessary when the signing key is not already known before parsing
|
||||
* the JWT and the JWT header or payload (plaintext body or Claims) must be inspected first to determine how to
|
||||
* look up the signing key. Once returned by the resolver, the JwtParser will then verify the JWS signature with the
|
||||
* returned key. For example:</p>
|
||||
*
|
||||
* <pre>
|
||||
* Jws<Claims> jws = Jwts.parser().setSigningKeyResolver(new SigningKeyResolverAdapter() {
|
||||
* @Override
|
||||
* public byte[] resolveSigningKeyBytes(JwsHeader header, Claims claims) {
|
||||
* //inspect the header or claims, lookup and return the signing key
|
||||
* return getSigningKey(header, claims); //implement me
|
||||
* }})
|
||||
* .parseClaimsJws(compact);
|
||||
* </pre>
|
||||
*
|
||||
* <p>A {@code SigningKeyResolver} is invoked once during parsing before the signature is verified.</p>
|
||||
*
|
||||
* <p>This method should only be used if a signing key is not provided by the other {@code setSigningKey*} builder
|
||||
* methods.</p>
|
||||
*
|
||||
* @param signingKeyResolver the signing key resolver used to retrieve the signing key.
|
||||
* @return the parser for method chaining.
|
||||
* @since 0.4
|
||||
*/
|
||||
JwtParser setSigningKeyResolver(SigningKeyResolver signingKeyResolver);
|
||||
|
||||
|
@ -96,7 +111,7 @@ public interface JwtParser {
|
|||
* Returns {@code true} if the specified JWT compact string represents a signed JWT (aka a 'JWS'), {@code false}
|
||||
* otherwise.
|
||||
*
|
||||
* <p>Note that if you are reasonably sure that the token is signed, it is usually more efficient to attempt to
|
||||
* <p>Note that if you are reasonably sure that the token is signed, it is more efficient to attempt to
|
||||
* parse the token (and catching exceptions if necessary) instead of calling this method first before parsing.</p>
|
||||
*
|
||||
* @param jwt the compact serialized JWT to check
|
||||
|
|
|
@ -15,44 +15,59 @@
|
|||
*/
|
||||
package io.jsonwebtoken;
|
||||
|
||||
import java.security.Key;
|
||||
|
||||
/**
|
||||
* A JwsSigningKeyResolver is invoked by a {@link io.jsonwebtoken.JwtParser JwtParser} if it's provided and the
|
||||
* JWT being parsed 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.
|
||||
* A {@code SigningKeyResolver} can be used by a {@link io.jsonwebtoken.JwtParser JwtParser} to find a signing key that
|
||||
* should be used to verify a JWS signature.
|
||||
*
|
||||
* <p>A {@code SigningKeyResolver} is necessary when the signing key is not already known before parsing the JWT and the
|
||||
* JWT header or payload (plaintext body or Claims) must be inspected first to determine how to look up the signing key.
|
||||
* Once returned by the resolver, the JwtParser will then verify the JWS signature with the returned key. For
|
||||
* example:</p>
|
||||
*
|
||||
* <pre>
|
||||
* Jws<Claims> jws = Jwts.parser().setSigningKeyResolver(new SigningKeyResolverAdapter() {
|
||||
* @Override
|
||||
* public byte[] resolveSigningKeyBytes(JwsHeader header, Claims claims) {
|
||||
* //inspect the header or claims, lookup and return the signing key
|
||||
* return getSigningKeyBytes(header, claims); //implement me
|
||||
* }})
|
||||
* .parseClaimsJws(compact);
|
||||
* </pre>
|
||||
*
|
||||
* <p>A {@code SigningKeyResolver} is invoked once during parsing before the signature is verified.</p>
|
||||
*
|
||||
* <h4>SigningKeyResolverAdapter</h4>
|
||||
*
|
||||
* <p>If you only need to resolve a signing key for a particular JWS (either a plaintext or Claims JWS), consider using
|
||||
* the {@link io.jsonwebtoken.SigningKeyResolverAdapter} and overriding only the method you need to support instead of
|
||||
* implementing this interface directly.</p>
|
||||
*
|
||||
* @see io.jsonwebtoken.SigningKeyResolverAdapter
|
||||
* @since 0.4
|
||||
*/
|
||||
public interface SigningKeyResolver {
|
||||
|
||||
/**
|
||||
* 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>
|
||||
* Returns the signing key that should be used to validate a digital signature for the Claims JWS with the specified
|
||||
* header and claims.
|
||||
*
|
||||
* @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.
|
||||
* @param header the header of the JWS to validate
|
||||
* @param claims the claims (body) of the JWS to validate
|
||||
* @return the signing key that should be used to validate a digital signature for the Claims JWS with the specified
|
||||
* header and claims.
|
||||
*/
|
||||
byte[] resolveSigningKey(JwsHeader header, Claims claims);
|
||||
Key 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>
|
||||
* Returns the signing key that should be used to validate a digital signature for the Plaintext JWS with the
|
||||
* specified header and plaintext payload.
|
||||
*
|
||||
* @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.
|
||||
* @param header the header of the JWS to validate
|
||||
* @param plaintext the plaintext body of the JWS to validate
|
||||
* @return the signing key that should be used to validate a digital signature for the Plaintext JWS with the
|
||||
* specified header and plaintext payload.
|
||||
*/
|
||||
byte[] resolveSigningKey(JwsHeader header, String payload);
|
||||
Key resolveSigningKey(JwsHeader header, String plaintext);
|
||||
}
|
||||
|
|
|
@ -15,27 +15,82 @@
|
|||
*/
|
||||
package io.jsonwebtoken;
|
||||
|
||||
import io.jsonwebtoken.lang.Assert;
|
||||
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.security.Key;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* {@link SigningKeyResolver} interface that allows subclasses to process only the type of JWS body that
|
||||
* is 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>
|
||||
* <p>The {@link #resolveSigningKey(JwsHeader, Claims)} and {@link #resolveSigningKey(JwsHeader, String)} method
|
||||
* implementations delegate to the
|
||||
* {@link #resolveSigningKeyBytes(JwsHeader, Claims)} and {@link #resolveSigningKeyBytes(JwsHeader, String)} methods
|
||||
* respectively. The latter two methods simply throw exceptions: they represent scenarios expected by
|
||||
* calling code in known situations, and it is expected that you override the implementation in those known situations;
|
||||
* non-overridden *KeyBytes methods indicates that the JWS input was unexpected.</p>
|
||||
*
|
||||
* <p>If either {@link #resolveSigningKey(JwsHeader, String)} or {@link #resolveSigningKey(JwsHeader, String)}
|
||||
* are not overridden, one (or both) of the *KeyBytes variants must be overridden depending on your expected
|
||||
* use case. You do not have to override any method that does not represent an expected condition.</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.");
|
||||
public Key resolveSigningKey(JwsHeader header, Claims claims) {
|
||||
SignatureAlgorithm alg = SignatureAlgorithm.forName(header.getAlgorithm());
|
||||
byte[] keyBytes = resolveSigningKeyBytes(header, claims);
|
||||
Assert.isTrue(!alg.isRsa(), "resolveSigningKeyBytes(JwsHeader, Claims) cannot be used for RSA signatures. " +
|
||||
"Override the resolveSigningKey(JwsHeader, Claims) method instead and return a " +
|
||||
"PublicKey or PrivateKey instance.");
|
||||
return new SecretKeySpec(keyBytes, alg.getJcaName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] resolveSigningKey(JwsHeader header, String payload) {
|
||||
throw new UnsupportedJwtException("Resolving signing keys with plaintext payload are not supported.");
|
||||
public Key resolveSigningKey(JwsHeader header, String plaintext) {
|
||||
SignatureAlgorithm alg = SignatureAlgorithm.forName(header.getAlgorithm());
|
||||
byte[] keyBytes = resolveSigningKeyBytes(header, plaintext);
|
||||
Assert.isTrue(!alg.isRsa(), "resolveSigningKeyBytes(JwsHeader, String) cannot be used for RSA signatures. " +
|
||||
"Override the resolveSigningKey(JwsHeader, String) method instead and return a " +
|
||||
"PublicKey or PrivateKey instance.");
|
||||
return new SecretKeySpec(keyBytes, alg.getJcaName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method invoked by {@link #resolveSigningKey(JwsHeader, Claims)} that obtains the necessary signing
|
||||
* key bytes. This implementation simply throws an exception: if the JWS parsed is a Claims JWS, you must
|
||||
* override this method or the {@link #resolveSigningKey(JwsHeader, Claims)} method instead.
|
||||
*
|
||||
* <p><b>NOTE:</b> You cannot override this method when validating RSA signatures. If you expect RSA signatures, </p>
|
||||
*
|
||||
* @param header the parsed {@link JwsHeader}
|
||||
* @param claims the parsed {@link Claims}
|
||||
* @return the signing key bytes to use to verify the JWS signature.
|
||||
*/
|
||||
public byte[] resolveSigningKeyBytes(JwsHeader header, Claims claims) {
|
||||
throw new UnsupportedJwtException("The specified SigningKeyResolver implementation does not support " +
|
||||
"Claims JWS signing key resolution. Consider overriding either the " +
|
||||
"resolveSigningKey(JwsHeader, Claims) or " +
|
||||
"resolveSigningKeyBytes(JwsHeader, Claims) method.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method invoked by {@link #resolveSigningKey(JwsHeader, String)} that obtains the necessary signing
|
||||
* key bytes. This implementation simply throws an exception: if the JWS parsed is a plaintext JWS, you must
|
||||
* override this method or the {@link #resolveSigningKey(JwsHeader, String)} method instead.
|
||||
*
|
||||
* @param header the parsed {@link JwsHeader}
|
||||
* @param payload the parsed String plaintext payload
|
||||
* @return the signing key bytes to use to verify the JWS signature.
|
||||
*/
|
||||
public byte[] resolveSigningKeyBytes(JwsHeader header, String payload) {
|
||||
throw new UnsupportedJwtException("The specified SigningKeyResolver implementation does not support " +
|
||||
"plaintext JWS signing key resolution. Consider overriding either the " +
|
||||
"resolveSigningKey(JwsHeader, String) or " +
|
||||
"resolveSigningKeyBytes(JwsHeader, String) method.");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -81,7 +81,7 @@ public class DefaultJwtParser implements JwtParser {
|
|||
|
||||
@Override
|
||||
public JwtParser setSigningKeyResolver(SigningKeyResolver signingKeyResolver) {
|
||||
Assert.notNull(signingKeyResolver, "jwsSigningKeyResolver cannot be null.");
|
||||
Assert.notNull(signingKeyResolver, "SigningKeyResolver cannot be null.");
|
||||
this.signingKeyResolver = signingKeyResolver;
|
||||
return this;
|
||||
}
|
||||
|
@ -245,8 +245,8 @@ 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) && 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.");
|
||||
String object = key != null ? "a key object" : "key bytes";
|
||||
throw new IllegalStateException("A signing key resolver and " + object + " cannot both be specified. Choose either.");
|
||||
}
|
||||
|
||||
//digitally signed, let's assert the signature:
|
||||
|
@ -258,9 +258,9 @@ public class DefaultJwtParser implements JwtParser {
|
|||
|
||||
if (Objects.isEmpty(keyBytes) && signingKeyResolver != null) { //use the signingKeyResolver
|
||||
if (claims != null) {
|
||||
keyBytes = signingKeyResolver.resolveSigningKey(jwsHeader, claims);
|
||||
key = signingKeyResolver.resolveSigningKey(jwsHeader, claims);
|
||||
} else {
|
||||
keyBytes = signingKeyResolver.resolveSigningKey(jwsHeader, payload);
|
||||
key = signingKeyResolver.resolveSigningKey(jwsHeader, payload);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -516,7 +516,7 @@ class JwtParserTest {
|
|||
|
||||
def signingKeyResolver = new SigningKeyResolverAdapter() {
|
||||
@Override
|
||||
byte[] resolveSigningKey(JwsHeader header, Claims claims) {
|
||||
byte[] resolveSigningKeyBytes(JwsHeader header, Claims claims) {
|
||||
return key
|
||||
}
|
||||
}
|
||||
|
@ -537,7 +537,7 @@ class JwtParserTest {
|
|||
|
||||
def signingKeyResolver = new SigningKeyResolverAdapter() {
|
||||
@Override
|
||||
byte[] resolveSigningKey(JwsHeader header, Claims claims) {
|
||||
byte[] resolveSigningKeyBytes(JwsHeader header, Claims claims) {
|
||||
return randomKey()
|
||||
}
|
||||
}
|
||||
|
@ -561,7 +561,7 @@ class JwtParserTest {
|
|||
|
||||
def signingKeyResolver = new SigningKeyResolverAdapter() {
|
||||
@Override
|
||||
byte[] resolveSigningKey(JwsHeader header, Claims claims) {
|
||||
byte[] resolveSigningKeyBytes(JwsHeader header, Claims claims) {
|
||||
return randomKey()
|
||||
}
|
||||
}
|
||||
|
@ -570,7 +570,7 @@ class JwtParserTest {
|
|||
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.'
|
||||
assertEquals ise.getMessage(), 'A signing key resolver and a key object cannot both be specified. Choose either.'
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -585,7 +585,7 @@ class JwtParserTest {
|
|||
|
||||
def signingKeyResolver = new SigningKeyResolverAdapter() {
|
||||
@Override
|
||||
byte[] resolveSigningKey(JwsHeader header, Claims claims) {
|
||||
byte[] resolveSigningKeyBytes(JwsHeader header, Claims claims) {
|
||||
return randomKey()
|
||||
}
|
||||
}
|
||||
|
@ -594,7 +594,7 @@ class JwtParserTest {
|
|||
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.'
|
||||
assertEquals ise.getMessage(), 'A signing key resolver and key bytes cannot both be specified. Choose either.'
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -611,7 +611,7 @@ class JwtParserTest {
|
|||
Jwts.parser().setSigningKeyResolver(null).parseClaimsJws(compact);
|
||||
fail()
|
||||
} catch (IllegalArgumentException iae) {
|
||||
assertEquals iae.getMessage(), 'jwsSigningKeyResolver cannot be null.'
|
||||
assertEquals iae.getMessage(), 'SigningKeyResolver cannot be null.'
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -630,7 +630,9 @@ class JwtParserTest {
|
|||
Jwts.parser().setSigningKeyResolver(signingKeyResolver).parseClaimsJws(compact);
|
||||
fail()
|
||||
} catch (UnsupportedJwtException ex) {
|
||||
assertEquals ex.getMessage(), 'Resolving signing keys with claims are not supported.'
|
||||
assertEquals ex.getMessage(), 'The specified SigningKeyResolver implementation does not support ' +
|
||||
'Claims JWS signing key resolution. Consider overriding either the ' +
|
||||
'resolveSigningKey(JwsHeader, Claims) or resolveSigningKeyBytes(JwsHeader, Claims) method.'
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -649,7 +651,7 @@ class JwtParserTest {
|
|||
|
||||
def signingKeyResolver = new SigningKeyResolverAdapter() {
|
||||
@Override
|
||||
byte[] resolveSigningKey(JwsHeader header, String payload) {
|
||||
byte[] resolveSigningKeyBytes(JwsHeader header, String payload) {
|
||||
return key
|
||||
}
|
||||
}
|
||||
|
@ -670,7 +672,7 @@ class JwtParserTest {
|
|||
|
||||
def signingKeyResolver = new SigningKeyResolverAdapter() {
|
||||
@Override
|
||||
byte[] resolveSigningKey(JwsHeader header, String payload) {
|
||||
byte[] resolveSigningKeyBytes(JwsHeader header, String payload) {
|
||||
return randomKey()
|
||||
}
|
||||
}
|
||||
|
@ -698,7 +700,9 @@ class JwtParserTest {
|
|||
Jwts.parser().setSigningKeyResolver(signingKeyResolver).parsePlaintextJws(compact);
|
||||
fail()
|
||||
} catch (UnsupportedJwtException ex) {
|
||||
assertEquals ex.getMessage(), 'Resolving signing keys with plaintext payload are not supported.'
|
||||
assertEquals ex.getMessage(), 'The specified SigningKeyResolver implementation does not support plaintext ' +
|
||||
'JWS signing key resolution. Consider overriding either the ' +
|
||||
'resolveSigningKey(JwsHeader, String) or resolveSigningKeyBytes(JwsHeader, String) method.'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue