Google OAuth2 服务帐户 HTTP/REST 身份验证

Google OAuth2 Service Account HTTP/REST Authentication

我正在尝试使用以下文档发出访问令牌请求: https://developers.google.com/identity/protocols/OAuth2ServiceAccount

我正在利用 jsrsasign 库生成我的 JWT

为清楚起见,我将代码部署到 Parse.com 云代码,除了来自 Google.

的失败响应外,一切都执行良好

jsrsasign [http://kjur.github.io/jsrsasign/] https://kjur.github.io/jsrsasign/api/symbols/KJUR.jws.JWS.html#.sign

Parse.Cloud.define("testBase", function(request, response) {

  var createJWT = function(){

    var creds = {
      private_key_id: "532ca15e518a0<foobar>74dd81d48a9cb24",
      private_key: "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcw<foobar>QCzxcu5ae3l7JXT\nZzI2kHA3lYay/2DIcC4KXqQWCMejQacRlFROftfnqRrf9qmEewH/0TMSMlOFt0G6\n9hjznhOHu3rxZcAxuK5bh7UnmoUWYksdtb+6VgCGF9Z5piTASrLxssILAUqY6EjT\nHKp3IQk6aqMqe6NhymCS/o0K9NvGA98znpv28ilD9dd5HXhfTdeGm7PDkZGIXIbR\nG+sQN8+tW46N1PaYnoz8iNGfvGk2Y04WDC2HJ590z4DAk41jbcWtnZnr/UyIJzTq\nTBjCWwAcF0qwSabf/mlWSf2S7DeCZKYNveMSN7F6meI8uYshuVoNqd95u/KGfQ4q\ns+wMdF3tAgMBAAECggEAcQ2MhnelUhisSBv3qfS/fVUdNmf/d02ExqSpz+mJkpNw\n+08qjYqbQGZKLloyVMv+f+ARm/nmKIsMXQTywBHC+nLeZ/yzFxGrJIh9VgCIfYEm\n9/IaNpZrEejfyfS/2+WeDv15pe4T+YDqe0jlsrEl2oTBQ7ApGRBqF0bZb/B4XVd3\ngZ6kya+UL5j+PSgNcaGABUfj7pDXZIRmVnWJxXSYhvvbD+SrXIhBMS3wXZ+vka1J\nkW/bwhzayu9/nI24WN7pALxf6/zB3Ewyoj5n1pnxbkvBMcK+PBiX9yAPvfH5cGQF\nQZx86L11maYpWHxufrbclow16qZHP41O+1eePGbIgQKBgQDmOY04z02RIMX32I7k\nbtokmG8qDnR8iu8dkLSRU4Ib7ZPzIBpjg9neaRib9A2fPVjyuxjvsUbob11BuklZ\nGCMu9SFV8w4LpUQ3clL+kBiGncuSmBfZWbj7uqLuzsNeeu3pihVTlkeWeagTAR36\nhi8K4IVQi5SmPF2dPiw9A8oXkQKBgQDH5j1smCFu9d+F2HIwDFXsW1wlyWhtcgfV\n70uCXdImnU25pJDARLX8vqaZp0KHIPmXLgUV/sU3oAX9NRdgV56bJTo7vwO3DATM\ntK14h7GZCKSYniOqX+3FdweNyn89qlHeAkZdvCZhGX5rOVXtlhpey7Eu8fQnPs1S\nbxd1EXRKnQKBgH+m1Yj0WLvpghskdkZuuIGmC600Cp6rol2wSI5z0SaPGoOp/zfC\neeD6QOzn602qBFHCL9dnYjuq0/iHw/ekjI2S2YMAm38Vibd8qkv/tbmecKu9rSuU\nth7No13qQyV138ioCZ8pKlRi7DBtZCPultLfHsxEOI3b1sRDHuBN45YhAoGAGbKe\niNxRx/rxvjoiC806KoVgJjdrJk63dSgrE9pNzssAF/Jw7Van8pLrxer7oXV6wJWY\n78ftwIXg3zk5BRieeiFiCBY5OwnfgBVmC42eJic3SatiuF9WqMDxhqfWja3ckmbG\ndvxeDrOBTfVz93QJddBHudo+4eCv8n33jQQuZ/0CgYEAsYXmQWOUndqBaZgho3ZV\njrRFmwiwiqJ1hqJdflBXbKlTQOpqea8QoQOQqyeMVQ1X7x3rDcHbhFSbd65GJT5j\n65B/OXrBpIBhb5u0/x6ytJhlM9sPRIL+G/m5QYnBY7dcQo6jlKxTUKHPEV/mwT2m\nt/ZxkAmz/9DKWFKtDc4ZshI\u003d\n-----END PRIVATE KEY-----\n",
      client_email: "<foobar>-t18b3hrkab6urireblm8kb4kt45c92a2@developer.gserviceaccount.com",
      client_id: "<foobar>-t18b3hrkab6urireblm8kb4kt45c92a2.apps.googleusercontent.com",
      type: "service_account"
    };

    var header = {
      alg:"RS256",
      typ:"JWT"
    };

    var data = {
      iss: creds.client_email,
      scope:"https://www.googleapis.com/auth/analytics.readonly",
      aud:"https://www.googleapis.com/oauth2/v3/token",
      exp: KJUR.jws.IntDate.get('now + 1hour'),
      iat: KJUR.jws.IntDate.get('now')
    };

    console.log("Preparing to generate RS256 JWT");
    var sJWT = KJUR.jws.JWS.sign("RS256", JSON.stringify(header), JSON.stringify(data), creds.private_key);
    console.log("RS256 JWT generation complete:");

    console.log(sJWT)
    return sJWT;
  }

  console.log("############################## ")


  try{
      console.log("Preparing assertion...")
      var jwt = createJWT()
      console.log("Assertion: "+jwt);

      var options = {
          method: 'POST',
          headers: {
            'Content-Type':'application/x-www-form-urlencoded'
          },
          url: "https://www.googleapis.com/oauth2/v3/token",
          params: {
            grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",

            assertion: jwt
          }
      };

      console.log("------------------------");
      console.log(options);
      console.log("------------------------");

      Parse.Cloud.httpRequest(options).done(function(rsp){
          var r = (_.isString(rsp.text)) ? JSON.parse(rsp.text) : rsp.text;
          console.log("Reponse from Google:");
          console.log(rsp)
          // console.log({ body: req.body, params: req.params, query: req.query, o: options, r: r });
          // res.send(r);
          response.success(r);

      }).fail(function(err){
          // console.error(err);
          console.error("Failed response from Google:")
          console.error(err.text)
          response.error(err);

      });
    }catch(err){
        console.error(err);
        response.error(err);
    }
});

控制台输出:

I2015-06-29T19:42:17.315Z]############################## 
I2015-06-29T19:42:17.316Z]Preparing assertion...
I2015-06-29T19:42:17.317Z]Preparing to generate RS256 JWT
I2015-06-29T19:42:17.401Z]RS256 JWT generation complete:
I2015-06-29T19:42:17.402Z]eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiI2MDQ4Mjk2NTQ1MzktdDE4YjNocmthYjZ1cmlyZWJsbThrYjRrdDQ1YzkyYTJAZGV2ZWxvcGVyLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJzY29wZSI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL2F1dGgvYW5hbHl0aWNzLnJlYWRvbmx5IiwiYXVkIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vb2F1dGgyL3YzL3Rva2VuIiwiZXhwIjoxNDM1NjEwNTM3LCJpYXQiOjE0MzU2MDY5Mzd9.nGbApndzwwtadeL2Jr2zU__JZrBZ6tYGJ17sTDksiSsFRXop_6CFAsV7fkXC6Xd-Nf3KfYzNuqGzLciQTzc9AhGNFTk_aUXU-ndMbYiVh3EpTkBI0olkS5rkgnmm3Q_yfaOswkyvMwE12RvgTTjymVzHGTZ8xC_x22Ep1n07Ap3TQn3WpeFeJlHciiwcxMTG7TsxAvHEgaqLzZ79feFmZanj6pqEH1kfZeJUQK1n3bwKtU92qpPn7b4dFtJs8I7El62HLExU1B2l7qdSyp4CRxmUPViUfWykElDZeqDzPoX38QEMDmmTgCYUXna7wJB6O0qC3aJpxkCAmzPCDkXkZQ
I2015-06-29T19:42:17.403Z]Assertion: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiI2MDQ4Mjk2NTQ1MzktdDE4YjNocmthYjZ1cmlyZWJsbThrYjRrdDQ1YzkyYTJAZGV2ZWxvcGVyLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJzY29wZSI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL2F1dGgvYW5hbHl0aWNzLnJlYWRvbmx5IiwiYXVkIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vb2F1dGgyL3YzL3Rva2VuIiwiZXhwIjoxNDM1NjEwNTM3LCJpYXQiOjE0MzU2MDY5Mzd9.nGbApndzwwtadeL2Jr2zU__JZrBZ6tYGJ17sTDksiSsFRXop_6CFAsV7fkXC6Xd-Nf3KfYzNuqGzLciQTzc9AhGNFTk_aUXU-ndMbYiVh3EpTkBI0olkS5rkgnmm3Q_yfaOswkyvMwE12RvgTTjymVzHGTZ8xC_x22Ep1n07Ap3TQn3WpeFeJlHciiwcxMTG7TsxAvHEgaqLzZ79feFmZanj6pqEH1kfZeJUQK1n3bwKtU92qpPn7b4dFtJs8I7El62HLExU1B2l7qdSyp4CRxmUPViUfWykElDZeqDzPoX38QEMDmmTgCYUXna7wJB6O0qC3aJpxkCAmzPCDkXkZQ
I2015-06-29T19:42:17.404Z]------------------------
I2015-06-29T19:42:17.405Z]{"method":"POST","headers":{"Content-Type":"application/x-www-form-urlencoded"},"url":"https://www.googleapis.com/oauth2/v3/token","params":{"grant_type":"urn:ietf:params:oauth:grant-type:jwt-bearer","assertion":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiI2MDQ4Mjk2NTQ1MzktdDE4YjNocmthYjZ1cmlyZWJsbThrYjRrdDQ1YzkyYTJAZGV2ZWxvcGVyLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJzY29wZSI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL2F1dGgvYW5hbHl0aWNzLnJlYWRvbmx5IiwiYXVkIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vb2F1dGgyL3YzL3Rva2VuIiwiZXhwIjoxNDM1NjEwNTM3LCJpYXQiOjE0MzU2MDY5Mzd9.nGbApndzwwtadeL2Jr2zU__JZrBZ6tYGJ17sTDksiSsFRXop_6CFAsV7fkXC6Xd-Nf3KfYzNuqGzLciQTzc9AhGNFTk_aUXU-ndMbYiVh3EpTkBI0olkS5rkgnmm3Q_yfaOswkyvMwE12RvgTTjymVzHGTZ8xC_x22Ep1n07Ap3TQn3WpeFeJlHciiwcxMTG7TsxAvHEgaqLzZ79feFmZanj6pqEH1kfZeJUQK1n3bwKtU92qpPn7b4dFtJs8I7El62HLExU1B2l7qdSyp4CRxmUPViUfWykElDZeqDzPoX38QEMDmmTgCYUXna7wJB6O0qC3aJpxkCAmzPCDkXkZQ"}}
I2015-06-29T19:42:17.406Z]------------------------
I2015-06-29T19:42:17.504Z]Failed response from Google:
I2015-06-29T19:42:17.506Z]{
 "error": "invalid_grant",
 "error_description": "Bad Request"
}

最终解决方案: 导入 google 服务帐户凭据 json 文件(重命名为 google-service-account-credentials.js),生成 jwt,在 Parse.Request 中应用 jwt 作为正文而不是参数。

var fs = require('fs');
var moment = require('moment');
var _ = require('underscore');
var KJUR = require("cloud/lib/jsrsasign/npm/lib/jsrsasign.js");
var googleServiceAccountCredentials = JSON.parse(fs.readFileSync('cloud/google-service-account-credentials.js'));


var createJWT = function(args, credentials){

  var header = {
    alg:"RS256",
    typ:"JWT"
  };

  var now = moment().unix();

  var defaults = {
    iss: credentials.client_email,
    scope:"https://www.googleapis.com/auth/analytics.readonly",
    aud:"https://www.googleapis.com/oauth2/v3/token",
    exp: now + (15*60),
    iat: now
  };

  var data = {};

  _.extend(data, defaults, args);

  var sJWT = KJUR.jws.JWS.sign("RS256", JSON.stringify(header), JSON.stringify(data), credentials.private_key );
  return sJWT;
};

Parse.Cloud.define("testBase", function(request, response) {


  try{
      var now = moment().unix();

      var options = {
          method: 'POST',
          headers: {
            'Content-Type':'application/x-www-form-urlencoded'
          },
          url: "https://www.googleapis.com/oauth2/v3/token",
          body: {
            grant_type: encodeURI("urn:ietf:params:oauth:grant-type:jwt-bearer"),
            assertion: createJWT({
              exp: now + (60*60),
              iat: now
            }, googleServiceAccountCredentials)
          }
      };

      Parse.Cloud.httpRequest(options).done(function(rsp){
          var r = (_.isString(rsp.text)) ? JSON.parse(rsp.text) : rsp.text;
          console.log("Reponse from Google:");
          console.log(r.access_token);
          console.log(r.expires_in);
          console.log(r.token_type);
          response.success(r);

      }).fail(function(err){
          console.error("Failed response from Google:")
          console.error(err.text)
          response.error(err);

      });
    }catch(err){
        console.error(err);
        response.error(err);
    }
});

在您的请求选项中使用正文而不是参数