google 通过 nodejs 使用不记名令牌进行云身份验证

google cloud authentication with bearer token via nodejs

我的客户在 Google 云 运行.

上有一个 GraphQL API 运行ning

我收到了一个用于身份验证以及访问 gcloud 命令行工具的服务帐户。

像这样使用 gcloud 命令行时:

gcloud auth print-identity-token

我可以生成一个令牌,可用于向 api 发出 post 请求。这行得通,我可以从 postman, insomnia 和我的 nodejs 应用程序向 api 发出成功的 post 请求。

但是,当我将 JWT 身份验证与 "googleapis" 或 "google-auth" npm 库一起使用时,如下所示:

var { google } = require('googleapis')

let privatekey = require('./auth/google/service-account.json')

let jwtClient = new google.auth.JWT(
  privatekey.client_email,
  null,
  privatekey.private_key,
  ['https://www.googleapis.com/auth/cloud-platform']
)

jwtClient.authorize(function(err, _token) {
  if (err) {
    console.log(err)
    return err
  } else {
    console.log('token obj:', _token)
  }
})

这会输出一个 "bearer" 令牌:

token obj: {
  access_token: 'ya29.c.Ko8BvQcMD5zU-0raojM_u2FZooWMyhB9Ni0Yv2_dsGdjuIDeL1tftPg0O17uFrdtkCuJrupBBBK2IGfUW0HGtgkYk-DZiS1aKyeY9wpXTwvbinGe9sud0k1POA2vEKiGONRqFBSh9-xms3JhZVdCmpBi5EO5aGjkkJeFI_EBry0E12m2DTm0T_7izJTuGQ9hmyw',
  token_type: 'Bearer',
  expiry_date: 1581954138000,
  id_token: undefined,
  refresh_token: 'jwt-placeholder'
}

但是这个不记名令牌不像上面的那样工作,并且在发出与 gcloud 命令相同的请求时总是给出 "unauthorised error 401" "gcloud auth print-identity-token"。

请帮忙,我不确定为什么第一个不记名令牌有效,但使用 JWT 生成的不记名令牌无效。

编辑

我也尝试过获取身份令牌而不是像这样的访问令牌:

let privatekey = require('./auth/google/service-account.json')

let jwtClient = new google.auth.JWT(
  privatekey.client_email,
  null,
  privatekey.private_key,
  []
)

jwtClient
  .fetchIdToken('https://my.audience.url')
  .then((res) => console.log('res:', res))
  .catch((err) => console.log('err', err))

这会打印一个身份令牌,但是,使用它也只会给出“401 未授权”消息。

编辑以显示我如何调用端点

请注意,以下任何方法都可以使用命令行身份令牌,但是当通过 JWT 生成时,它 returns 401

方法一:

 const client = new GraphQLClient(baseUrl, {
        headers: {
          Authorization: 'Bearer ' + _token.id_token
        }
      })
      const query = `{
        ... my graphql query goes here ...
    }`
      client
        .request(query)
        .then((data) => {
          console.log('result from query:', data)
          res.send({ data })
          return 0
        })
        .catch((err) => {
          res.send({ message: 'error ' + err })
          return 0
        })
    }

方法 2(使用我用 google-auth 创建的 "authorized" 客户端):

  const res = await client.request({
    url: url,
    method: 'post',
    data: `{
        My graphQL query goes here ...
    }`
  })
  console.log(res.data)
}

您可以找到节点的官方文档OAuth2

A complete OAuth2 example:

const {OAuth2Client} = require('google-auth-library');
const http = require('http');
const url = require('url');
const open = require('open');
const destroyer = require('server-destroy');

// Download your OAuth2 configuration from the Google
const keys = require('./oauth2.keys.json');

/**
 * Start by acquiring a pre-authenticated oAuth2 client.
 */
async function main() {
  const oAuth2Client = await getAuthenticatedClient();
  // Make a simple request to the People API using our pre-authenticated client. The `request()` method
  // takes an GaxiosOptions object.  Visit https://github.com/JustinBeckwith/gaxios.
  const url = 'https://people.googleapis.com/v1/people/me?personFields=names';
  const res = await oAuth2Client.request({url});
  console.log(res.data);

  // After acquiring an access_token, you may want to check on the audience, expiration,
  // or original scopes requested.  You can do that with the `getTokenInfo` method.
  const tokenInfo = await oAuth2Client.getTokenInfo(
    oAuth2Client.credentials.access_token
  );
  console.log(tokenInfo);
}


/**
 * Create a new OAuth2Client, and go through the OAuth2 content
 * workflow.  Return the full client to the callback.
 */
function getAuthenticatedClient() {
  return new Promise((resolve, reject) => {
    // create an oAuth client to authorize the API call.  Secrets are kept in a `keys.json` file,
    // which should be downloaded from the Google Developers Console.
    const oAuth2Client = new OAuth2Client(
      keys.web.client_id,
      keys.web.client_secret,
      keys.web.redirect_uris[0]
    );

    // Generate the url that will be used for the consent dialog.
    const authorizeUrl = oAuth2Client.generateAuthUrl({
      access_type: 'offline',
      scope: 'https://www.googleapis.com/auth/userinfo.profile',
    });

    // Open an http server to accept the oauth callback. In this simple example, the
    // only request to our webserver is to /oauth2callback?code=<code>
    const server = http
      .createServer(async (req, res) => {
        try {
          if (req.url.indexOf('/oauth2callback') > -1) {
            // acquire the code from the querystring, and close the web server.
            const qs = new url.URL(req.url, 'http://localhost:3000')
              .searchParams;
            const code = qs.get('code');
            console.log(`Code is ${code}`);
            res.end('Authentication successful! Please return to the console.');
            server.destroy();

            // Now that we have the code, use that to acquire tokens.
            const r = await oAuth2Client.getToken(code);
            // Make sure to set the credentials on the OAuth2 client.
            oAuth2Client.setCredentials(r.tokens);
            console.info('Tokens acquired.');
            resolve(oAuth2Client);
          }
        } catch (e) {
          reject(e);
        }
      })
      .listen(3000, () => {
        // open the browser to the authorize url to start the workflow
        open(authorizeUrl, {wait: false}).then(cp => cp.unref());
      });
    destroyer(server);
  });
}

main().catch(console.error);

编辑

云的另一个示例 运行。

// sample-metadata:
//   title: ID Tokens for Cloud Run
//   description: Requests a Cloud Run URL with an ID Token.
//   usage: node idtokens-cloudrun.js <url> [<target-audience>]

'use strict';

function main(
  url = 'https://service-1234-uc.a.run.app',
  targetAudience = null
) {
  // [START google_auth_idtoken_cloudrun]
  /**
   * TODO(developer): Uncomment these variables before running the sample.
   */
  // const url = 'https://YOUR_CLOUD_RUN_URL.run.app';
  const {GoogleAuth} = require('google-auth-library');
  const auth = new GoogleAuth();

  async function request() {
    if (!targetAudience) {
      // Use the request URL hostname as the target audience for Cloud Run requests
      const {URL} = require('url');
      targetAudience = new URL(url).origin;
    }
    console.info(
      `request Cloud Run ${url} with target audience ${targetAudience}`
    );
    const client = await auth.getIdTokenClient(targetAudience);
    const res = await client.request({url});
    console.info(res.data);
  }

  request().catch(err => {
    console.error(err.message);
    process.exitCode = 1;
  });
  // [END google_auth_idtoken_cloudrun]
}

const args = process.argv.slice(2);
main(...args);

下面是 node.js 中的一个示例,它正确地创建了具有正确受众的身份令牌以调用 Cloud 运行 或 Cloud Functions 服务。

修改此示例以适应 GraphQLClient。不要忘记在每次调用中包含授权 header。

    // This program creates an OIDC Identity Token from a service account
    // and calls an HTTP endpoint with the Identity Token as the authorization
    
    var { google } = require('googleapis')
    const request = require('request')
    
    // The service account JSON key file to use to create the Identity Token
    let privatekey = require('/config/service-account.json')
    
    // The HTTP endpoint to call with an Identity Token for authorization
    // Note: This url is using a custom domain. Do not use the same domain for the audience
    let url = 'https://example.jhanley.dev'
    
    // The audience that this ID token is intended for (example Google Cloud Run service URL)
    // Do not use a custom domain name, use the Assigned by Cloud Run url
    let audience = 'https://example-ylabperdfq-uc.a.run.app'
    
    let jwtClient = new google.auth.JWT(
        privatekey.client_email,
        null,
        privatekey.private_key,
        audience
    )
    
    jwtClient.authorize(function(err, _token) {
        if (err) {
            console.log(err)
            return err
        } else {
            // console.log('token obj:', _token)
    
            request(
                {
                    url: url,
                    headers: {
                        "Authorization": "Bearer " + _token.id_token
                    }
                },
                function(err, response, body) {
                    if (err) {
                        console.log(err)
                        return err
                    } else {
                        // console.log('Response:', response)
                        console.log(body)
                    }
                }
            );
        }
    })

致那些不想因为缺少文档而浪费一整天工作的人。这是当今世界公认的答案,因为 JWT class 不再接受构造函数中的观众。

import { JWT } from "google-auth-library"

const client = new JWT({
  forceRefreshOnFailure: true,
  key: service_account.private_key,
  email: service_account.client_email,
})

const token = await client.fetchIdToken("cloud run endpoint")
const { data } = await axios.post("cloud run endpoint"/path, payload, {
  headers: {
    Authorization: `Bearer ${token}`
  }
}) 

return data