This is an automated email from the ASF dual-hosted git repository.
joewitt pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git
The following commit(s) were added to refs/heads/main by this push:
new 03b54c1db6 NIFI-15211 Updated NiFi Registry JWT Key ID Resolution This
closes #10525
03b54c1db6 is described below
commit 03b54c1db6fb50a8d1d44be8468582e494b5c092
Author: exceptionfactory <[email protected]>
AuthorDate: Thu Nov 13 08:51:53 2025 -0600
NIFI-15211 Updated NiFi Registry JWT Key ID Resolution
This closes #10525
- Replaced deprecated SigningKeyResolverAdapter to KeyLocator
- Changed Key Identifier location from payload to header to work with
KeyLocator
- Added nifi-registry-web-api to integration tests workflow paths
Co-authored-by: dan-s1 <[email protected]>
Signed-off-by: Joseph Witt <[email protected]>
---
.github/workflows/integration-tests.yml | 2 +
.../security/authentication/jwt/JwtService.java | 61 +++++++---------------
2 files changed, 20 insertions(+), 43 deletions(-)
diff --git a/.github/workflows/integration-tests.yml
b/.github/workflows/integration-tests.yml
index 15bc034c37..f1bbf93b92 100644
--- a/.github/workflows/integration-tests.yml
+++ b/.github/workflows/integration-tests.yml
@@ -27,6 +27,7 @@ on:
- '**/test/**/IT*.java'
- 'nifi-mock/**'
- 'nifi-extension-bundles/nifi-kafka-bundle/**'
+ - 'nifi-registry/nifi-registry-core/nifi-registry-web-api/**'
pull_request:
paths:
- '.github/workflows/integration-tests.yml'
@@ -38,6 +39,7 @@ on:
- '**/test/**/IT*.java'
- 'nifi-mock/**'
- 'nifi-extension-bundles/nifi-kafka-bundle/**'
+ - 'nifi-registry/nifi-registry-core/nifi-registry-web-api/**'
env:
DEFAULT_MAVEN_OPTS: >-
diff --git
a/nifi-registry/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/jwt/JwtService.java
b/nifi-registry/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/jwt/JwtService.java
index 030e82bb40..e4adf1f903 100644
---
a/nifi-registry/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/jwt/JwtService.java
+++
b/nifi-registry/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/jwt/JwtService.java
@@ -23,7 +23,6 @@ import io.jsonwebtoken.JwsHeader;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.MalformedJwtException;
-import io.jsonwebtoken.SigningKeyResolverAdapter;
import io.jsonwebtoken.UnsupportedJwtException;
import io.jsonwebtoken.security.Keys;
import io.jsonwebtoken.security.MacAlgorithm;
@@ -52,7 +51,6 @@ public class JwtService {
private static final org.slf4j.Logger logger =
LoggerFactory.getLogger(JwtService.class);
private static final MacAlgorithm SIGNATURE_ALGORITHM = Jwts.SIG.HS256;
- private static final String KEY_ID_CLAIM = "kid";
private static final String USERNAME_CLAIM = "preferred_username";
private static final String GROUPS_CLAIM = "groups";
@@ -101,27 +99,25 @@ public class JwtService {
private Jws<Claims> parseTokenFromBase64EncodedString(final String
base64EncodedToken) throws JwtException {
try {
- return Jwts.parser().setSigningKeyResolver(new
SigningKeyResolverAdapter() {
- @Override
- public byte[] resolveSigningKeyBytes(JwsHeader header, Claims
claims) {
- final String identity = claims.getSubject();
+ return Jwts.parser().keyLocator(header -> {
+ if (header instanceof JwsHeader jwsHeader) {
+ final String keyId = jwsHeader.getKeyId();
+ if (keyId == null) {
+ throw new UnsupportedJwtException("Key Identifier not
found in header");
+ }
- // Get the key based on the key id in the claims
- final String keyId = claims.get(KEY_ID_CLAIM,
String.class);
final Key key = keyService.getKey(keyId);
-
- // Ensure we were able to find a key that was previously
issued by this key service for this user
if (key == null || key.getKey() == null) {
- throw new UnsupportedJwtException("Unable to determine
signing key for " + identity + " [kid: " + keyId + "]");
+ throw new UnsupportedJwtException("Signing Key [%s]
not found".formatted(keyId));
}
-
- return key.getKey().getBytes(StandardCharsets.UTF_8);
+ final byte[] keyBytes =
key.getKey().getBytes(StandardCharsets.UTF_8);
+ return Keys.hmacShaKeyFor(keyBytes);
+ } else {
+ throw new UnsupportedJwtException("JWE is not currently
supported");
}
}).build().parseSignedClaims(base64EncodedToken);
} catch (final MalformedJwtException | UnsupportedJwtException |
SignatureException | ExpiredJwtException | IllegalArgumentException e) {
- // TODO: Exercise all exceptions to ensure none leak key material
to logs
- final String errorMessage = "Unable to validate the access token.";
- throw new JwtException(errorMessage, e);
+ throw new JwtException("Access Token validation failed", e);
}
}
@@ -146,10 +142,6 @@ public class JwtService {
null);
}
- public String generateSignedToken(String identity, String
preferredUsername, String issuer, String audience, long expirationMillis)
throws JwtException {
- return this.generateSignedToken(identity, preferredUsername, issuer,
audience, expirationMillis, null);
- }
-
public String generateSignedToken(String identity, String
preferredUsername, String issuer, String audience, long expirationMillis,
Collection<String> groups) throws JwtException {
if (identity == null || StringUtils.isEmpty(identity)) {
String errorMessage = "Cannot generate a JWT for a token with an
empty identity";
@@ -168,14 +160,16 @@ public class JwtService {
// Get/create the key for this user
final Key key = keyService.getOrCreateKey(identity);
final byte[] keyBytes =
key.getKey().getBytes(StandardCharsets.UTF_8);
+ final String keyId = key.getId();
- // TODO: Implement "jti" claim with nonce to prevent replay
attacks and allow blacklisting of revoked tokens
- // Build the token
- return Jwts.builder().subject(identity)
+ return Jwts.builder()
+ .header()
+ .keyId(keyId)
+ .and()
+ .subject(identity)
.issuer(issuer)
.audience().add(audience).and()
.claim(USERNAME_CLAIM, preferredUsername)
- .claim(KEY_ID_CLAIM, key.getId())
.claim(GROUPS_CLAIM, groups != null ? groups :
Collections.EMPTY_LIST)
.issuedAt(now.getTime())
.expiration(expiration.getTime())
@@ -216,23 +210,4 @@ public class JwtService {
return proposedTokenExpiration;
}
-
- private static String describe(AuthenticationResponse
authenticationResponse) {
- Calendar expirationTime = Calendar.getInstance();
- expirationTime.setTimeInMillis(authenticationResponse.getExpiration());
- long remainingTime = expirationTime.getTimeInMillis() -
Calendar.getInstance().getTimeInMillis();
-
- return new StringBuilder("LoginAuthenticationToken for ")
- .append(authenticationResponse.getUsername())
- .append(" issued by ")
- .append(authenticationResponse.getIssuer())
- .append(" expiring at ")
- .append(expirationTime.getTime().toInstant().toString())
- .append(" [")
- .append(authenticationResponse.getExpiration())
- .append(" ms, ")
- .append(remainingTime)
- .append(" ms remaining]")
- .toString();
- }
}