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

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


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

commit 6723329285d1287df59e2424200e218e768e7dda
Author: Ramachandran Krishnan <[email protected]>
AuthorDate: Sun Jul 19 09:52:22 2026 +0530

    RANGER-5693: Stop logging full JWT bearer tokens on validation failure 
(#1081)
    
    * RANGER-5693: Stop logging full JWT bearer tokens on validation failure
    
    Replace jwtToken.serialize() in the validation-failure WARN path with
    safeJwtLogContext() that logs only non-sensitive metadata and a SHA-256
    hash for log correlation. Add null-safety for missing audience claims
    and unit tests for the safe logging path.
    
    * RANGER-5693: Drop tokenHash from JWT validation failure logs.
    
    Per review feedback, remove sha256Hex/tokenHash from safeJwtLogContext;
    jwtId is sufficient for log correlation when present.
    
    * Remove tokenHash assertion from JWT test
    
    ---------
    
    Co-authored-by: ramk <[email protected]>
---
 .../authz/handler/jwt/RangerJwtAuthHandler.java    | 39 ++++++++++++++++----
 .../handler/jwt/TestRangerJwtAuthHandler.java      | 42 ++++++++++++++++++++++
 2 files changed, 74 insertions(+), 7 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 d921c7063..22c6289fa 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
@@ -19,6 +19,7 @@
 package org.apache.ranger.authz.handler.jwt;
 
 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;
@@ -30,6 +31,7 @@
 import com.nimbusds.jose.proc.JWSVerificationKeySelector;
 import com.nimbusds.jose.proc.SecurityContext;
 import com.nimbusds.jose.util.X509CertUtils;
+import com.nimbusds.jwt.JWTClaimsSet;
 import com.nimbusds.jwt.SignedJWT;
 import com.nimbusds.jwt.proc.ConfigurableJWTProcessor;
 import org.apache.commons.lang3.StringUtils;
@@ -125,7 +127,7 @@ protected String authenticate(final String jwtAuthHeader) {
                         }
                         return userName;
                     } 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);
@@ -260,13 +262,15 @@ protected boolean validateAudiences(final SignedJWT 
jwtToken) {
                 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 (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) {
@@ -304,6 +308,27 @@ protected boolean validateIssuer(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 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
index 4841a28fa..e2499f194 100644
--- 
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
@@ -30,9 +30,11 @@
 
 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 {
@@ -50,6 +52,14 @@ public RangerAuth authenticate(HttpServletRequest request) {
         boolean callValidateIssuer(SignedJWT jwt) {
             return validateIssuer(jwt);
         }
+
+        boolean callValidateAudiences(SignedJWT jwt) {
+            return validateAudiences(jwt);
+        }
+
+        String callSafeJwtLogContext(SignedJWT jwt) {
+            return safeJwtLogContext(jwt);
+        }
     }
 
     private static SignedJWT jwtWithIssuer(String issuer) {
@@ -106,4 +116,36 @@ void validateIssuerFalse_whenJwtClaimsCannotBeParsed() 
throws Exception {
 
         assertFalse(handler.callValidateIssuer(badJwt));
     }
+
+    @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