3

私が達成しようとしているのは、Force.com から、既に設定されているサービス アカウントで保持されている Google Fusion Table にデータをアップロードして、そのデータに基づいてネットワーク グラフの視覚化を生成し、 Force.com 内の VF ページ。

以下で構成される Apex クラスを作成しました。

public class NetworkGraphTestController {
    public static String base64URLEncode(Blob input) {
        String output = encodingUtil.base64Encode(input);
        output = output.replace('+', '-');
        output = output.replace('/', '_');
        while (output.endsWith('=')) {
            output = output.subString(0, output.length()-1);
        }
        return output;
    }

    public static Long findSecondsBetween2DateTimes(DateTime dt1, DateTime dt2)
    {
        Integer intDays = dt1.Date().daysBetween(dt2.Date());
        Long seconds = dt2.getTime() - dt1.getTime();
        Long daysToSeconds = intDays * 24 * 60 * 60;
        return daysToSeconds + seconds;
    }

    public static void generateJWT() {

        String pkCS8PrivateKey = 'PRIVATE KEY OBTAINED VIA OPENSSL';

        DateTime utc0 = DateTime.newInstance(1970, 1, 1, 0, 0, 0);
        DateTime issueTime = DateTime.now();

        JSONGenerator gen = JSON.createGenerator(false);
        gen.writeStartObject();
        gen.writeStringField('iss', 'xxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx@developer.gserviceaccount.com');
        gen.writeStringField('scope', 'https:\\/\\/www.googleapis.com\\/auth\\/fusiontables');
        gen.writeStringField('aud', 'https:\\/\\/accounts.google.com\\/o\\/oauth2\\/token');
        Long now = findSecondsBetween2DateTimes(utc0, issueTime);
        gen.writeNumberField('exp', now + 3500);
        gen.writeNumberField('iat', now);
        String claimSet = gen.getAsString().trim();
        System.debug(gen.getAsString());
        System.debug('Gen trimmed: ' + gen.getAsString().trim());

        String header = '{"alg":"RS256", "typ":"JWT"}';
        String signatureInput = base64URLEncode(blob.valueOf(header)) + '.' + base64URLEncode(blob.valueOf(claimSet));

        Blob signature = Crypto.sign('RSA', blob.valueOf(signatureInput), encodingUtil.base64decode(pkCS8PrivateKey));

        String jwt = signatureInput+'.'+base64URLEncode(signature);

        //System.debug(jwt);

        Http h = new Http();
        HttpRequest req = new HttpRequest();
        req.setHeader('Content-Type', 'application/x-www-form-urlencoded');
        req.setMethod('POST');
        req.setBody('grant_type=' + encodingUtil.urlEncode('urn:ietf:params:oauth:grant-type:jwt-bearer', 'UTF-8')+'&assertion=' + encodingUtil.urlEncode(jwt, 'UTF-8'));
        req.setEndpoint('https://accounts.google.com/o/oauth2/token');
        System.debug('Request: ' + req + ' BODY: ' + req.getBody());
        //System.debug(req.toString());
        HttpResponse res = h.send(req);
        System.debug(res + ' BODY: ' + res.getBody() + ' STATUS: ' + res.getStatus());
    }
}

サービス アカウントはすでに設定されており、Fusion Tables API は Google API コンソールで有効になっています。Google が OpenSSL 経由で提供した PKCS12 p12 秘密鍵ファイルから PKCS8 キーを抽出しましたが、このプロセスに慣れていないため、間違いがあった可能性があります。私が気付いたのは、その秘密鍵の値を抽出しようと何度も試みた結果、現在 pkCS8PrivateKey に格納されている値を取得するまで、Crypto クラスが鍵の値を受け入れないことでした。プロセスを正しく覚えていれば、最初に PKCS12 ファイルを中間の pem ファイルに変換し、次に別の pem ファイルに出力された PKCS8 形式の秘密鍵を抽出しました。

発生した 400 'Bad Request' エラーの本文は次のとおりです。

{
     "error" : "invalid_grant"
}

iss および scope パラメータのスラッシュもエスケープしないようにしましたが、違いはありません。また、リモート サイト セキュリティ設定で「 https://accounts.google.com 」へのアクセスを許可しました。

このコードを書くための私の最大の情報源は次の質問です: Google API oAuth 2.0 JWT を生成するための Force.com Apex コード

その質問の作成者は、Google で認証しようとすると、invalid_grant エラーしか取得できないという点で、私と同じ状況に陥りました。残念ながら、彼の質問には答えられなかったので、ここにいます。

私が直面している問題に関して、何か助けがあればよろしくお願いします。

4

2 に答える 2

1

Unfortunately, I'm not sure that this is going to be possible using Apex. I created a test Java program (which works against the Google API) to observe the differences between what Java produces vs Apex. I noted the signatures generated from the two were different which narrowed it down to the output of the Crypto.sign() method.

I found this link which gives the following info:

The Apex Crypto class provides support for Digital Signatures with the sign() method. The following considerations apply:

  • The two algorithms are RSA and RSA-SHA1, which are functionally equivalent.
  • A PKCS8 formatted private key in base64 decoded form is required. This private key should not be hardcoded in the Apex script but should be stored in a protected custom setting or a encrypted fields in a custom table.
  • It is equivalent to the Java Signature.sign() class method using "SHA1withRSA".
  • In C#, it is the equivalent of (1) signing the clear text using SHA1Managed.ComputeHash() and (2) Signing using RSACryptoServiceProvider.ComputeHash() against the resulting hash.
  • Functionally, it will compute a SHA1 digest from clear text and encrypt the digest using RSA with the provided private key.

I have highlighted the key issue here, I believe you need the equivalent of SHA256withRSA which does not seem to be an option with the Crypto class (at least not that I can figure out).

So, in summary I think your code is correct but the signature being generated is not.

于 2013-06-28T12:27:47.623 に答える
0

Constructing the JWT is tricky. You may use Google API java client library. https://code.google.com/p/google-api-java-client/wiki/OAuth2#Service_Accounts

于 2013-06-21T21:26:49.833 に答える