This is an automated email from the ASF dual-hosted git repository.
roryqi pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gravitino.git
The following commit(s) were added to refs/heads/main by this push:
new f88d07517b [#10746] fix(auth): Cache JWKSource and downgrade token
validation log to WARN (#10723)
f88d07517b is described below
commit f88d07517b67086a9e71041a3ad10a975812fb02
Author: Bharath Krishna <[email protected]>
AuthorDate: Sat Apr 11 00:29:29 2026 -0700
[#10746] fix(auth): Cache JWKSource and downgrade token validation log to
WARN (#10723)
### What changes were proposed in this pull request?
1. Cache `JWKSource` in `initialize()` as an instance field instead of
creating a new one per `validateToken()` call.
2. Downgrade the catch-block log from `LOG.error` to `LOG.warn` for
token validation failures.
3. Remove the now-unused `createJwkSource()` private method.
### Why are the changes needed?
- **Per-request `JWKSource` creation**:
`JWKSourceBuilder.create(url).build()` was called on every
`validateToken()` invocation, potentially triggering an outbound HTTP
fetch to the JWKS endpoint each time. The Nimbus `JWKSource` already
handles key caching and automatic rotation internally, so a single
instance should be reused.
- **Incorrect log level**: Expired or invalid client tokens are not
server errors. Logging them at `ERROR` pollutes error-level
monitoring/alerting for issues the server cannot fix. `WARN` is the
appropriate level.
### Does this PR introduce _any_ user-facing change?
No. Validation behavior is unchanged. Log level changes from ERROR to
WARN for client-side token failures.
### How was this patch tested?
Existing unit tests in `TestJwksTokenValidator` cover the validation
paths. The change is a straightforward refactoring of when the
`JWKSource` is constructed (startup vs per-call) with no behavioral
change.
---
.../server/authentication/JwksTokenValidator.java | 29 +++++++++-------------
1 file changed, 12 insertions(+), 17 deletions(-)
diff --git
a/server-common/src/main/java/org/apache/gravitino/server/authentication/JwksTokenValidator.java
b/server-common/src/main/java/org/apache/gravitino/server/authentication/JwksTokenValidator.java
index 4cbc9a1edd..490e477cd9 100644
---
a/server-common/src/main/java/org/apache/gravitino/server/authentication/JwksTokenValidator.java
+++
b/server-common/src/main/java/org/apache/gravitino/server/authentication/JwksTokenValidator.java
@@ -63,6 +63,7 @@ public class JwksTokenValidator implements
OAuthTokenValidator {
private long allowSkewSeconds;
private PrincipalMapper principalMapper;
private GroupMapper groupMapper;
+ private JWKSource<SecurityContext> jwkSource;
@Override
public void initialize(Config config) {
@@ -89,12 +90,17 @@ public class JwksTokenValidator implements
OAuthTokenValidator {
"JWKS URI must be configured when using JWKS-based OAuth providers");
}
- // Validate JWKS URI format
+ // Create the JWK source once at initialization.
JWKSourceBuilder.create(url).build() enables
+ // rate-limiting (min 30 s between URL fetches) and caching with
refresh-ahead by default:
+ // - Cache TTL: 5 minutes (DEFAULT_CACHE_TIME_TO_LIVE)
+ // - Refresh-ahead: 30 seconds before expiration on a background thread
+ // The Nimbus library handles key rotation transparently within these
defaults.
try {
- new URL(jwksUri);
+ this.jwkSource = JWKSourceBuilder.create(new URL(jwksUri)).build();
} catch (Exception e) {
- LOG.error("Invalid JWKS URI format: {}", jwksUri);
- throw new IllegalArgumentException("Invalid JWKS URI format: " +
jwksUri, e);
+ LOG.error("Failed to create JWKS source from URI: {}", jwksUri, e);
+ throw new IllegalArgumentException(
+ "Invalid JWKS URI or failed to create JWKS source: " + jwksUri, e);
}
}
@@ -115,7 +121,6 @@ public class JwksTokenValidator implements
OAuthTokenValidator {
signedJWT = SignedJWT.parse(token);
// Set up JWKS source and processor
- JWKSource<SecurityContext> jwkSource = createJwkSource();
JWSAlgorithm algorithm =
JWSAlgorithm.parse(signedJWT.getHeader().getAlgorithm().getName());
JWSKeySelector<SecurityContext> keySelector =
new JWSVerificationKeySelector<>(algorithm, jwkSource);
@@ -162,8 +167,8 @@ public class JwksTokenValidator implements
OAuthTokenValidator {
return userPrincipal;
} catch (Exception e) {
- LOG.error(
- "JWKS JWT validation error for principal [{}]: {}",
+ LOG.warn(
+ "JWKS JWT validation failed for principal [{}]: {}",
extractPrincipalForLogging(signedJWT),
e.getMessage());
throw new UnauthorizedException(e, "JWKS JWT validation error");
@@ -186,16 +191,6 @@ public class JwksTokenValidator implements
OAuthTokenValidator {
}
}
- /** Creates a JWK source from the configured JWKS URI. */
- private JWKSource<SecurityContext> createJwkSource() throws Exception {
- try {
- return JWKSourceBuilder.create(new URL(jwksUri)).build();
- } catch (Exception e) {
- LOG.error("Failed to create JWKS source from URI: {}", jwksUri, e);
- throw new Exception("Failed to create JWKS source: " + e.getMessage(),
e);
- }
- }
-
/** Extracts the principal from the validated JWT claims using configured
field(s). */
private String extractPrincipal(JWTClaimsSet validatedClaims) {
// Try the principal field(s) one by one in order