Skip to content

Commit 03b54c1

Browse files
exceptionfactorydan-s1
authored andcommitted
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]>
1 parent 50794ce commit 03b54c1

File tree

2 files changed

+20
-43
lines changed

2 files changed

+20
-43
lines changed

.github/workflows/integration-tests.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ on:
2727
- '**/test/**/IT*.java'
2828
- 'nifi-mock/**'
2929
- 'nifi-extension-bundles/nifi-kafka-bundle/**'
30+
- 'nifi-registry/nifi-registry-core/nifi-registry-web-api/**'
3031
pull_request:
3132
paths:
3233
- '.github/workflows/integration-tests.yml'
@@ -38,6 +39,7 @@ on:
3839
- '**/test/**/IT*.java'
3940
- 'nifi-mock/**'
4041
- 'nifi-extension-bundles/nifi-kafka-bundle/**'
42+
- 'nifi-registry/nifi-registry-core/nifi-registry-web-api/**'
4143

4244
env:
4345
DEFAULT_MAVEN_OPTS: >-

nifi-registry/nifi-registry-core/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/jwt/JwtService.java

Lines changed: 18 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
import io.jsonwebtoken.JwtException;
2424
import io.jsonwebtoken.Jwts;
2525
import io.jsonwebtoken.MalformedJwtException;
26-
import io.jsonwebtoken.SigningKeyResolverAdapter;
2726
import io.jsonwebtoken.UnsupportedJwtException;
2827
import io.jsonwebtoken.security.Keys;
2928
import io.jsonwebtoken.security.MacAlgorithm;
@@ -52,7 +51,6 @@ public class JwtService {
5251
private static final org.slf4j.Logger logger = LoggerFactory.getLogger(JwtService.class);
5352

5453
private static final MacAlgorithm SIGNATURE_ALGORITHM = Jwts.SIG.HS256;
55-
private static final String KEY_ID_CLAIM = "kid";
5654
private static final String USERNAME_CLAIM = "preferred_username";
5755
private static final String GROUPS_CLAIM = "groups";
5856

@@ -101,27 +99,25 @@ public Set<String> getUserGroupsFromToken(final Jws<Claims> jws) throws JwtExcep
10199

102100
private Jws<Claims> parseTokenFromBase64EncodedString(final String base64EncodedToken) throws JwtException {
103101
try {
104-
return Jwts.parser().setSigningKeyResolver(new SigningKeyResolverAdapter() {
105-
@Override
106-
public byte[] resolveSigningKeyBytes(JwsHeader header, Claims claims) {
107-
final String identity = claims.getSubject();
102+
return Jwts.parser().keyLocator(header -> {
103+
if (header instanceof JwsHeader jwsHeader) {
104+
final String keyId = jwsHeader.getKeyId();
105+
if (keyId == null) {
106+
throw new UnsupportedJwtException("Key Identifier not found in header");
107+
}
108108

109-
// Get the key based on the key id in the claims
110-
final String keyId = claims.get(KEY_ID_CLAIM, String.class);
111109
final Key key = keyService.getKey(keyId);
112-
113-
// Ensure we were able to find a key that was previously issued by this key service for this user
114110
if (key == null || key.getKey() == null) {
115-
throw new UnsupportedJwtException("Unable to determine signing key for " + identity + " [kid: " + keyId + "]");
111+
throw new UnsupportedJwtException("Signing Key [%s] not found".formatted(keyId));
116112
}
117-
118-
return key.getKey().getBytes(StandardCharsets.UTF_8);
113+
final byte[] keyBytes = key.getKey().getBytes(StandardCharsets.UTF_8);
114+
return Keys.hmacShaKeyFor(keyBytes);
115+
} else {
116+
throw new UnsupportedJwtException("JWE is not currently supported");
119117
}
120118
}).build().parseSignedClaims(base64EncodedToken);
121119
} catch (final MalformedJwtException | UnsupportedJwtException | SignatureException | ExpiredJwtException | IllegalArgumentException e) {
122-
// TODO: Exercise all exceptions to ensure none leak key material to logs
123-
final String errorMessage = "Unable to validate the access token.";
124-
throw new JwtException(errorMessage, e);
120+
throw new JwtException("Access Token validation failed", e);
125121
}
126122
}
127123

@@ -146,10 +142,6 @@ public String generateSignedToken(final AuthenticationResponse authenticationRes
146142
null);
147143
}
148144

149-
public String generateSignedToken(String identity, String preferredUsername, String issuer, String audience, long expirationMillis) throws JwtException {
150-
return this.generateSignedToken(identity, preferredUsername, issuer, audience, expirationMillis, null);
151-
}
152-
153145
public String generateSignedToken(String identity, String preferredUsername, String issuer, String audience, long expirationMillis, Collection<String> groups) throws JwtException {
154146
if (identity == null || StringUtils.isEmpty(identity)) {
155147
String errorMessage = "Cannot generate a JWT for a token with an empty identity";
@@ -168,14 +160,16 @@ public String generateSignedToken(String identity, String preferredUsername, Str
168160
// Get/create the key for this user
169161
final Key key = keyService.getOrCreateKey(identity);
170162
final byte[] keyBytes = key.getKey().getBytes(StandardCharsets.UTF_8);
163+
final String keyId = key.getId();
171164

172-
// TODO: Implement "jti" claim with nonce to prevent replay attacks and allow blacklisting of revoked tokens
173-
// Build the token
174-
return Jwts.builder().subject(identity)
165+
return Jwts.builder()
166+
.header()
167+
.keyId(keyId)
168+
.and()
169+
.subject(identity)
175170
.issuer(issuer)
176171
.audience().add(audience).and()
177172
.claim(USERNAME_CLAIM, preferredUsername)
178-
.claim(KEY_ID_CLAIM, key.getId())
179173
.claim(GROUPS_CLAIM, groups != null ? groups : Collections.EMPTY_LIST)
180174
.issuedAt(now.getTime())
181175
.expiration(expiration.getTime())
@@ -216,23 +210,4 @@ private static long validateTokenExpiration(long proposedTokenExpiration, String
216210

217211
return proposedTokenExpiration;
218212
}
219-
220-
private static String describe(AuthenticationResponse authenticationResponse) {
221-
Calendar expirationTime = Calendar.getInstance();
222-
expirationTime.setTimeInMillis(authenticationResponse.getExpiration());
223-
long remainingTime = expirationTime.getTimeInMillis() - Calendar.getInstance().getTimeInMillis();
224-
225-
return new StringBuilder("LoginAuthenticationToken for ")
226-
.append(authenticationResponse.getUsername())
227-
.append(" issued by ")
228-
.append(authenticationResponse.getIssuer())
229-
.append(" expiring at ")
230-
.append(expirationTime.getTime().toInstant().toString())
231-
.append(" [")
232-
.append(authenticationResponse.getExpiration())
233-
.append(" ms, ")
234-
.append(remainingTime)
235-
.append(" ms remaining]")
236-
.toString();
237-
}
238213
}

0 commit comments

Comments
 (0)