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));
+ }
+}