Как подписать токен JWT для учетных данных клиента OAuth для Office 365 в Java/JJWT? Утверждение клиента содержит недопустимую подпись

Я следую это руководство по потоку учетных данных клиента и этого руководства к необходимому токену 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 отклоняет токен как недостаточно безопасный).


person Jon Moore    schedule 03.02.2018    source источник
comment
Какая конечная точка в MS Graph отклоняет токен?   -  person juunas    schedule 04.02.2018
comment
Я пытался использовать API потоковых подписок: /api/beta/me/subscriptions. К сожалению, сейчас я не могу точно воспроизвести ошибку, потому что консоль управления приложениями (apps.dev.microsoft.com) имеет «временную проблему» со вчерашнего дня, когда я пытаюсь создать новый пароль.   -  person Jon Moore    schedule 06.02.2018
comment
Ошибка от API подписки: Токен доступа получен с использованием метода аутентификации, который слишком слаб, чтобы разрешить доступ для этого приложения. Представленная сила аутентификации равна 1, требуется 2.;error_category=invalid_token. токен, полученный с использованием пароля, недостаточно безопасен для предоставления доступа к API - вместо пароля я должен предоставить JWT, подписанный сертификатом.   -  person Jon Moore    schedule 06.02.2018
comment
см. мой ответ на аналогичную проблему. Скорее всего, это вычисленный отпечаток сертификата. Мне пришлось сделать это вручную с помощью openssl и использовать это для x5t stackoverflow.com/questions/50657463/   -  person codebrane    schedule 03.10.2018