This is an automated email from the ASF dual-hosted git repository.

ramackri pushed a commit to branch ranger-2.9
in repository https://gitbox.apache.org/repos/asf/ranger.git


The following commit(s) were added to refs/heads/ranger-2.9 by this push:
     new 222f58a7e RANGER-5693: Stop logging full JWT bearer tokens on 
validation failure (#1089)
222f58a7e is described below

commit 222f58a7ed635c33d2cf4a53dc9db4bb71f0a3d5
Author: Ramachandran Krishnan <[email protected]>
AuthorDate: Sun Jul 19 10:36:34 2026 +0530

    RANGER-5693: Stop logging full JWT bearer tokens on validation failure 
(#1089)
    
    Back-port of 6723329285d1287df59e2424200e218e768e7dda from master.
    
    Replace jwtToken.serialize() in the validation-failure WARN path with
    safeJwtLogContext() that logs only non-sensitive metadata. Add null-safety
    for missing audience claims and unit tests for the safe logging path.
    
    Co-authored-by: ramk <[email protected]>
---
 .../authz/handler/jwt/RangerJwtAuthHandler.java    |  42 +++++++--
 .../handler/jwt/TestRangerJwtAuthHandler.java      | 102 +++++++++++++++++++++
 2 files changed, 135 insertions(+), 9 deletions(-)

diff --git 
a/ranger-authn/src/main/java/org/apache/ranger/authz/handler/jwt/RangerJwtAuthHandler.java
 
b/ranger-authn/src/main/java/org/apache/ranger/authz/handler/jwt/RangerJwtAuthHandler.java
index 5480fcd79..651006b10 100644
--- 
a/ranger-authn/src/main/java/org/apache/ranger/authz/handler/jwt/RangerJwtAuthHandler.java
+++ 
b/ranger-authn/src/main/java/org/apache/ranger/authz/handler/jwt/RangerJwtAuthHandler.java
@@ -33,6 +33,7 @@
 import org.slf4j.LoggerFactory;
 
 import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.JWSHeader;
 import com.nimbusds.jose.JWSObject;
 import com.nimbusds.jose.JWSVerifier;
 import com.nimbusds.jose.crypto.RSASSAVerifier;
@@ -42,6 +43,7 @@
 import com.nimbusds.jose.proc.JWSKeySelector;
 import com.nimbusds.jose.proc.JWSVerificationKeySelector;
 import com.nimbusds.jose.proc.SecurityContext;
+import com.nimbusds.jwt.JWTClaimsSet;
 import com.nimbusds.jwt.SignedJWT;
 import com.nimbusds.jwt.proc.ConfigurableJWTProcessor;
 
@@ -113,7 +115,7 @@ protected AuthenticationToken authenticate(final String 
jwtAuthHeader) {
                         }
                         token = new AuthenticationToken(userName, userName, 
TYPE);
                     } else {
-                        LOG.warn("RangerJwtAuthHandler.authenticate(): 
Validation failed for JWT token: [{}] ", jwtToken.serialize());
+                        LOG.warn("JWT validation failed ({})", 
safeJwtLogContext(jwtToken));
                     }
                 } catch (ParseException pe) {
                     LOG.warn("RangerJwtAuthHandler.authenticate(): Unable to 
parse the JWT token", pe);
@@ -244,15 +246,16 @@ protected boolean validateAudiences(final SignedJWT 
jwtToken) {
             if (audiences == null) {
                 valid = true;
             } else {
-                // if any of the configured audiences is found then consider it
-                // acceptable
-                for (String aud : tokenAudienceList) {
-                    if (audiences.contains(aud)) {
-                        if (LOG.isDebugEnabled()) {
-                            LOG.debug("JWT token audience has been 
successfully validated.");
+                // if any of the configured audiences is found then consider 
it acceptable
+                if (tokenAudienceList != null) {
+                    for (String aud : tokenAudienceList) {
+                        if (audiences.contains(aud)) {
+                            if (LOG.isDebugEnabled()) {
+                                LOG.debug("JWT token audience has been 
successfully validated.");
+                            }
+                            valid = true;
+                            break;
                         }
-                        valid = true;
-                        break;
                     }
                 }
                 if (!valid) {
@@ -265,6 +268,27 @@ protected boolean validateAudiences(final SignedJWT 
jwtToken) {
         return valid;
     }
 
+    /**
+     * Build non-sensitive JWT metadata for operational logs.
+     * Never log the raw bearer token.
+     *
+     * @param jwtToken parsed JWT used to extract claim metadata
+     * @return safe diagnostic string for log output
+     */
+    protected String safeJwtLogContext(final SignedJWT jwtToken) {
+        try {
+            JWTClaimsSet claims = jwtToken.getJWTClaimsSet();
+            JWSHeader header = jwtToken.getHeader();
+            String keyId = header != null ? header.getKeyID() : null;
+            List<String> tokenAudiences = claims.getAudience();
+            String audience = tokenAudiences == null || 
tokenAudiences.isEmpty() ? null : StringUtils.join(tokenAudiences, ",");
+
+            return String.format("subject=%s, audience=%s, issuer=%s, 
keyId=%s, jwtId=%s", claims.getSubject(), audience, claims.getIssuer(), keyId, 
claims.getJWTID());
+        } catch (ParseException pe) {
+            return "claims_unparseable";
+        }
+    }
+
     /**
      * Validate that the expiration time of the JWT token has not been 
violated. If
      * it has then throw an AuthenticationException. Override this method in
diff --git 
a/ranger-authn/src/test/java/org/apache/ranger/authz/handler/jwt/TestRangerJwtAuthHandler.java
 
b/ranger-authn/src/test/java/org/apache/ranger/authz/handler/jwt/TestRangerJwtAuthHandler.java
new file mode 100644
index 000000000..ef032bb25
--- /dev/null
+++ 
b/ranger-authn/src/test/java/org/apache/ranger/authz/handler/jwt/TestRangerJwtAuthHandler.java
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.ranger.authz.handler.jwt;
+
+import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jose.JWSHeader;
+import com.nimbusds.jose.proc.JWSKeySelector;
+import com.nimbusds.jose.proc.SecurityContext;
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.SignedJWT;
+import com.nimbusds.jwt.proc.ConfigurableJWTProcessor;
+import org.apache.ranger.authz.handler.RangerAuth;
+import org.junit.jupiter.api.Test;
+
+import javax.servlet.http.HttpServletRequest;
+
+import java.util.Arrays;
+import java.util.Date;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class TestRangerJwtAuthHandler {
+    static class TestHandler extends RangerJwtAuthHandler {
+        @Override
+        public ConfigurableJWTProcessor<SecurityContext> 
getJwtProcessor(JWSKeySelector<SecurityContext> keySelector) {
+            return null;
+        }
+
+        @Override
+        public RangerAuth authenticate(HttpServletRequest request) {
+            return null;
+        }
+
+        boolean callValidateAudiences(SignedJWT jwt) {
+            return validateAudiences(jwt);
+        }
+
+        String callSafeJwtLogContext(SignedJWT jwt) {
+            return safeJwtLogContext(jwt);
+        }
+    }
+
+    private static SignedJWT jwtWithIssuer(String issuer) {
+        JWTClaimsSet claims = new JWTClaimsSet.Builder()
+                .issuer(issuer)
+                .subject("user")
+                .expirationTime(new Date(System.currentTimeMillis() + 60_000))
+                .build();
+
+        return new SignedJWT(new JWSHeader(JWSAlgorithm.RS256), claims);
+    }
+
+    @Test
+    void 
validateAudiencesFalse_whenTokenAudienceMissingAndAudiencesConfigured() {
+        TestHandler handler = new TestHandler();
+        handler.audiences = Arrays.asList("service-a");
+
+        SignedJWT jwt = jwtWithIssuer("test-issuer");
+
+        assertFalse(handler.callValidateAudiences(jwt));
+    }
+
+    @Test
+    void safeJwtLogContext_includesMetadataWithoutRawToken() throws Exception {
+        TestHandler handler = new TestHandler();
+        String header = "eyJhbGciOiJSUzI1NiIsImtpZCI6ImtpZC1hYmMifQ";
+        String payload = 
"eyJzdWIiOiJmMDE1X3JlcGxheV91c2VyIiwiYXVkIjoic2VydmljZS1hIiwiaXNzIjoidGVzdC1pc3N1ZXIiLCJqdGkiOiJqdGktMTIzIiwiZXhwIjoxOTk5OTk5OTk5fQ";
+        String signature = "abcd";
+        String serialized = header + "." + payload + "." + signature;
+
+        String context = 
handler.callSafeJwtLogContext(SignedJWT.parse(serialized));
+
+        assertNotNull(context);
+        assertTrue(context.contains("subject=f015_replay_user"));
+        assertTrue(context.contains("audience=service-a"));
+        assertTrue(context.contains("issuer=test-issuer"));
+        assertTrue(context.contains("keyId=kid-abc"));
+        assertTrue(context.contains("jwtId=jti-123"));
+        assertFalse(context.contains(serialized));
+        assertFalse(context.contains(header));
+        assertFalse(context.contains(payload));
+        assertFalse(context.contains(signature));
+    }
+}

Reply via email to