1515 * limitations under the License.
1616 */
1717
18- package org .apache .kafka .common .security .oauthbearer . internals . secured ;
18+ package org .apache .kafka .common .security .oauthbearer ;
1919
20- import org .apache .kafka .common .security .oauthbearer .OAuthBearerToken ;
20+ import org .apache .kafka .common .security .oauthbearer .internals .secured .BasicOAuthBearerToken ;
21+ import org .apache .kafka .common .security .oauthbearer .internals .secured .ClaimValidationUtils ;
22+ import org .apache .kafka .common .security .oauthbearer .internals .secured .CloseableVerificationKeyResolver ;
23+ import org .apache .kafka .common .security .oauthbearer .internals .secured .ConfigurationUtils ;
24+ import org .apache .kafka .common .security .oauthbearer .internals .secured .SerializedJwt ;
25+ import org .apache .kafka .common .security .oauthbearer .internals .secured .VerificationKeyResolverFactory ;
2126
2227import org .jose4j .jwt .JwtClaims ;
2328import org .jose4j .jwt .MalformedClaimException ;
2732import org .jose4j .jwt .consumer .JwtConsumer ;
2833import org .jose4j .jwt .consumer .JwtConsumerBuilder ;
2934import org .jose4j .jwt .consumer .JwtContext ;
30- import org .jose4j .keys .resolvers .VerificationKeyResolver ;
3135import org .slf4j .Logger ;
3236import org .slf4j .LoggerFactory ;
3337
3438import java .util .Collection ;
3539import java .util .Collections ;
40+ import java .util .List ;
41+ import java .util .Map ;
42+ import java .util .Optional ;
3643import java .util .Set ;
3744
45+ import javax .security .auth .login .AppConfigurationEntry ;
46+
47+ import static org .apache .kafka .common .config .SaslConfigs .SASL_OAUTHBEARER_CLOCK_SKEW_SECONDS ;
48+ import static org .apache .kafka .common .config .SaslConfigs .SASL_OAUTHBEARER_EXPECTED_AUDIENCE ;
49+ import static org .apache .kafka .common .config .SaslConfigs .SASL_OAUTHBEARER_EXPECTED_ISSUER ;
50+ import static org .apache .kafka .common .config .SaslConfigs .SASL_OAUTHBEARER_SCOPE_CLAIM_NAME ;
51+ import static org .apache .kafka .common .config .SaslConfigs .SASL_OAUTHBEARER_SUB_CLAIM_NAME ;
3852import static org .jose4j .jwa .AlgorithmConstraints .DISALLOW_NONE ;
3953
4054/**
4357 * from the client, but ultimately from posting the client credentials to the OAuth/OIDC provider's
4458 * token endpoint.
4559 *
46- * The validation steps performed (primary by the jose4j library) are:
60+ * The validation steps performed (primarily by the jose4j library) are:
4761 *
4862 * <ol>
4963 * <li>
5064 * Basic structural validation of the <code>b64token</code> value as defined in
5165 * <a href="https://tools.ietf.org/html/rfc6750#section-2.1">RFC 6750 Section 2.1</a>
5266 * </li>
53- * <li>Basic conversion of the token into an in-memory data structure</li>
5467 * <li>
55- * Presence of scope, <code>exp</code>, subject, <code>iss</code>, and
68+ * Basic conversion of the token into an in-memory data structure
69+ * </li>
70+ * <li>
71+ * Presence of <code>scope</code>, <code>exp</code>, <code>subject</code>, <code>iss</code>, and
5672 * <code>iat</code> claims
5773 * </li>
5874 * <li>
6177 * </li>
6278 * </ol>
6379 */
64-
6580public class BrokerJwtValidator implements JwtValidator {
6681
6782 private static final Logger log = LoggerFactory .getLogger (BrokerJwtValidator .class );
6883
69- private final JwtConsumer jwtConsumer ;
84+ private final Optional < CloseableVerificationKeyResolver > verificationKeyResolverOpt ;
7085
71- private final String scopeClaimName ;
86+ private JwtConsumer jwtConsumer ;
7287
73- private final String subClaimName ;
88+ private String scopeClaimName ;
89+
90+ private String subClaimName ;
7491
7592 /**
76- * Creates a new {@code BrokerJwtValidator} that will be used by the broker for more
77- * thorough validation of the JWT.
78- *
79- * @param clockSkew The optional value (in seconds) to allow for differences
80- * between the time of the OAuth/OIDC identity provider and
81- * the broker. If <code>null</code> is provided, the broker
82- * and the OAUth/OIDC identity provider are assumed to have
83- * very close clock settings.
84- * @param expectedAudiences The (optional) set the broker will use to verify that
85- * the JWT was issued for one of the expected audiences.
86- * The JWT will be inspected for the standard OAuth
87- * <code>aud</code> claim and if this value is set, the
88- * broker will match the value from JWT's <code>aud</code>
89- * claim to see if there is an <b>exact</b> match. If there is no
90- * match, the broker will reject the JWT and authentication
91- * will fail. May be <code>null</code> to not perform any
92- * check to verify the JWT's <code>aud</code> claim matches any
93- * fixed set of known/expected audiences.
94- * @param expectedIssuer The (optional) value for the broker to use to verify that
95- * the JWT was created by the expected issuer. The JWT will
96- * be inspected for the standard OAuth <code>iss</code> claim
97- * and if this value is set, the broker will match it
98- * <b>exactly</b> against what is in the JWT's <code>iss</code>
99- * claim. If there is no match, the broker will reject the JWT
100- * and authentication will fail. May be <code>null</code> to not
101- * perform any check to verify the JWT's <code>iss</code> claim
102- * matches a specific issuer.
103- * @param verificationKeyResolver jose4j-based {@link VerificationKeyResolver} that is used
104- * to validate the signature matches the contents of the header
105- * and payload
106- * @param scopeClaimName Name of the scope claim to use; must be non-<code>null</code>
107- * @param subClaimName Name of the subject claim to use; must be
108- * non-<code>null</code>
109- *
110- * @see JwtConsumerBuilder
111- * @see JwtConsumer
112- * @see VerificationKeyResolver
93+ * A public, no-args constructor is necessary for instantiation via configuration.
11394 */
95+ public BrokerJwtValidator () {
96+ this .verificationKeyResolverOpt = Optional .empty ();
97+ }
98+
99+ /*
100+ * Package-visible for testing.
101+ */
102+ BrokerJwtValidator (CloseableVerificationKeyResolver verificationKeyResolver ) {
103+ this .verificationKeyResolverOpt = Optional .of (verificationKeyResolver );
104+ }
105+
106+ @ Override
107+ public void configure (Map <String , ?> configs , String saslMechanism , List <AppConfigurationEntry > jaasConfigEntries ) {
108+ ConfigurationUtils cu = new ConfigurationUtils (configs , saslMechanism );
109+ List <String > expectedAudiencesList = cu .get (SASL_OAUTHBEARER_EXPECTED_AUDIENCE );
110+ Set <String > expectedAudiences = expectedAudiencesList != null ? Set .copyOf (expectedAudiencesList ) : null ;
111+ Integer clockSkew = cu .validateInteger (SASL_OAUTHBEARER_CLOCK_SKEW_SECONDS , false );
112+ String expectedIssuer = cu .validateString (SASL_OAUTHBEARER_EXPECTED_ISSUER , false );
113+ String scopeClaimName = cu .validateString (SASL_OAUTHBEARER_SCOPE_CLAIM_NAME );
114+ String subClaimName = cu .validateString (SASL_OAUTHBEARER_SUB_CLAIM_NAME );
115+
116+ CloseableVerificationKeyResolver verificationKeyResolver = verificationKeyResolverOpt .orElseGet (
117+ () -> VerificationKeyResolverFactory .get (configs , saslMechanism , jaasConfigEntries )
118+ );
114119
115- public BrokerJwtValidator (Integer clockSkew ,
116- Set <String > expectedAudiences ,
117- String expectedIssuer ,
118- VerificationKeyResolver verificationKeyResolver ,
119- String scopeClaimName ,
120- String subClaimName ) {
121120 final JwtConsumerBuilder jwtConsumerBuilder = new JwtConsumerBuilder ();
122121
123122 if (clockSkew != null )
@@ -145,19 +144,19 @@ public BrokerJwtValidator(Integer clockSkew,
145144 *
146145 * @param accessToken Non-<code>null</code> JWT access token
147146 * @return {@link OAuthBearerToken}
148- * @throws ValidateException Thrown on errors performing validation of given token
147+ * @throws JwtValidatorException Thrown on errors performing validation of given token
149148 */
150149
151150 @ SuppressWarnings ("unchecked" )
152- public OAuthBearerToken validate (String accessToken ) throws ValidateException {
151+ public OAuthBearerToken validate (String accessToken ) throws JwtValidatorException {
153152 SerializedJwt serializedJwt = new SerializedJwt (accessToken );
154153
155154 JwtContext jwt ;
156155
157156 try {
158157 jwt = jwtConsumer .process (serializedJwt .getToken ());
159158 } catch (InvalidJwtException e ) {
160- throw new ValidateException (String .format ("Could not validate the access token: %s" , e .getMessage ()), e );
159+ throw new JwtValidatorException (String .format ("Could not validate the access token: %s" , e .getMessage ()), e );
161160 }
162161
163162 JwtClaims claims = jwt .getJwtClaims ();
@@ -190,13 +189,13 @@ else if (scopeRaw instanceof Collection)
190189 issuedAt );
191190 }
192191
193- private <T > T getClaim (ClaimSupplier <T > supplier , String claimName ) throws ValidateException {
192+ private <T > T getClaim (ClaimSupplier <T > supplier , String claimName ) throws JwtValidatorException {
194193 try {
195194 T value = supplier .get ();
196195 log .debug ("getClaim - {}: {}" , claimName , value );
197196 return value ;
198197 } catch (MalformedClaimException e ) {
199- throw new ValidateException (String .format ("Could not extract the '%s' claim from the access token" , claimName ), e );
198+ throw new JwtValidatorException (String .format ("Could not extract the '%s' claim from the access token" , claimName ), e );
200199 }
201200 }
202201
@@ -205,5 +204,4 @@ public interface ClaimSupplier<T> {
205204 T get () throws MalformedClaimException ;
206205
207206 }
208-
209207}
0 commit comments