Подключение к Google API с помощью учетной записи службы и OAuth

Я использую среду, в которой нет встроенной поддержки клиентской библиотеки GCP. Итак, я пытаюсь понять, как пройти аутентификацию напрямую, используя вручную созданный токен JWT.

Я адаптировал задачи отсюда. Используя тестовую среду nodeJS, с jwa для реализации алгоритма.

https://developers.google.com/identity/protocols/OAuth2ServiceAccount

Закрытый ключ берется из JSON-версии файла учетной записи службы.

Когда тест запускается, он выявляет очень простую ошибку 400, которая просто говорит «недопустимый запрос». Я не знаю, как это исправить.

Может ли кто-нибудь помочь определить, что я делаю неправильно?

var assert = require('assert');
const jwa = require('jwa');
const request = require('request-promise');

const pk = require('../auth/tradestate-2-tw').private_key;

const authEndpoint = 'https://www.googleapis.com/oauth2/v4/token';


describe('Connecting to Google API', function() {

    it('should be able to get an auth token for Google Access', async () => {
      assert(pk && pk.length, 'PK exists');
      const header = { alg: "RS256", typ: "JWT" };
      const body = {
        "iss":"[email protected]",
        "scope":"https://www.googleapis.com/auth/devstorage.readonly",
        "aud":"https://www.googleapis.com/oauth2/v4/token",
        "exp": new Date().getTime() + 3600 * 1000,
        "iat": new Date().getTime()
      };
      console.log(JSON.stringify(body, null, 2));
      const encodedHeader = Buffer.from(JSON.toString(header)).toString('base64')
      const encodedBody = Buffer.from(JSON.toString(body)).toString('base64');
      const cryptoString = `${encodedHeader}.${encodedBody}`;
      const algo = jwa('RS256');
      const signature = algo.sign(cryptoString, pk);
      const jwt = `${encodedHeader}.${encodedBody}.${signature}`;
      console.log('jwt', jwt);
      const headers = {'Content-Type': 'application/x-www-form-urlencoded'};
      const form = {
        grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
        assertion: jwt
      };
      try { 
        const result = await request.post({url: authEndpoint, form, headers});
        assert(result, 'Reached result');
        console.log('Got result', JSON.stringify(result, null, 2));
      } catch (err) {
        console.log(JSON.stringify(err, null, 2));
        throw (err);
      }

    });
});

person Richard G    schedule 26.05.2019    source источник
comment
Я написал статью, в которой подробно описаны необходимые шаги. Это включает в себя настоящий исходный код на Python, чтобы показать вам шаги. Объясняются важные основные шаги, как создать JWT и как обменять JWT на токены. jhanley.com/   -  person John Hanley    schedule 26.05.2019
comment
Еще я бы сделал, чтобы iat и exp использовали один и тот же объект new Date () вместо того, чтобы вызывать его дважды.   -  person abelito    schedule 26.05.2019
comment
Привет @abelito, я пробовал это, но это вызывает ошибку в соответствии с вашим ответом ниже. Но изменение на JSON.stringify работает хорошо. Но теперь у меня есть еще одна проблема с iad и сроком действия ... Может быть, это часовой пояс?   -  person Richard G    schedule 26.05.2019


Ответы (1)


Используйте JSON.stringify вместо JSON.toString. По ссылке в вашем вопросе:

{alg: RS256, typ: JWT}

Это представление в Base64url выглядит следующим образом:

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9

При использовании JSON.toString (), а затем кодировки base64 вы получите W29iamVjdCBKU09OXQ ==, который объясняет 400 для недопустимого запроса, поскольку он не может расшифровать все, что вы его отправляете.

person abelito    schedule 26.05.2019
comment
Ой, здорово, это облегчение. Я косился, пытаясь понять, что случилось. У меня есть шаг вперед к новой ошибке: 'StatusCodeError: 400 - {\ n \ error \: \ invalid_grant \, \ n \ error_description \: \ Invalid JWT: токен должен быть недолговечным токеном (60 минут). и в разумные сроки. Проверьте свои значения iat и exp и используйте часы с перекосом, чтобы учесть разницу часов между системами. \\ n} ' - person Richard G; 26.05.2019
comment
Это код, который я изменил в соответствии с предыдущим комментарием: const now = new Date (). GetTime (); const then = now + 3600 * 1000; const body = {iss: [email protected], область действия: googleapis.com/auth/devstorage.readonly, aud: googleapis.com/ oauth2 / v4 / token, exp: then, iat: now}; - person Richard G; 26.05.2019
comment
Я рад, что стрингифай помог! Я думаю о вашем новом выпуске, но пока попробуйте более короткий период времени ... может быть, 15 или 30 минут? Может быть, проблема с UTC и местным часовым поясом, как вы упомянули в своем комментарии выше. - person abelito; 26.05.2019
comment
@RichardG stackoverflow.com/questions/36189612/ Похоже, что потенциально компьютер / сервер, с которого вы делаете запрос, имеет системные часы, которые не синхронизированы с NTP - person abelito; 26.05.2019
comment
Не думаю, что это могут быть системные часы. Я использую свой локальный Mac, и он настроен на автоматическое определение времени, часы в порядке. Я тоже пробовал сократить отметку времени, но проблема та же. Возможно проблема с кодировкой? Есть ли способ заставить строку использовать UTF-8? - person Richard G; 26.05.2019
comment
Наконец-то понял! Дата в iat и exp указывается в секундах, а не в миллисекундах. Поэтому мне нужно было его преобразовать: const now = Date().getTime() / 1000. Спасибо за помощь. Сейчас отмечу это как правильное. - person Richard G; 26.05.2019