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 25bfe375f1 [#13971] fix(auth): preserve UnauthorizedException from 
PrincipalMapper in JWKS validation (#13972)
25bfe375f1 is described below

commit 25bfe375f152a91dfd9ad01d789f929be6e645ef
Author: MaSai <[email protected]>
AuthorDate: Tue Oct 20 16:27:37 2026 +0800

    [#13971] fix(auth): preserve UnauthorizedException from PrincipalMapper in 
JWKS validation (#13972)
    
    ### What changes were proposed in this pull request?
    
    `JwksTokenValidator.validateToken` now rethrows `UnauthorizedException`
    from `principalMapper.map` as-is, instead of wrapping every failure as
    `"JWKS JWT validation error"`.
    
    ### Why are the changes needed?
    
    Custom `PrincipalMapper` implementations may throw
    `UnauthorizedException` with a deliberate client message (for example
    rejecting a disabled identity). On the JWKS path that message was
    overwritten. The static-key path already preserves
    `UnauthorizedException` via `OAuth2TokenAuthenticator`.
    
    Fix: #13971
    
    ### Does this PR introduce _any_ user-facing change?
    
    When a configured `PrincipalMapper` throws `UnauthorizedException`, JWKS
    OAuth authentication returns that exception's message unchanged.
    
    ### How was this patch tested?
    
    - `./gradlew :server-common:test --tests
    
org.apache.gravitino.server.authentication.TestJwksTokenValidator.testValidateTokenPreservesUnauthorizedExceptionFromPrincipalMapper`
    
    ---------
    
    Co-authored-by: Cursor <[email protected]>
---
 .../server/authentication/JwksTokenValidator.java  |  2 +
 .../authentication/TestJwksTokenValidator.java     | 55 ++++++++++++++++++++++
 2 files changed, 57 insertions(+)

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 32e47b84e1..3605f016c9 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
@@ -170,6 +170,8 @@ public class JwksTokenValidator implements 
OAuthTokenValidator {
 
     } catch (ExpiredJWTException e) {
       throw new TokenExpiredException(e, "Authentication token is expired");
+    } catch (UnauthorizedException e) {
+      throw e;
     } catch (Exception e) {
       LOG.warn(
           "JWKS JWT validation failed for principal [{}]: {}",
diff --git 
a/server-common/src/test/java/org/apache/gravitino/server/authentication/TestJwksTokenValidator.java
 
b/server-common/src/test/java/org/apache/gravitino/server/authentication/TestJwksTokenValidator.java
index 2fb1c1f2f7..db0b56eea5 100644
--- 
a/server-common/src/test/java/org/apache/gravitino/server/authentication/TestJwksTokenValidator.java
+++ 
b/server-common/src/test/java/org/apache/gravitino/server/authentication/TestJwksTokenValidator.java
@@ -46,8 +46,10 @@ import java.util.Arrays;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.Map;
+import org.apache.commons.lang3.reflect.FieldUtils;
 import org.apache.gravitino.Config;
 import org.apache.gravitino.UserPrincipal;
+import org.apache.gravitino.auth.PrincipalMapper;
 import org.apache.gravitino.exceptions.TokenExpiredException;
 import org.apache.gravitino.exceptions.UnauthorizedException;
 import org.junit.jupiter.api.BeforeEach;
@@ -771,4 +773,57 @@ public class TestJwksTokenValidator {
       assertTrue(exception.getMessage().contains("expired"));
     }
   }
+
+  @Test
+  public void 
testValidateTokenPreservesUnauthorizedExceptionFromPrincipalMapper()
+      throws Exception {
+    RSAKey rsaKey =
+        new 
RSAKeyGenerator(2048).keyID("test-key-id").algorithm(JWSAlgorithm.RS256).generate();
+
+    JWTClaimsSet claimsSet =
+        new JWTClaimsSet.Builder()
+            .subject("disabled-user")
+            .audience("test-service")
+            .issuer("https://test-issuer.com";)
+            .expirationTime(Date.from(Instant.now().plusSeconds(3600)))
+            .issueTime(Date.from(Instant.now()))
+            .build();
+
+    SignedJWT signedJWT =
+        new SignedJWT(
+            new 
JWSHeader.Builder(JWSAlgorithm.RS256).keyID("test-key-id").build(), claimsSet);
+    signedJWT.sign(new RSASSASigner(rsaKey));
+    String tokenString = signedJWT.serialize();
+
+    try (MockedStatic<JWKSourceBuilder> mockedBuilder = 
mockStatic(JWKSourceBuilder.class)) {
+      @SuppressWarnings("unchecked")
+      JWKSource<SecurityContext> mockJwkSource = mock(JWKSource.class);
+      @SuppressWarnings("unchecked")
+      JWKSourceBuilder<SecurityContext> mockBuilder = 
mock(JWKSourceBuilder.class);
+
+      mockedBuilder.when(() -> 
JWKSourceBuilder.create(any(URL.class))).thenReturn(mockBuilder);
+      when(mockBuilder.build()).thenReturn(mockJwkSource);
+      when(mockJwkSource.get(any(), any())).thenReturn(Arrays.asList(rsaKey));
+
+      Map<String, String> config = new HashMap<>();
+      config.put(
+          "gravitino.authenticator.oauth.jwksUri", 
"https://test-jwks.com/.well-known/jwks.json";);
+      config.put("gravitino.authenticator.oauth.authority", 
"https://test-issuer.com";);
+      config.put("gravitino.authenticator.oauth.principalMapper", "regex");
+      
config.put("gravitino.authenticator.oauth.principalMapper.regex.pattern", 
"(.*)");
+      config.put("gravitino.authenticator.oauth.allowSkewSecs", "120");
+
+      validator.initialize(createConfig(config));
+
+      PrincipalMapper rejectingMapper = mock(PrincipalMapper.class);
+      when(rejectingMapper.map(any())).thenThrow(new 
UnauthorizedException("Invalid access token"));
+      FieldUtils.writeField(validator, "principalMapper", rejectingMapper, 
true);
+
+      UnauthorizedException exception =
+          assertThrows(
+              UnauthorizedException.class,
+              () -> validator.validateToken(tokenString, "test-service"));
+      assertEquals("Invalid access token", exception.getMessage());
+    }
+  }
 }

Reply via email to