Skip to content

Commit 587f403

Browse files
RyanL1997MaciejMierzwapeternied
authored
[Backport 2.x] Switch from org.apache.cxf.rs.security.jose to com.nimbusds.jose.jwk. (#3595)
### Description Switch from `org.apache.cxf.rs.security.jose` to `com.nimbusds.jose.jwk`. --------- Signed-off-by: Peter Nied <petern@amazon.com> Signed-off-by: Maciej Mierzwa <dev.maciej.mierzwa@gmail.com> Signed-off-by: Ryan Liang <jiallian@amazon.com> Co-authored-by: MaciejMierzwa <dev.maciej.mierzwa@gmail.com> Co-authored-by: Peter Nied <petern@amazon.com>
1 parent 13917f9 commit 587f403

22 files changed

Lines changed: 675 additions & 540 deletions

build.gradle

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -564,6 +564,7 @@ dependencies {
564564
implementation 'commons-cli:commons-cli:1.5.0'
565565
implementation "org.bouncycastle:bcprov-jdk15to18:${versions.bouncycastle}"
566566
implementation 'org.ldaptive:ldaptive:1.2.3'
567+
implementation 'com.nimbusds:nimbus-jose-jwt:9.31'
567568

568569
//JWT
569570
implementation "io.jsonwebtoken:jjwt-api:${jjwt_version}"
@@ -587,9 +588,6 @@ dependencies {
587588

588589
runtimeOnly 'net.minidev:accessors-smart:2.4.7'
589590

590-
implementation("org.apache.cxf:cxf-rt-rs-security-jose:${apache_cxf_version}") {
591-
exclude(group: 'jakarta.activation', module: 'jakarta.activation-api')
592-
}
593591
runtimeOnly "org.apache.cxf:cxf-core:${apache_cxf_version}"
594592
implementation "org.apache.cxf:cxf-rt-rs-json-basic:${apache_cxf_version}"
595593
runtimeOnly "org.apache.cxf:cxf-rt-security:${apache_cxf_version}"

checkstyle/checkstyle.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@
120120
</module>
121121
<module name="IllegalImport"> <!-- defaults to sun.* packages -->
122122
<property name="severity" value="error"/>
123+
<property name="illegalPkgs" value="org.apache.cxf.rs.security.jose"/>
123124
</module>
124125
<module name="RedundantImport">
125126
<property name="severity" value="error"/>

src/main/java/com/amazon/dlic/auth/http/jwt/AbstractHTTPJwtAuthenticator.java

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,16 @@
1616
import java.nio.file.Path;
1717
import java.security.AccessController;
1818
import java.security.PrivilegedAction;
19+
import java.text.ParseException;
1920
import java.util.Collection;
2021
import java.util.Map;
2122
import java.util.Optional;
2223
import java.util.Map.Entry;
2324
import java.util.regex.Pattern;
2425

2526
import com.google.common.annotations.VisibleForTesting;
26-
import org.apache.cxf.rs.security.jose.jwt.JwtClaims;
27-
import org.apache.cxf.rs.security.jose.jwt.JwtToken;
27+
import com.nimbusds.jwt.JWTClaimsSet;
28+
import com.nimbusds.jwt.SignedJWT;
2829
import org.apache.http.HttpStatus;
2930
import org.apache.logging.log4j.LogManager;
3031
import org.apache.logging.log4j.Logger;
@@ -112,37 +113,34 @@ private AuthCredentials extractCredentials0(final SecurityRequest request) throw
112113
return null;
113114
}
114115

115-
JwtToken jwt;
116+
SignedJWT jwt;
117+
JWTClaimsSet claimsSet;
116118

117119
try {
118120
jwt = jwtVerifier.getVerifiedJwtToken(jwtString);
121+
claimsSet = jwt.getJWTClaimsSet();
119122
} catch (AuthenticatorUnavailableException e) {
120123
log.info(e.toString());
121124
throw new OpenSearchSecurityException(e.getMessage(), RestStatus.SERVICE_UNAVAILABLE);
122-
} catch (BadCredentialsException e) {
125+
} catch (BadCredentialsException | ParseException e) {
123126
log.info("Extracting JWT token from {} failed", jwtString, e);
124127
return null;
125128
}
126129

127-
JwtClaims claims = jwt.getClaims();
128-
129-
final String subject = extractSubject(claims);
130-
130+
final String subject = extractSubject(claimsSet);
131131
if (subject == null) {
132132
log.error("No subject found in JWT token");
133133
return null;
134134
}
135135

136-
final String[] roles = extractRoles(claims);
137-
136+
final String[] roles = extractRoles(claimsSet);
138137
final AuthCredentials ac = new AuthCredentials(subject, roles).markComplete();
139138

140-
for (Entry<String, Object> claim : claims.asMap().entrySet()) {
139+
for (Entry<String, Object> claim : claimsSet.getClaims().entrySet()) {
141140
ac.addAttribute("attr.jwt." + claim.getKey(), String.valueOf(claim.getValue()));
142141
}
143142

144143
return ac;
145-
146144
}
147145

148146
protected String getJwtTokenString(SecurityRequest request) {
@@ -174,7 +172,7 @@ protected String getJwtTokenString(SecurityRequest request) {
174172
}
175173

176174
@VisibleForTesting
177-
public String extractSubject(JwtClaims claims) {
175+
public String extractSubject(JWTClaimsSet claims) {
178176
String subject = claims.getSubject();
179177

180178
if (subjectKey != null) {
@@ -204,7 +202,7 @@ public String extractSubject(JwtClaims claims) {
204202

205203
@SuppressWarnings("unchecked")
206204
@VisibleForTesting
207-
public String[] extractRoles(JwtClaims claims) {
205+
public String[] extractRoles(JWTClaimsSet claims) {
208206
if (rolesKey == null) {
209207
return new String[0];
210208
}

src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/JwtVerifier.java

Lines changed: 47 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,24 @@
1212
package com.amazon.dlic.auth.http.jwt.keybyoidc;
1313

1414
import com.google.common.base.Strings;
15+
import com.nimbusds.jose.Algorithm;
16+
import com.nimbusds.jose.JOSEException;
17+
import com.nimbusds.jose.JWSVerifier;
18+
import com.nimbusds.jose.jwk.JWK;
19+
import com.nimbusds.jose.jwk.OctetSequenceKey;
20+
import com.nimbusds.jose.crypto.factories.DefaultJWSVerifierFactory;
21+
import com.nimbusds.jose.proc.SimpleSecurityContext;
22+
import com.nimbusds.jwt.JWTClaimsSet;
23+
import com.nimbusds.jwt.SignedJWT;
24+
import com.nimbusds.jwt.proc.BadJWTException;
25+
import com.nimbusds.jwt.proc.DefaultJWTClaimsVerifier;
1526
import org.apache.commons.lang3.StringEscapeUtils;
16-
import org.apache.cxf.rs.security.jose.jwa.SignatureAlgorithm;
17-
import org.apache.cxf.rs.security.jose.jwk.JsonWebKey;
18-
import org.apache.cxf.rs.security.jose.jwk.KeyType;
19-
import org.apache.cxf.rs.security.jose.jwk.PublicKeyUse;
20-
import org.apache.cxf.rs.security.jose.jws.JwsJwtCompactConsumer;
21-
import org.apache.cxf.rs.security.jose.jws.JwsSignatureVerifier;
22-
import org.apache.cxf.rs.security.jose.jws.JwsUtils;
23-
import org.apache.cxf.rs.security.jose.jwt.JwtClaims;
24-
import org.apache.cxf.rs.security.jose.jwt.JwtException;
25-
import org.apache.cxf.rs.security.jose.jwt.JwtToken;
26-
import org.apache.cxf.rs.security.jose.jwt.JwtUtils;
2727
import org.apache.logging.log4j.LogManager;
2828
import org.apache.logging.log4j.Logger;
2929

30+
import java.text.ParseException;
31+
import java.util.Collections;
32+
3033
public class JwtVerifier {
3134

3235
private final static Logger log = LogManager.getLogger(JwtVerifier.class);
@@ -43,31 +46,24 @@ public JwtVerifier(KeyProvider keyProvider, int clockSkewToleranceSeconds, Strin
4346
this.requiredAudience = requiredAudience;
4447
}
4548

46-
public JwtToken getVerifiedJwtToken(String encodedJwt) throws BadCredentialsException {
49+
public SignedJWT getVerifiedJwtToken(String encodedJwt) throws BadCredentialsException {
4750
try {
48-
JwsJwtCompactConsumer jwtConsumer = new JwsJwtCompactConsumer(encodedJwt);
49-
JwtToken jwt = jwtConsumer.getJwtToken();
51+
SignedJWT jwt = SignedJWT.parse(encodedJwt);
5052

51-
String escapedKid = jwt.getJwsHeaders().getKeyId();
53+
String escapedKid = jwt.getHeader().getKeyID();
5254
String kid = escapedKid;
5355
if (!Strings.isNullOrEmpty(kid)) {
5456
kid = StringEscapeUtils.unescapeJava(escapedKid);
5557
}
56-
JsonWebKey key = keyProvider.getKey(kid);
57-
58-
// Algorithm is not mandatory for the key material, so we set it to the same as the JWT
59-
if (key.getAlgorithm() == null && key.getPublicKeyUse() == PublicKeyUse.SIGN && key.getKeyType() == KeyType.RSA) {
60-
key.setAlgorithm(jwt.getJwsHeaders().getAlgorithm());
61-
}
62-
63-
JwsSignatureVerifier signatureVerifier = getInitializedSignatureVerifier(key, jwt);
58+
JWK key = keyProvider.getKey(kid);
6459

65-
boolean signatureValid = jwtConsumer.verifySignatureWith(signatureVerifier);
60+
JWSVerifier signatureVerifier = getInitializedSignatureVerifier(key, jwt);
61+
boolean signatureValid = jwt.verify(signatureVerifier);
6662

6763
if (!signatureValid && Strings.isNullOrEmpty(kid)) {
6864
key = keyProvider.getKeyAfterRefresh(null);
6965
signatureVerifier = getInitializedSignatureVerifier(key, jwt);
70-
signatureValid = jwtConsumer.verifySignatureWith(signatureVerifier);
66+
signatureValid = jwt.verify(signatureVerifier);
7167
}
7268

7369
if (!signatureValid) {
@@ -77,18 +73,18 @@ public JwtToken getVerifiedJwtToken(String encodedJwt) throws BadCredentialsExce
7773
validateClaims(jwt);
7874

7975
return jwt;
80-
} catch (JwtException e) {
76+
} catch (JOSEException | ParseException | BadJWTException e) {
8177
throw new BadCredentialsException(e.getMessage(), e);
8278
}
8379
}
8480

85-
private void validateSignatureAlgorithm(JsonWebKey key, JwtToken jwt) throws BadCredentialsException {
86-
if (Strings.isNullOrEmpty(key.getAlgorithm())) {
81+
private void validateSignatureAlgorithm(JWK key, SignedJWT jwt) throws BadCredentialsException {
82+
if (key.getAlgorithm() == null || jwt.getHeader().getAlgorithm() == null) {
8783
return;
8884
}
8985

90-
SignatureAlgorithm keyAlgorithm = SignatureAlgorithm.getAlgorithm(key.getAlgorithm());
91-
SignatureAlgorithm tokenAlgorithm = SignatureAlgorithm.getAlgorithm(jwt.getJwsHeaders().getAlgorithm());
86+
Algorithm keyAlgorithm = key.getAlgorithm();
87+
Algorithm tokenAlgorithm = jwt.getHeader().getAlgorithm();
9288

9389
if (!keyAlgorithm.equals(tokenAlgorithm)) {
9490
throw new BadCredentialsException(
@@ -97,38 +93,48 @@ private void validateSignatureAlgorithm(JsonWebKey key, JwtToken jwt) throws Bad
9793
}
9894
}
9995

100-
private JwsSignatureVerifier getInitializedSignatureVerifier(JsonWebKey key, JwtToken jwt) throws BadCredentialsException,
101-
JwtException {
96+
private JWSVerifier getInitializedSignatureVerifier(JWK key, SignedJWT jwt) throws BadCredentialsException, JOSEException {
10297

10398
validateSignatureAlgorithm(key, jwt);
104-
JwsSignatureVerifier result = JwsUtils.getSignatureVerifier(key, jwt.getJwsHeaders().getSignatureAlgorithm());
99+
final JWSVerifier result;
100+
if (key.getClass() == OctetSequenceKey.class) {
101+
result = new DefaultJWSVerifierFactory().createJWSVerifier(jwt.getHeader(), key.toOctetSequenceKey().toSecretKey());
102+
} else {
103+
result = new DefaultJWSVerifierFactory().createJWSVerifier(jwt.getHeader(), key.toRSAKey().toRSAPublicKey());
104+
}
105+
105106
if (result == null) {
106107
throw new BadCredentialsException("Cannot verify JWT");
107108
} else {
108109
return result;
109110
}
110111
}
111112

112-
private void validateClaims(JwtToken jwt) throws JwtException {
113-
JwtClaims claims = jwt.getClaims();
113+
private void validateClaims(SignedJWT jwt) throws ParseException, BadJWTException {
114+
JWTClaimsSet claims = jwt.getJWTClaimsSet();
114115

115116
if (claims != null) {
116-
JwtUtils.validateJwtExpiry(claims, clockSkewToleranceSeconds, false);
117-
JwtUtils.validateJwtNotBefore(claims, clockSkewToleranceSeconds, false);
117+
DefaultJWTClaimsVerifier<SimpleSecurityContext> claimsVerifier = new DefaultJWTClaimsVerifier<>(
118+
requiredAudience,
119+
null,
120+
Collections.emptySet()
121+
);
122+
claimsVerifier.setMaxClockSkew(clockSkewToleranceSeconds);
123+
claimsVerifier.verify(claims, null);
118124
validateRequiredAudienceAndIssuer(claims);
119125
}
120126
}
121127

122-
private void validateRequiredAudienceAndIssuer(JwtClaims claims) {
123-
String audience = claims.getAudience();
128+
private void validateRequiredAudienceAndIssuer(JWTClaimsSet claims) throws BadJWTException {
129+
String audience = claims.getAudience().stream().findFirst().orElse("");
124130
String issuer = claims.getIssuer();
125131

126132
if (!Strings.isNullOrEmpty(requiredAudience) && !requiredAudience.equals(audience)) {
127-
throw new JwtException("Invalid audience");
133+
throw new BadJWTException("Invalid audience");
128134
}
129135

130136
if (!Strings.isNullOrEmpty(requiredIssuer) && !requiredIssuer.equals(issuer)) {
131-
throw new JwtException("Invalid issuer");
137+
throw new BadJWTException("Invalid issuer");
132138
}
133139
}
134140
}

src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/KeyProvider.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@
1111

1212
package com.amazon.dlic.auth.http.jwt.keybyoidc;
1313

14-
import org.apache.cxf.rs.security.jose.jwk.JsonWebKey;
14+
import com.nimbusds.jose.jwk.JWK;
1515

1616
public interface KeyProvider {
17-
public JsonWebKey getKey(String kid) throws AuthenticatorUnavailableException, BadCredentialsException;
17+
JWK getKey(String kid) throws AuthenticatorUnavailableException, BadCredentialsException;
1818

19-
public JsonWebKey getKeyAfterRefresh(String kid) throws AuthenticatorUnavailableException, BadCredentialsException;
19+
JWK getKeyAfterRefresh(String kid) throws AuthenticatorUnavailableException, BadCredentialsException;
2020
}

src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/KeySetProvider.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111

1212
package com.amazon.dlic.auth.http.jwt.keybyoidc;
1313

14-
import org.apache.cxf.rs.security.jose.jwk.JsonWebKeys;
14+
import com.nimbusds.jose.jwk.JWKSet;
1515

1616
@FunctionalInterface
1717
public interface KeySetProvider {
18-
JsonWebKeys get() throws AuthenticatorUnavailableException;
18+
JWKSet get() throws AuthenticatorUnavailableException;
1919
}

src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/KeySetRetriever.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@
1212
package com.amazon.dlic.auth.http.jwt.keybyoidc;
1313

1414
import java.io.IOException;
15+
import java.text.ParseException;
1516

17+
import com.nimbusds.jose.jwk.JWKSet;
1618
import joptsimple.internal.Strings;
17-
import org.apache.cxf.rs.security.jose.jwk.JsonWebKeys;
18-
import org.apache.cxf.rs.security.jose.jwk.JwkUtils;
1919
import org.apache.http.HttpEntity;
2020
import org.apache.http.StatusLine;
2121
import org.apache.http.client.cache.HttpCacheContext;
@@ -68,7 +68,7 @@ public class KeySetRetriever implements KeySetProvider {
6868
configureCache(useCacheForOidConnectEndpoint);
6969
}
7070

71-
public JsonWebKeys get() throws AuthenticatorUnavailableException {
71+
public JWKSet get() throws AuthenticatorUnavailableException {
7272
String uri = getJwksUri();
7373

7474
try (CloseableHttpClient httpClient = createHttpClient(null)) {
@@ -95,10 +95,11 @@ public JsonWebKeys get() throws AuthenticatorUnavailableException {
9595
if (httpEntity == null) {
9696
throw new AuthenticatorUnavailableException("Error while getting " + uri + ": Empty response entity");
9797
}
98-
99-
JsonWebKeys keySet = JwkUtils.readJwkSet(httpEntity.getContent());
98+
JWKSet keySet = JWKSet.load(httpEntity.getContent());
10099

101100
return keySet;
101+
} catch (ParseException e) {
102+
throw new RuntimeException(e);
102103
}
103104
} catch (IOException e) {
104105
throw new AuthenticatorUnavailableException("Error while getting " + uri + ": " + e, e);

0 commit comments

Comments
 (0)