Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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) {
Expand Down Expand Up @@ -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));
}
}
Loading