Skip to content

Commit f0d8e24

Browse files
authored
Merge pull request #941 from AzureAD/avdunn/nimbus-utils
Remove and replace various com.nimbusds utils
2 parents cd495fa + 8d1f418 commit f0d8e24

File tree

10 files changed

+184
-151
lines changed

10 files changed

+184
-151
lines changed

msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AuthenticationErrorCode.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,11 @@ public class AuthenticationErrorCode {
9393
*/
9494
public final static String INVALID_REDIRECT_URI = "invalid_redirect_uri";
9595

96+
/**
97+
* Indicates token endpoint is invalid. Ensure authority and tenant are correctly set, as this endpoint is typically created using those values.
98+
*/
99+
public final static String INVALID_ENDPOINT_URI = "invalid_endpoint_uri";
100+
96101
/**
97102
* MSAL was unable to open the user-default browser. This is either because the current platform
98103
* does not support {@link java.awt.Desktop} or {@link java.awt.Desktop.Action#BROWSE}. Interactive

msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/ClientAssertion.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
package com.microsoft.aad.msal4j;
55

6-
import com.nimbusds.oauth2.sdk.auth.JWTAuthentication;
76
import lombok.EqualsAndHashCode;
87
import lombok.Getter;
98
import lombok.experimental.Accessors;
@@ -13,7 +12,7 @@
1312
@EqualsAndHashCode
1413
final class ClientAssertion implements IClientAssertion {
1514

16-
static final String assertionType = JWTAuthentication.CLIENT_ASSERTION_TYPE;
15+
static final String ASSERTION_TYPE_JWT_BEARER = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer";
1716
private final String assertion;
1817

1918
ClientAssertion(final String assertion) {

msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/ClientInfo.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
package com.microsoft.aad.msal4j;
55

66
import com.fasterxml.jackson.annotation.JsonProperty;
7-
import com.nimbusds.jose.util.StandardCharset;
87

8+
import java.nio.charset.StandardCharsets;
99
import java.util.Base64;
1010

1111
import static com.microsoft.aad.msal4j.Constants.POINT_DELIMITER;
@@ -23,9 +23,9 @@ public static ClientInfo createFromJson(String clientInfoJsonBase64Encoded) {
2323
return null;
2424
}
2525

26-
byte[] decodedInput = Base64.getUrlDecoder().decode(clientInfoJsonBase64Encoded.getBytes(StandardCharset.UTF_8));
26+
byte[] decodedInput = Base64.getUrlDecoder().decode(clientInfoJsonBase64Encoded.getBytes(StandardCharsets.UTF_8));
2727

28-
return JsonHelper.convertJsonToObject(new String(decodedInput, StandardCharset.UTF_8), ClientInfo.class);
28+
return JsonHelper.convertJsonToObject(new String(decodedInput, StandardCharsets.UTF_8), ClientInfo.class);
2929
}
3030

3131
String toAccountIdentifier() {

msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/IBroker.java

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33

44
package com.microsoft.aad.msal4j;
55

6-
import com.nimbusds.jwt.JWTParser;
7-
86
import java.net.URL;
97
import java.util.concurrent.CompletableFuture;
108

@@ -67,11 +65,10 @@ default IAuthenticationResult parseBrokerAuthResult(String authority, String idT
6765
if (idToken != null) {
6866
builder.idToken(idToken);
6967
if (accountId != null) {
70-
String idTokenJson =
71-
JWTParser.parse(idToken).getParsedParts()[1].decodeToString();
68+
IdToken idTokenObj = JsonHelper.createIdTokenFromEncodedTokenString(idToken);
69+
7270
builder.accountCacheEntity(AccountCacheEntity.create(clientInfo,
73-
Authority.createAuthority(new URL(authority)), JsonHelper.convertJsonToObject(idTokenJson,
74-
IdToken.class), null));
71+
Authority.createAuthority(new URL(authority)), idTokenObj, null));
7572
}
7673
}
7774
if (accessToken != null) {

msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/IdToken.java

Lines changed: 0 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,10 @@
44
package com.microsoft.aad.msal4j;
55

66
import com.fasterxml.jackson.annotation.JsonProperty;
7-
import com.nimbusds.jwt.JWTClaimsSet;
8-
9-
import java.text.ParseException;
10-
import java.util.HashMap;
11-
import java.util.Map;
12-
137
import java.io.Serializable;
148

159
class IdToken implements Serializable {
1610

17-
static final String ISSUER = "iss";
18-
static final String SUBJECT = "sub";
19-
static final String AUDIENCE = "aud";
20-
static final String EXPIRATION_TIME = "exp";
21-
static final String ISSUED_AT = "issuedAt";
22-
static final String NOT_BEFORE = "nbf";
23-
static final String NAME = "name";
24-
static final String PREFERRED_USERNAME = "preferred_username";
25-
static final String OBJECT_IDENTIFIER = "oid";
26-
static final String TENANT_IDENTIFIER = "tid";
27-
static final String UPN = "upn";
28-
static final String UNIQUE_NAME = "unique_name";
29-
3011
@JsonProperty("iss")
3112
protected String issuer;
3213

@@ -62,26 +43,4 @@ class IdToken implements Serializable {
6243

6344
@JsonProperty("unique_name")
6445
protected String uniqueName;
65-
66-
static IdToken createFromJWTClaims(final JWTClaimsSet claims) throws ParseException {
67-
IdToken idToken = new IdToken();
68-
69-
idToken.issuer = claims.getStringClaim(ISSUER);
70-
idToken.subject = claims.getStringClaim(SUBJECT);
71-
idToken.audience = claims.getStringClaim(AUDIENCE);
72-
73-
idToken.expirationTime = claims.getLongClaim(EXPIRATION_TIME);
74-
idToken.issuedAt = claims.getLongClaim(ISSUED_AT);
75-
idToken.notBefore = claims.getLongClaim(NOT_BEFORE);
76-
77-
idToken.name = claims.getStringClaim(NAME);
78-
idToken.preferredUsername = claims.getStringClaim(PREFERRED_USERNAME);
79-
idToken.objectIdentifier = claims.getStringClaim(OBJECT_IDENTIFIER);
80-
idToken.tenantIdentifier = claims.getStringClaim(TENANT_IDENTIFIER);
81-
82-
idToken.upn = claims.getStringClaim(UPN);
83-
idToken.uniqueName = claims.getStringClaim(UNIQUE_NAME);
84-
85-
return idToken;
86-
}
8746
}

msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/JsonHelper.java

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,8 @@
1515
import com.fasterxml.jackson.databind.JsonNode;
1616

1717
import java.io.IOException;
18-
import java.util.ArrayList;
19-
import java.util.Iterator;
20-
import java.util.Map;
21-
import java.util.Set;
18+
import java.nio.charset.StandardCharsets;
19+
import java.util.*;
2220

2321
class JsonHelper {
2422
static ObjectMapper mapper;
@@ -41,6 +39,18 @@ static <T> T convertJsonToObject(final String json, final Class<T> tClass) {
4139
}
4240
}
4341

42+
static IdToken createIdTokenFromEncodedTokenString(String token) {
43+
String idTokenJson;
44+
try {
45+
idTokenJson = new String(Base64.getUrlDecoder().decode(token.split("\\.")[1]), StandardCharsets.UTF_8);
46+
} catch (ArrayIndexOutOfBoundsException e) {
47+
throw new MsalClientException("Error parsing ID token, missing payload section.",
48+
AuthenticationErrorCode.INVALID_JWT);
49+
}
50+
51+
return JsonHelper.convertJsonToObject(idTokenJson, IdToken.class);
52+
}
53+
4454
//This method is used to convert a JSON string to an object which implements the JsonSerializable interface from com.azure.json
4555
static <T extends JsonSerializable<T>> T convertJsonStringToJsonSerializableObject(String jsonResponse, ReadValueCallback<JsonReader, T> readFunction) {
4656
try (JsonReader jsonReader = JsonProviders.createReader(jsonResponse)) {

msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/JwtHelper.java

Lines changed: 50 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -3,76 +3,82 @@
33

44
package com.microsoft.aad.msal4j;
55

6+
import java.nio.charset.StandardCharsets;
7+
import java.security.Signature;
68
import java.util.ArrayList;
7-
import java.util.Collections;
8-
import java.util.Date;
9+
import java.util.Base64;
10+
import java.util.HashMap;
911
import java.util.List;
12+
import java.util.Map;
1013
import java.util.UUID;
1114

12-
import com.nimbusds.jose.JWSAlgorithm;
13-
import com.nimbusds.jose.JWSHeader;
14-
import com.nimbusds.jose.JWSHeader.Builder;
15-
import com.nimbusds.jose.crypto.RSASSASigner;
16-
import com.nimbusds.jose.util.Base64;
17-
import com.nimbusds.jose.util.Base64URL;
18-
import com.nimbusds.jwt.JWTClaimsSet;
19-
import com.nimbusds.jwt.SignedJWT;
20-
2115
final class JwtHelper {
2216

2317
static ClientAssertion buildJwt(String clientId, final ClientCertificate credential,
2418
final String jwtAudience, boolean sendX5c,
2519
boolean useSha1) throws MsalClientException {
26-
if (StringHelper.isBlank(clientId)) {
27-
throw new IllegalArgumentException("clientId is null or empty");
28-
}
29-
30-
if (credential == null) {
31-
throw new IllegalArgumentException("credential is null");
32-
}
3320

34-
final long time = System.currentTimeMillis();
21+
ParameterValidationUtils.validateNotBlank("clientId", clientId);
22+
ParameterValidationUtils.validateNotNull("credential", clientId);
3523

36-
final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
37-
.audience(Collections.singletonList(jwtAudience))
38-
.issuer(clientId)
39-
.jwtID(UUID.randomUUID().toString())
40-
.notBeforeTime(new Date(time))
41-
.expirationTime(new Date(time
42-
+ Constants.AAD_JWT_TOKEN_LIFETIME_SECONDS
43-
* 1000))
44-
.subject(clientId)
45-
.build();
46-
47-
SignedJWT jwt;
4824
try {
49-
JWSHeader.Builder builder = new Builder(JWSAlgorithm.RS256);
25+
final long time = System.currentTimeMillis();
26+
27+
// Build header
28+
Map<String, Object> header = new HashMap<>();
29+
header.put("alg", "RS256");
30+
header.put("typ", "JWT");
5031

5132
if (sendX5c) {
52-
List<Base64> certs = new ArrayList<>();
53-
for (String cert : credential.getEncodedPublicKeyCertificateChain()) {
54-
certs.add(new Base64(cert));
55-
}
56-
builder.x509CertChain(certs);
33+
List<String> certs = new ArrayList<>(credential.getEncodedPublicKeyCertificateChain());
34+
header.put("x5c", certs);
5735
}
5836

5937
//SHA-256 is preferred, however certain flows still require SHA-1 due to what is supported server-side. If SHA-256
6038
// is not supported or the IClientCredential.publicCertificateHash256() method is not implemented, the library will default to SHA-1.
6139
String hash256 = credential.publicCertificateHash256();
6240
if (useSha1 || hash256 == null) {
63-
builder.x509CertThumbprint(new Base64URL(credential.publicCertificateHash()));
41+
header.put("x5t", credential.publicCertificateHash());
6442
} else {
65-
builder.x509CertSHA256Thumbprint(new Base64URL(hash256));
43+
header.put("x5t#S256", hash256);
6644
}
6745

68-
jwt = new SignedJWT(builder.build(), claimsSet);
69-
final RSASSASigner signer = new RSASSASigner(credential.privateKey());
46+
// Build payload
47+
Map<String, Object> payload = new HashMap<>();
48+
payload.put("aud", jwtAudience);
49+
payload.put("iss", clientId);
50+
payload.put("jti", UUID.randomUUID().toString());
51+
payload.put("nbf", time / 1000);
52+
payload.put("exp", time / 1000 + Constants.AAD_JWT_TOKEN_LIFETIME_SECONDS);
53+
payload.put("sub", clientId);
54+
55+
// Concatenate header and payload
56+
String jsonHeader = JsonHelper.mapper.writeValueAsString(header);
57+
String jsonPayload = JsonHelper.mapper.writeValueAsString(payload);
7058

71-
jwt.sign(signer);
59+
String encodedHeader = base64UrlEncode(jsonHeader.getBytes(StandardCharsets.UTF_8));
60+
String encodedPayload = base64UrlEncode(jsonPayload.getBytes(StandardCharsets.UTF_8));
61+
62+
// Create signature
63+
String dataToSign = encodedHeader + "." + encodedPayload;
64+
65+
Signature sig = Signature.getInstance("SHA256withRSA");
66+
sig.initSign(credential.privateKey());
67+
sig.update(dataToSign.getBytes(StandardCharsets.UTF_8));
68+
byte[] signatureBytes = sig.sign();
69+
70+
String encodedSignature = base64UrlEncode(signatureBytes);
71+
72+
// Build the JWT
73+
String jwt = dataToSign + "." + encodedSignature;
74+
75+
return new ClientAssertion(jwt);
7276
} catch (final Exception e) {
7377
throw new MsalClientException(e);
7478
}
79+
}
7580

76-
return new ClientAssertion(jwt.serialize());
81+
private static String base64UrlEncode(byte[] data) {
82+
return Base64.getUrlEncoder().withoutPadding().encodeToString(data);
7783
}
78-
}
84+
}

msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/SAML11BearerGrant.java

Lines changed: 0 additions & 33 deletions
This file was deleted.

msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/TokenRequestExecutor.java

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,11 @@
33

44
package com.microsoft.aad.msal4j;
55

6-
import com.nimbusds.oauth2.sdk.ParseException;
7-
import com.nimbusds.oauth2.sdk.SerializeException;
86
import org.slf4j.Logger;
97
import org.slf4j.LoggerFactory;
108

119
import java.io.IOException;
1210
import java.net.MalformedURLException;
13-
import java.nio.charset.StandardCharsets;
1411
import java.util.*;
1512

1613
class TokenRequestExecutor {
@@ -30,18 +27,19 @@ class TokenRequestExecutor {
3027
msalRequest.requestContext().apiParameters().tenant() ;
3128
}
3229

33-
AuthenticationResult executeTokenRequest() throws ParseException, IOException {
30+
AuthenticationResult executeTokenRequest() throws IOException {
3431

3532
log.debug("Sending token request to: {}", requestAuthority.canonicalAuthorityUrl());
3633
OAuthHttpRequest oAuthHttpRequest = createOauthHttpRequest();
3734
HttpResponse oauthHttpResponse = oAuthHttpRequest.send();
3835
return createAuthenticationResultFromOauthHttpResponse(oauthHttpResponse);
3936
}
4037

41-
OAuthHttpRequest createOauthHttpRequest() throws SerializeException, MalformedURLException {
38+
OAuthHttpRequest createOauthHttpRequest() throws MalformedURLException {
4239

4340
if (requestAuthority.tokenEndpointUrl() == null) {
44-
throw new SerializeException("The endpoint URI is not specified");
41+
throw new MsalClientException("The endpoint URI is not specified",
42+
AuthenticationErrorCode.INVALID_ENDPOINT_URI);
4543
}
4644

4745
final OAuthHttpRequest oauthHttpRequest = new OAuthHttpRequest(
@@ -109,27 +107,18 @@ private void addQueryParameters(OAuthHttpRequest oauthHttpRequest) {
109107

110108
private void addJWTBearerAssertionParams(Map<String, String> queryParameters, String assertion) {
111109
queryParameters.put("client_assertion", assertion);
112-
queryParameters.put("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer");
110+
queryParameters.put("client_assertion_type", ClientAssertion.ASSERTION_TYPE_JWT_BEARER);
113111
}
114112

115-
private AuthenticationResult createAuthenticationResultFromOauthHttpResponse(
116-
HttpResponse oauthHttpResponse) throws ParseException {
113+
private AuthenticationResult createAuthenticationResultFromOauthHttpResponse(HttpResponse oauthHttpResponse) {
117114
AuthenticationResult result;
118115

119116
if (oauthHttpResponse.statusCode() == HttpHelper.HTTP_STATUS_200) {
120117
final TokenResponse response = TokenResponse.parseHttpResponse(oauthHttpResponse);
121118

122119
AccountCacheEntity accountCacheEntity = null;
123120
if (!StringHelper.isNullOrBlank(response.idToken())) {
124-
String idTokenJson;
125-
try {
126-
idTokenJson = new String(Base64.getUrlDecoder().decode(response.idToken().split("\\.")[1]), StandardCharsets.UTF_8);
127-
} catch (ArrayIndexOutOfBoundsException e) {
128-
throw new MsalServiceException("Error parsing ID token, missing payload section. Ensure that the ID token is following the JWT format.",
129-
AuthenticationErrorCode.INVALID_JWT);
130-
}
131-
132-
IdToken idToken = JsonHelper.convertJsonToObject(idTokenJson, IdToken.class);
121+
IdToken idToken = JsonHelper.createIdTokenFromEncodedTokenString(response.idToken());
133122

134123
AuthorityType type = msalRequest.application().authenticationAuthority.authorityType;
135124
if (!StringHelper.isBlank(response.getClientInfo())) {

0 commit comments

Comments
 (0)