使用用户账号对安装式应用进行身份验证

本指南介绍了在您的应用已安装到用户机器上的情况下,如何使用用户账号执行身份验证,以获得 BigQuery API 的访问权限。

如需确保应用仅访问最终用户可用的 BigQuery 表,请使用用户凭据进行身份验证。用户凭据只能对最终用户的 Google Cloud 项目(而非应用的项目)运行查询。因此,系统会按查询(而非应用)向用户收取费用。

准备工作

  1. 创建一个代表已安装的应用的 Google Cloud 项目
  2. 安装 BigQuery 客户端库
  3. 安装身份验证库。

    Java

    如果您使用的是 Maven,请在 Pom 文件中添加以下依赖项。

    <dependency>
      <groupId>com.google.oauth-client</groupId>
      <artifactId>google-oauth-client-java6</artifactId>
      <version>1.31.0</version>
    </dependency>
    <dependency>
      <groupId>com.google.oauth-client</groupId>
      <artifactId>google-oauth-client-jetty</artifactId>
      <version>1.31.0</version>
    </dependency>

    Python

    安装用于 Google 身份验证的 oauthlib 集成

    pip install --upgrade google-auth-oauthlib

    Node.js

    安装用于 Google 身份验证的 oauthlib 集成

    npm install google-auth-library
    npm install readline-promise

设置您的客户端凭据

使用以下按钮选择项目并创建所需的凭据。

获取凭据

手动创建凭据

  1. 进入 Google Cloud 控制台中的凭据页面
  2. 填写 OAuth 同意屏幕中的必填字段。
  3. 凭据页面上,点击创建凭据按钮。

    选择 OAuth 客户端 ID

  4. 选择桌面设备作为应用类型,然后点击创建
  5. 点击下载 JSON 按钮以下载凭据。

    下载 JSON。

    将凭据文件保存到 client_secrets.json。此文件必须随应用一起分发。

API 的身份验证与调用

  1. 使用客户端凭据执行 OAuth 2.0 流程

    Java

    import com.google.api.client.auth.oauth2.Credential;
    import com.google.api.client.extensions.java6.auth.oauth2.AuthorizationCodeInstalledApp;
    import com.google.api.client.extensions.jetty.auth.oauth2.LocalServerReceiver;
    import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow;
    import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets;
    import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
    import com.google.api.client.json.JsonFactory;
    import com.google.api.client.json.jackson2.JacksonFactory;
    import com.google.api.client.util.store.FileDataStoreFactory;
    import com.google.api.gax.paging.Page;
    import com.google.auth.oauth2.GoogleCredentials;
    import com.google.auth.oauth2.UserCredentials;
    import com.google.cloud.bigquery.BigQuery;
    import com.google.cloud.bigquery.BigQueryException;
    import com.google.cloud.bigquery.BigQueryOptions;
    import com.google.cloud.bigquery.Dataset;
    import com.google.common.collect.ImmutableList;
    import java.io.File;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    import java.nio.file.Files;
    import java.nio.file.Path;
    import java.nio.file.Paths;
    import java.security.GeneralSecurityException;
    import java.util.List;
    
    // Sample to authenticate by using a user credential
    public class AuthUserFlow {
    
      private static final File DATA_STORE_DIR =
          new File(AuthUserFlow.class.getResource("/").getPath(), "credentials");
      private static final JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();
      // i.e redirect_uri http://localhost:61984/Callback
      private static final int LOCAL_RECEIVER_PORT = 61984;
    
      public static void runAuthUserFlow() {
        // TODO(developer): Replace these variables before running the sample.
        /**
         * Download your OAuth2 configuration from the Google Developers Console API Credentials page.
         * https://console.cloud.google.com/apis/credentials
         */
        Path credentialsPath = Paths.get("path/to/your/client_secret.json");
        List<String> scopes = ImmutableList.of("https://www.googleapis.com/auth/bigquery");
        authUserFlow(credentialsPath, scopes);
      }
    
      public static void authUserFlow(Path credentialsPath, List<String> selectedScopes) {
        // Reading credentials file
        try (InputStream inputStream = Files.newInputStream(credentialsPath)) {
    
          // Load client_secret.json file
          GoogleClientSecrets clientSecrets =
              GoogleClientSecrets.load(JSON_FACTORY, new InputStreamReader(inputStream));
          String clientId = clientSecrets.getDetails().getClientId();
          String clientSecret = clientSecrets.getDetails().getClientSecret();
    
          // Generate the url that will be used for the consent dialog.
          GoogleAuthorizationCodeFlow flow =
              new GoogleAuthorizationCodeFlow.Builder(
                      GoogleNetHttpTransport.newTrustedTransport(),
                      JSON_FACTORY,
                      clientSecrets,
                      selectedScopes)
                  .setDataStoreFactory(new FileDataStoreFactory(DATA_STORE_DIR))
                  .setAccessType("offline")
                  .setApprovalPrompt("auto")
                  .build();
    
          // Exchange an authorization code for  refresh token
          LocalServerReceiver receiver =
              new LocalServerReceiver.Builder().setPort(LOCAL_RECEIVER_PORT).build();
          Credential credential = new AuthorizationCodeInstalledApp(flow, receiver).authorize("user");
    
          // OAuth2 Credentials representing a user's identity and consent
          GoogleCredentials credentials =
              UserCredentials.newBuilder()
                  .setClientId(clientId)
                  .setClientSecret(clientSecret)
                  .setRefreshToken(credential.getRefreshToken())
                  .build();
    
          // Initialize client that will be used to send requests. This client only needs to be created
          // once, and can be reused for multiple requests.
          BigQuery bigquery =
              BigQueryOptions.newBuilder().setCredentials(credentials).build().getService();
    
          Page<Dataset> datasets = bigquery.listDatasets(BigQuery.DatasetListOption.pageSize(100));
          if (datasets == null) {
            System.out.println("Dataset does not contain any models");
            return;
          }
          datasets
              .iterateAll()
              .forEach(
                  dataset -> System.out.printf("Success! Dataset ID: %s ", dataset.getDatasetId()));
    
        } catch (BigQueryException | IOException | GeneralSecurityException ex) {
          System.out.println("Project does not contain any datasets \n" + ex.toString());
        }
      }
    }

    Python

    from google_auth_oauthlib import flow
    
    # A local server is used as the callback URL in the auth flow.
    appflow = flow.InstalledAppFlow.from_client_secrets_file(
        "client_secrets.json", scopes=["https://www.googleapis.com/auth/bigquery"]
    )
    
    # This launches a local server to be used as the callback URL in the desktop
    # app auth flow. If you are accessing the application remotely, such as over
    # SSH or a remote Jupyter notebook, this flow will not work. Use the
    # `gcloud auth application-default login --no-browser` command or workload
    # identity federation to get authentication tokens, instead.
    #
    appflow.run_local_server()
    
    credentials = appflow.credentials

    Node.js

    const {OAuth2Client} = require('google-auth-library');
    const readline = require('readline-promise').default;
    
    function startRl() {
      const rl = readline.createInterface({
        input: process.stdin,
        output: process.stdout,
      });
    
      return rl;
    }
    
    /**
     * Download your OAuth2 configuration from the Google
     * Developers Console API Credentials page.
     * https://console.cloud.google.com/apis/credentials
     */
    const keys = require('./oauth2.keys.json');
    
    /**
     * Create a new OAuth2Client, and go through the OAuth2 content
     * workflow. Return the full client to the callback.
     */
    async function getRedirectUrl() {
      const rl = main.startRl();
      // 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.installed.client_id,
        keys.installed.client_secret,
        keys.installed.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/bigquery',
        prompt: 'consent',
      });
    
      console.info(
        `Please visit this URL to authorize this application: ${authorizeUrl}`
      );
    
      const code = await rl.questionAsync('Enter the authorization code: ');
      const tokens = await main.exchangeCode(code);
      rl.close();
    
      return tokens;
    }
    
    // Exchange an authorization code for an access token
    async function exchangeCode(code) {
      const oAuth2Client = new OAuth2Client(
        keys.installed.client_id,
        keys.installed.client_secret,
        keys.installed.redirect_uris[0]
      );
    
      const r = await oAuth2Client.getToken(code);
      console.info(r.tokens);
      return r.tokens;
    }
    
    async function authFlow(projectId = 'project_id') {
      /**
       * TODO(developer):
       * Save Project ID as environment variable PROJECT_ID="project_id"
       * Uncomment the following line before running the sample.
       */
      // projectId = process.env.PROJECT_ID;
    
      const tokens = await main.getRedirectUrl();
    
      const credentials = {
        type: 'authorized_user',
        client_id: keys.installed.client_id,
        client_secret: keys.installed.client_secret,
        refresh_token: tokens.refresh_token,
      };
    
      return {
        projectId,
        credentials,
      };
    }
  2. 使用经过身份验证的凭据连接到 BigQuery API。

    Java

    import com.google.api.client.auth.oauth2.Credential;
    import com.google.api.client.extensions.java6.auth.oauth2.AuthorizationCodeInstalledApp;
    import com.google.api.client.extensions.jetty.auth.oauth2.LocalServerReceiver;
    import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow;
    import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets;
    import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
    import com.google.api.client.json.JsonFactory;
    import com.google.api.client.json.jackson2.JacksonFactory;
    import com.google.api.client.util.store.FileDataStoreFactory;
    import com.google.auth.oauth2.GoogleCredentials;
    import com.google.auth.oauth2.UserCredentials;
    import com.google.cloud.bigquery.BigQuery;
    import com.google.cloud.bigquery.BigQueryException;
    import com.google.cloud.bigquery.BigQueryOptions;
    import com.google.cloud.bigquery.QueryJobConfiguration;
    import com.google.cloud.bigquery.TableResult;
    import com.google.common.collect.ImmutableList;
    import java.io.File;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    import java.nio.file.Files;
    import java.nio.file.Path;
    import java.nio.file.Paths;
    import java.security.GeneralSecurityException;
    import java.util.List;
    
    // Sample to query by using a user credential
    public class AuthUserQuery {
    
      private static final File DATA_STORE_DIR =
          new File(AuthUserQuery.class.getResource("/").getPath(), "credentials");
      private static final JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();
      // i.e redirect_uri http://localhost:61984/Callback
      private static final int LOCAL_RECEIVER_PORT = 61984;
    
      public static void runAuthUserQuery() {
        // TODO(developer): Replace these variables before running the sample.
        /**
         * Download your OAuth2 configuration from the Google Developers Console API Credentials page.
         * https://console.cloud.google.com/apis/credentials
         */
        Path credentialsPath = Paths.get("path/to/your/client_secret.json");
        List<String> scopes = ImmutableList.of("https://www.googleapis.com/auth/bigquery");
        String query =
            "SELECT name, SUM(number) as total"
                + "  FROM `bigquery-public-data.usa_names.usa_1910_current`"
                + "  WHERE name = 'William'"
                + "  GROUP BY name;";
        authUserQuery(credentialsPath, scopes, query);
      }
    
      public static void authUserQuery(
          Path credentialsPath, List<String> selectedScopes, String query) {
        // Reading credentials file
        try (InputStream inputStream = Files.newInputStream(credentialsPath)) {
    
          // Load client_secret.json file
          GoogleClientSecrets clientSecrets =
              GoogleClientSecrets.load(JSON_FACTORY, new InputStreamReader(inputStream));
          String clientId = clientSecrets.getDetails().getClientId();
          String clientSecret = clientSecrets.getDetails().getClientSecret();
    
          // Generate the url that will be used for the consent dialog.
          GoogleAuthorizationCodeFlow flow =
              new GoogleAuthorizationCodeFlow.Builder(
                      GoogleNetHttpTransport.newTrustedTransport(),
                      JSON_FACTORY,
                      clientSecrets,
                      selectedScopes)
                  .setDataStoreFactory(new FileDataStoreFactory(DATA_STORE_DIR))
                  .setAccessType("offline")
                  .setApprovalPrompt("auto")
                  .build();
    
          // Exchange an authorization code for  refresh token
          LocalServerReceiver receiver =
              new LocalServerReceiver.Builder().setPort(LOCAL_RECEIVER_PORT).build();
          Credential credential = new AuthorizationCodeInstalledApp(flow, receiver).authorize("user");
    
          // OAuth2 Credentials representing a user's identity and consent
          GoogleCredentials credentials =
              UserCredentials.newBuilder()
                  .setClientId(clientId)
                  .setClientSecret(clientSecret)
                  .setRefreshToken(credential.getRefreshToken())
                  .build();
    
          // Initialize client that will be used to send requests. This client only needs to be created
          // once, and can be reused for multiple requests.
          BigQuery bigquery =
              BigQueryOptions.newBuilder().setCredentials(credentials).build().getService();
    
          QueryJobConfiguration queryConfig = QueryJobConfiguration.newBuilder(query).build();
    
          TableResult results = bigquery.query(queryConfig);
    
          results
              .iterateAll()
              .forEach(row -> row.forEach(val -> System.out.printf("%s,", val.toString())));
    
          System.out.println("Query performed successfully.");
    
        } catch (BigQueryException | IOException | GeneralSecurityException | InterruptedException ex) {
          System.out.println("Query not performed \n" + ex.toString());
        }
      }
    }

    Python

    from google.cloud import bigquery
    
    # TODO: Uncomment the line below to set the `project` variable.
    # project = 'user-project-id'
    #
    # The `project` variable defines the project to be billed for query
    # processing. The user must have the bigquery.jobs.create permission on
    # this project to run a query. See:
    # https://cloud.google.com/bigquery/docs/access-control#permissions
    
    client = bigquery.Client(project=project, credentials=credentials)
    
    query_string = """SELECT name, SUM(number) as total
    FROM `bigquery-public-data.usa_names.usa_1910_current`
    WHERE name = 'William'
    GROUP BY name;
    """
    results = client.query_and_wait(query_string)
    
    # Print the results.
    for row in results:  # Wait for the job to complete.
        print("{}: {}".format(row["name"], row["total"]))

    Node.js

    async function query() {
      const {BigQuery} = require('@google-cloud/bigquery');
    
      const credentials = await main.authFlow();
      const bigquery = new BigQuery(credentials);
    
      // Queries the U.S. given names dataset for the state of Texas.
      const query = `SELECT name, SUM(number) as total
      FROM \`bigquery-public-data.usa_names.usa_1910_current\`
      WHERE name = 'William'
      GROUP BY name;`;
    
      // For all options, see https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs/query
      const options = {
        query: query,
      };
    
      // Run the query as a job
      const [job] = await bigquery.createQueryJob(options);
      console.log(`Job ${job.id} started.`);
    
      // Wait for the query to finish
      const [rows] = await job.getQueryResults();
    
      // Print the results
      console.log('Rows:');
      rows.forEach(row => console.log(row));
    
      return rows;
    }
    
    const main = {
      query,
      authFlow,
      exchangeCode,
      getRedirectUrl,
      startRl,
    };
    module.exports = {
      main,
    };
    
    if (module === require.main) {
      query().catch(console.error);
    }

当您运行示例代码时,它将启动一个浏览器,请求授予权限来访问与客户端密钥关联的项目。您可以使用所得到的凭据访问用户的 BigQuery 资源,因为示例代码请求的是 BigQuery 范围

后续步骤

  1. 了解对应用进行身份验证以访问 BigQuery API 的其他方式
  2. 了解如何使用最终用户凭据针对所有 Cloud API 进行身份验证