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

Reply via email to