Я следую это руководство по потоку учетных данных клиента и этого руководства к необходимому токену JWT. Конечной целью является доступ к данным календаря через API Microsoft Graph. Вызовы API Graph должны быть аутентифицированы с помощью токена доступа, полученного из конечной точки токена Майкрософт.
Но вызов самой конечной точки токена требует некоторой формы аутентификации, которая может быть либо с помощью общего секрета, либо с помощью токена JWT, подписанного сертификатом. Подход с общим секретом хорошо документирован и, похоже, работает нормально, за исключением того, что служба Graph, которую я пытаюсь вызвать, отклоняет токен доступа как недостаточно безопасный — кажется, что требуется токен JWT, подписанный сертификатом. Мне не удалось найти какие-либо примеры Java для этого второго подхода, и то, что я реализовал до сих пор, следуя приведенным выше руководствам, не работает.
При использовании JJWT код для генерации токена выглядит так:
PrivateKey key = loadPrivateKey();
String jwt = Jwts.builder()
.setHeaderParam("typ", "JWT")
.setHeaderParam("alg", "RS256")
.setHeaderParam("x5t", "A7...89")
.setSubject(clientId)
.setExpiration(new Date(System.currentTimeMillis() + 200000))
.setIssuer(clientId)
.setNotBefore(new Date())
.setAudience("https://login.microsoftonline.com/" + tenantId + "/oauth2/token")
.setId(UUID.randomUUID().toString())
.signWith(
SignatureAlgorithm.RS256,
key)
.compact();
loadPrivateKey() использует классы BouncyCastle:
KeyFactory factory = KeyFactory.getInstance("RSA");
PemObject pemObject;
PemReader pemReader = new PemReader(new StringReader(pemFileContent));
try {
pemObject = pemReader.readPemObject();
} finally {
pemReader.close();
}
byte[] content = pemObject.getContent();
PKCS8EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec(content);
return factory.generatePrivate(privKeySpec);
Я создал пару ключей в консоли apps.dev.microsoft.com, загрузил закрытый ключ и преобразовал его в формат PEM с помощью openssl, а затем вставил содержимое PEM в переменную pemFileContent, использованную выше. openssl вычисляет тот же отпечаток для версии PEM, что и консоль приложения MS.
Я использую токен JWT при вызове службы модернизации:
default Call<APIToken> getAccessToken(String tenantId, String clientId, String clientAssertion)
{
return this.getAccessToken(
tenantId,
clientId,
clientAssertion,
"urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
"https://graph.microsoft.com/.default",
"client_credentials");
}
@FormUrlEncoded
@Headers({
"Host: login.microsoftonline.com",
"Content-Type: application/x-www-form-urlencoded"
})
@POST("/{tenant_id}/oauth2/token")
Call<APIToken> getAccessToken(
@Path("tenant_id") String tenantId,
@Field("client_id") String clientId,
@Field("client_assertion") String clientAssertion,
@Field("client_assertion_type") String clientAssertionType,
@Field("scope") String scope,
@Field("grant_type") String grantType);
В выводе отладки все выглядит хорошо:
header={typ=JWT, alg=RS256, x5t=A7...89},body={sub=b0a0fd13-1e86-4ef3-a003-c53eaf21daa4, exp=1517656715, iss=b0a0fd13-1e86-4ef3-a003-c53eaf21daa4, nbf=1517656515, aud=https://login.microsoftonline.com/87ad3067-2703-49ea-8cd2-a094fc3ee413/oauth2/token, jti=4fa5db4f-76b7-4122-9c5a-092ac74fd0fa},signature=....342 characters....
Но ответ 401 Unauthorized с сообщением об ошибке:
{"error":"invalid_client","error_description":"AADSTS70002: Error validating credentials. AADSTS50012: Client assertion contains an invalid signature. [Reason - The key was not found., Thumbprint of key used by client: '03B....3D', Configured keys: [Key0:Start=02/03/2018, End=12/31/2099, Thumbprint=lQ...M4;]]\r\nTrace ID: b4775394-51cc-4a4a-b927-50bd05421100\r\nCorrelation ID: f9264339-9b85-4942-9404-af941aa0331c\r\nTimestamp: 2018-02-03 11:15:18Z","error_codes":[70002,50012],"timestamp":"2018-02-03 11:15:18Z","trace_id":"b4775394-51cc-4a4a-b927-50bd05421100","correlation_id":"f9264339-9b85-4942-9404-af941aa0331c"}
Я в растерянности, где искать проблему. Ни одно из значений отпечатка, упомянутых в ошибке, не соответствует отпечатку закрытого ключа. Другие сообщают об этом сообщении об ошибке при использовании неправильного алгоритма подписи, но RS256 является задокументированным алгоритмом для использования. Я ищу либо указатель на то, что я ошибаюсь, либо рабочий пример на Java (я не привередлив в отношении того, какие библиотеки используются) потока учетных данных клиента с использованием подписанного токена JWT (я нашел примеры с использованием общий секрет, и они работают нормально, но API MS Graph отклоняет токен как недостаточно безопасный).