Ошибка «invalid_grant» при получении токена OAuth с учетной записью службы в Google Cloud Storage

Я написал небольшое приложение на Qt/С++, которое использует Google Cloud Storage для сохранения файлов изображений. Я создал идентификатор клиента служебной учетной записи в своей консоли Google API и пытаюсь взаимодействовать с Google REST API. Но прежде всего мне нужно получить токен авторизации OAuth.

Я пытаюсь получить токен уже три дня, но каждый раз получаю одну и ту же надоедливую ошибку: { "error" : "invalid_grant" }

Я попытался настроить время, как было предложено здесь: invalid_grant Возвращено с использованием сервисного аккаунта и API Google Диска. Это не помогло.

Я создал еще один идентификатор клиента в Google API Console, как предлагает этот раздел: Invalid_grant в Google Analytics . Безрезультатно.

Вот код моего крошечного небольшого класса, который запрашивает токен. Пожалуйста, кто-нибудь может сказать, что я делаю неправильно?

#include "googlestorageuploader.h"
#include "openssl/evp.h"
#include "openssl/pem.h"
#include "openssl/bio.h"
#include "openssl/err.h"
#include "openssl/pkcs12.h"
#include <QFile>
#include <QDateTime>
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QSslConfiguration>
#include <QDebug>

// Just a wrapper that takes OpenSsl function 'a' and calls it.
// If 'a' returns zero the wrapper throws a string containg description
// of error occured
#define OPENSSL_FUNCTION_CALL(a) \
if (!a) \
{ \
    char buffer[120]; \
    ERR_error_string(ERR_get_error(), buffer); \
    char s[256]; \
    sprintf(s, ""#a" function call failed.\n%s", buffer); \
    throw s; \
} \

// String template for standard JWT Header
const QString kJwtHeader = "{\"alg\":\"RS256\",\"typ\":\"JWT\"}";

// String template for JWT claim set
// Contains two placeholders for "exp" and "iat" keys
const QString kJwtClaimSet = "{"
    "\"iss\":\"XXXXXXXXXXXX-yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy@developer.gserviceaccount.com\","
    "\"scope\":\"https://www.googleapis.com/auth/devstorage.readonly\","
    "\"aud\":\"https://accounts.google.com/o/oauth2/token\","
    "\"exp\":%1,"
    "\"iat\":%2"
    "}";

// This function computes JWT signature
// 'input' is an array of bytes passed as an input
// 'privateKey' is an array of bytes that contains a private key in PKCS12 format
//      (as it is in the file received from 'Google APIs Console->API Access')
// Uses OpenSSL API to create a signature using SHA256withRSA
QByteArray signWithRsaSha256(const QByteArray &input, const QByteArray &privateKey)
{
    EVP_PKEY *pkey = 0;
    BIO *bp = 0;
    EVP_MD_CTX *ctx = 0;
    const EVP_MD *sha256Md = 0;
    unsigned char sig[256];
    unsigned int s(0);
    QByteArray out;
    PKCS12 *p12 = 0;
    X509 *cert= 0;

    OpenSSL_add_all_ciphers();
    OpenSSL_add_all_digests();

    ctx = EVP_MD_CTX_create();
    EVP_MD_CTX_init(ctx);
    sha256Md = EVP_sha256(); // TODO: need to free
    bp = BIO_new_mem_buf((void*)privateKey.data(), privateKey.size());

    try {
        OPENSSL_FUNCTION_CALL(EVP_SignInit(ctx, sha256Md));
        OPENSSL_FUNCTION_CALL(EVP_SignUpdate(ctx, input.constData(), input.size()));
        OPENSSL_FUNCTION_CALL(d2i_PKCS12_bio(bp, &p12));
        OPENSSL_FUNCTION_CALL(PKCS12_parse(p12, "notasecret", &pkey, &cert, NULL));

        s = EVP_PKEY_size(pkey);
        OPENSSL_FUNCTION_CALL(EVP_SignFinal(ctx, sig, &s, pkey));

        out.setRawData((const char *)sig, s);
    }
    catch (const char *s)
    {
        qCritical() << s;
    }
    EVP_MD_CTX_destroy(ctx);
    BIO_free(bp);
    X509_free(cert);
    EVP_cleanup();

    return out;
}

CGoogleStorageUploader::CGoogleStorageUploader(QObject *parent) :
QObject(parent)
{
    nam_ = new QNetworkAccessManager(this);
}

// Creates and sends HTTP POST request to get an authorization token
void CGoogleStorageUploader::requestToken()
{
    QNetworkRequest req(QUrl("https://accounts.google.com/o/oauth2/token"));

    req.setRawHeader("Host", "accounts.google.com");
    req.setRawHeader("Content-Type", "application/x-www-form-urlencoded");
    req.setSslConfiguration(QSslConfiguration::defaultConfiguration());

    QByteArray assertion = formJWT();
    QByteArray data;
    data = "grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=" + assertion;

    QNetworkReply *reply = nam_->post(req, data);
    connect(reply, SIGNAL(finished()), SLOT(onNetworkReplyFinished()));
    connect(reply, SIGNAL(sslErrors(QList<QSslError>)), SLOT(onNetworkReplySslErrors(QList<QSslError>)));
    connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(onNetworkReplyError(QNetworkReply::NetworkError)));
}

// Constructs JWT
QByteArray CGoogleStorageUploader::formJWT()
{
    QDateTime now = QDateTime::currentDateTime();
    QDateTime utcNow = now.toUTC();
    quint32 secs = utcNow.toTime_t() - 300; // adjust to Google Server time
    QString claimSet = kJwtClaimSet.arg(secs + 3600).arg(secs); // fill "exp" and "iat" fields

    QByteArray signature, privateKey, jwt;
    QFile file (":res/pk.p12"); // copy of file received from Google API Console
    if (file.open(QFile::ReadOnly))
    {
        privateKey = file.readAll();
        QByteArray encodedHeader = kJwtHeader.toUtf8().toBase64(); // serialize to UTF8 and Base64url safe encode (https://developers.google.com/accounts/docs/OAuth2ServiceAccount#formingclaimset)
        QByteArray encodedClaimSet = claimSet.toUtf8().toBase64(); // serialize to UTF8 and Base64url safe encode (https://developers.google.com/accounts/docs/OAuth2ServiceAccount#formingclaimset)

        // "Sign the UTF-8 representation of the input" (https://developers.google.com/accounts/docs/OAuth2ServiceAccount#computingsignature)
        signature = signWithRsaSha256(QString(encodedHeader + "." + encodedClaimSet).toUtf8(), privateKey);
        QByteArray encodedSignature = signature.toBase64();

        jwt = encodedHeader + "." + encodedClaimSet + "." + encodedSignature;
    }

    file.close();

    qDebug() << jwt;

    return jwt;
}

void CGoogleStorageUploader::onNetworkReplyFinished()
{
    QNetworkReply *r = static_cast<QNetworkReply*>(sender());
    qDebug() << "finished: " << r->readAll();
}

void CGoogleStorageUploader::onNetworkReplySslErrors(const QList<QSslError> &errs)
{
    QNetworkReply *r = static_cast<QNetworkReply*>(sender());
    qDebug() << "ssl errors: " << r->readAll();
    r->ignoreSslErrors(errs);
}

void CGoogleStorageUploader::onNetworkReplyError(QNetworkReply::NetworkError err)
{
    qDebug() << "network error:  " << err;
}

person pehcha    schedule 17.03.2013    source источник


Ответы (1)


Разве вам не нужно сначала позвонить, чтобы получить код? Получив код, вы можете позвонить, чтобы получить токен...

person OhadR    schedule 17.03.2013
comment
Извините, какой код я должен получить первым, сделав звонок? Я использую сервисный аккаунт для получения токена доступа OAuth, как описано здесь ссылка - person pehcha; 18.03.2013