建立權限範圍縮小的短期憑證

本頁說明如何使用憑證存取權界線,建立具有範圍縮減 Cloud Storage 權限的 OAuth 2.0 存取權杖。

建立權限範圍縮減的權杖時,需要完成下列步驟:

  1. 將適當的 IAM 角色授予使用者或服務帳戶。
  2. 定義憑證存取權界線,為使用者或服務帳戶可用的權限設定上限。
  3. 為使用者或服務帳戶建立 OAuth 2.0 存取權杖。
  4. 將 OAuth 2.0 存取權杖換成新的權杖,並遵守憑證存取邊界。

接著,您可以使用新的範圍縮減 OAuth 2.0 存取權杖,驗證對 Cloud Storage 的要求。

事前準備

使用憑證存取邊界前,請確認符合下列條件:

  • 您只需要縮減 Cloud Storage 的權限,其他Google Cloud 服務則不需要。

    如要縮減其他 Google Cloud服務的權限範圍,可以建立多個服務帳戶,並為每個服務帳戶授予不同的角色組合。

  • 您可以使用 OAuth 2.0 存取權杖進行驗證。 其他類型的短期憑證不支援憑證存取權界線。

此外,您必須啟用必要的 API:

  • Enable the IAM and Security Token Service APIs.

    Enable the APIs

授予 IAM 角色

憑證存取邊界會為資源設定可用權限的上限。這項政策可以從主體減去權限,但無法新增主體尚未擁有的權限。

因此,您也必須在 Cloud Storage bucket較高層級的資源 (例如專案) 上,授予主體所需的權限。

舉例來說,假設您需要建立範圍縮減的短期憑證,允許服務帳戶在 bucket 中建立物件:

  • 您至少必須授予服務帳戶包含 storage.objects.create 權限的角色,例如儲存空間物件建立者角色 (roles/storage.objectCreator)。憑證存取邊界也必須包含這項權限。
  • 您也可以授予包含更多權限的角色,例如「儲存空間物件管理員」角色 (roles/storage.objectAdmin)。服務帳戶只能使用角色授予和憑證存取權界線中同時顯示的權限。

如要瞭解 Cloud Storage 的預先定義角色,請參閱 Cloud Storage 角色

定義憑證存取權範圍

憑證存取權範圍是包含存取權範圍規則清單的物件。規則由參數組成,可指定使用者或服務帳戶可用的權限上限。如要定義憑證存取權範圍,請建立 JSON 物件,列出存取權範圍規則及其參數。

以下是憑證存取權範圍的範例:

{
  "accessBoundary": {
    "accessBoundaryRules": [
      {
        "availablePermissions": [
          "inRole:ROLE_ID"
        ],
        "availableResource": "//storage.googleapis.com/projects/_/buckets/BUCKET_NAME"
         "availabilityCondition": {
            "expression" : "CONDITION"
      }
    ]
  }
}

更改下列內容:

  • ROLE_ID:預先定義或自訂角色的 ID,用於定義資源可用權限的上限。例如,roles/storage.objectViewer。如要指定多個角色,請新增一行,並加上 inRole: 前置字串,然後輸入角色 ID。您只能使用指定角色中的權限。
  • BUCKET_NAME:規則適用的 Cloud Storage bucket 名稱。
  • CONDITION:選用。條件運算式,用於指定可使用權限的 Cloud Storage 物件。舉例來說,下列條件會為名稱開頭為 customer-a 的物件提供權限:

    resource.name.startsWith('projects/_/buckets/example-bucket/objects/customer-a')
    

如要進一步瞭解如何建立及自訂憑證存取邊界,請參閱「憑證存取邊界元件」。

如需憑證存取權界線的潛在用途範例,請參閱憑證存取權界線範例

建立 OAuth 2.0 存取權杖

建立範圍縮減的短期憑證前,您必須先建立一般的 OAuth 2.0 存取權杖。然後,您可以使用一般憑證換取範圍縮減的憑證。建立存取權杖時,請使用 OAuth 2.0 範圍 https://www.googleapis.com/auth/cloud-platform

如要為服務帳戶建立存取權杖,您可以完成伺服器對伺服器的 OAuth 2.0 流程,也可以使用 Service Account Credentials API 產生 OAuth 2.0 存取權杖

如要為使用者建立存取權杖,請參閱「取得 OAuth 2.0 存取權杖」。您也可以使用 OAuth 2.0 Playground,為自己的 Google 帳戶建立存取權杖。

交換 OAuth 2.0 存取權杖

建立 OAuth 2.0 存取憑證後,您可以將存取憑證換成受憑證存取範圍限制的憑證。這個程序通常會涉及權杖代理人權杖消費者

  • 權杖代理人負責定義憑證存取權範圍,並將存取權杖換成權限範圍縮小的權杖。

    權杖代理程式可以使用支援的驗證程式庫自動交換存取權杖,也可以呼叫安全權杖服務手動交換權杖。

  • 權杖消費者會向權杖代理人要求範圍縮減的存取權杖,然後使用該權杖執行其他動作。

    權杖消費者可以使用支援的驗證程式庫,在存取權杖過期前自動更新。或者,也可以手動更新權杖,或允許權杖過期而不更新。

您可以透過下列兩種方式,將存取權杖換成範圍縮減的權杖:

  • 用戶端權杖交換 (建議):用戶端會從 Security Token Service API 伺服器取得加密編譯資料。用戶端可使用密碼編譯資料,在用戶端獨立產生具有不同憑證存取邊界規則的範圍縮減權杖,效期為一段時間 (例如 1 小時)。這種做法可縮短延遲時間並提高效率,特別是需要頻繁更新憑證存取邊界規則的用戶端。如果應用程式需要產生許多不重複的範圍縮減權杖,這種做法也更有效率。建議您採用這種做法,因為可提供更優異的效能、擴充性,以及日後的功能相容性。

  • 伺服器端權杖交換:每當憑證存取權界線規則變更時,用戶端都會向安全權杖服務 API 伺服器要求新的範圍縮減權杖。這種方法很簡單,但每次要求範圍縮減的權杖時,都必須往返於 Security Token Service API 伺服器。由於每次縮減範圍的權杖要求都會往返於安全權杖服務 API,因此建議只有在用戶端程式庫不支援用戶端權杖交換時,才採用這種方法。

用戶端權杖交換

如果您使用下列語言建立權杖中介服務和權杖消費者,即可透過用戶端方法,使用 Google 的驗證程式庫自動交換及更新權杖。

Java

如果是 Java,您可以使用 1.32.1 以上版本的 com.google.auth:google-auth-library-cab-token-generator 構件,自動交換及重新整理權杖。

如要檢查構件版本,請在應用程式目錄中執行下列 Maven 指令:

mvn dependency:list -DincludeArtifactIds=google-auth-library-cab-token-generator

以下範例說明權杖代理人如何產生範圍縮減的權杖:

import com.google.auth.credentialaccessboundary.ClientSideCredentialAccessBoundaryFactory;
import com.google.auth.oauth2.AccessToken;
import com.google.auth.oauth2.CredentialAccessBoundary;
import com.google.auth.oauth2.GoogleCredentials;
import dev.cel.common.CelValidationException;
import java.io.IOException;
import java.security.GeneralSecurityException;
  public static AccessToken getTokenFromBroker(String bucketName, String objectPrefix)
      throws IOException {
    // Retrieve the source credentials from ADC.
    GoogleCredentials sourceCredentials =
        GoogleCredentials.getApplicationDefault()
            .createScoped("https://www.googleapis.com/auth/cloud-platform");

    // Initialize the Credential Access Boundary rules.
    String availableResource = "//storage.googleapis.com/projects/_/buckets/" + bucketName;

    // Downscoped credentials will have readonly access to the resource.
    String availablePermission = "inRole:roles/storage.objectViewer";

    // Only objects starting with the specified prefix string in the object name will be allowed
    // read access.
    String expression =
        "resource.name.startsWith('projects/_/buckets/"
            + bucketName
            + "/objects/"
            + objectPrefix
            + "')";

    // Build the AvailabilityCondition.
    CredentialAccessBoundary.AccessBoundaryRule.AvailabilityCondition availabilityCondition =
        CredentialAccessBoundary.AccessBoundaryRule.AvailabilityCondition.newBuilder()
            .setExpression(expression)
            .build();

    // Define the single access boundary rule using the above properties.
    CredentialAccessBoundary.AccessBoundaryRule rule =
        CredentialAccessBoundary.AccessBoundaryRule.newBuilder()
            .setAvailableResource(availableResource)
            .addAvailablePermission(availablePermission)
            .setAvailabilityCondition(availabilityCondition)
            .build();

    // Define the Credential Access Boundary with all the relevant rules.
    CredentialAccessBoundary credentialAccessBoundary =
        CredentialAccessBoundary.newBuilder().addRule(rule).build();

    // Create an instance of ClientSideCredentialAccessBoundaryFactory.
    ClientSideCredentialAccessBoundaryFactory factory =
        ClientSideCredentialAccessBoundaryFactory.newBuilder()
            .setSourceCredential(sourceCredentials)
            .build();

    // Generate the token and pass it to the Token Consumer.
    try {
      return factory.generateToken(credentialAccessBoundary);
    } catch (GeneralSecurityException | CelValidationException e) {
      throw new IOException("Error generating downscoped token", e);
    }
  }

以下範例說明權杖消費者如何使用重新整理處理常式,自動取得及重新整理範圍縮減的權杖:

import com.google.auth.oauth2.AccessToken;
import com.google.auth.oauth2.OAuth2CredentialsWithRefresh;
import com.google.cloud.storage.Blob;
import com.google.cloud.storage.Storage;
import com.google.cloud.storage.StorageOptions;
import java.io.IOException;
  public static String retrieveBlobWithDownscopedToken(
      final String bucketName, final String objectName) throws IOException {
    // You can pass an `OAuth2RefreshHandler` to `OAuth2CredentialsWithRefresh` which will allow the
    // library to seamlessly handle downscoped token refreshes on expiration.
    OAuth2CredentialsWithRefresh.OAuth2RefreshHandler handler =
        new OAuth2CredentialsWithRefresh.OAuth2RefreshHandler() {
          @Override
          public AccessToken refreshAccessToken() throws IOException {
            // The common pattern of usage is to have a token broker pass the downscoped short-lived
            // access tokens to a token consumer via some secure authenticated channel.
            // For illustration purposes, we are generating the downscoped token locally.
            // We want to test the ability to limit access to objects with a certain prefix string
            // in the resource bucket. objectName.substring(0, 3) is the prefix here. This field is
            // not required if access to all bucket resources are allowed. If access to limited
            // resources in the bucket is needed, this mechanism can be used.
            return DownscopedAccessTokenGenerator
                .getTokenFromBroker(bucketName, objectName);
          }
        };

    AccessToken downscopedToken = handler.refreshAccessToken();

    OAuth2CredentialsWithRefresh credentials =
        OAuth2CredentialsWithRefresh.newBuilder()
            .setAccessToken(downscopedToken)
            .setRefreshHandler(handler)
            .build();

    StorageOptions options = StorageOptions.newBuilder().setCredentials(credentials).build();
    Storage storage = options.getService();

    Blob blob = storage.get(bucketName, objectName);
    if (blob == null) {
      return null;
    }
    return new String(blob.getContent());
  }

伺服器端權杖交換

本節說明可透過服務端方法交換權杖的下列方法:

使用伺服器端方法自動交換及更新存取權杖

如果您使用下列其中一種語言建立權杖代理人和權杖消費者,可以透過伺服器端權杖產生方法,使用 Google 的驗證程式庫自動交換及更新權杖:

Go

如果是 Go,您可以使用 golang.org/x/oauth2 套件的 v0.0.0-20210819190943-2bc19b11175f 以上版本,自動交換及重新整理權杖。

如要檢查套件版本,請在應用程式目錄中執行下列指令:

go list -m golang.org/x/oauth2

以下範例說明權杖代理人如何產生範圍縮減的權杖:


import (
	"context"
	"fmt"

	"golang.org/x/oauth2"
	"golang.org/x/oauth2/google"
	"golang.org/x/oauth2/google/downscope"
)

// createDownscopedToken would be run on the token broker in order to generate
// a downscoped access token that only grants access to objects whose name begins with prefix.
// The token broker would then pass the newly created token to the requesting token consumer for use.
func createDownscopedToken(bucketName string, prefix string) error {
	// bucketName := "foo"
	// prefix := "profile-picture-"

	ctx := context.Background()
	// A condition can optionally be provided to further restrict access permissions.
	condition := downscope.AvailabilityCondition{
		Expression:  "resource.name.startsWith('projects/_/buckets/" + bucketName + "/objects/" + prefix + "')",
		Title:       prefix + " Only",
		Description: "Restricts a token to only be able to access objects that start with `" + prefix + "`",
	}
	// Initializes an accessBoundary with one Rule which restricts the downscoped
	// token to only be able to access the bucket "bucketName" and only grants it the
	// permission "storage.objectViewer".
	accessBoundary := []downscope.AccessBoundaryRule{
		{
			AvailableResource:    "//storage.googleapis.com/projects/_/buckets/" + bucketName,
			AvailablePermissions: []string{"inRole:roles/storage.objectViewer"},
			Condition:            &condition, // Optional
		},
	}

	// This Source can be initialized in multiple ways; the following example uses
	// Application Default Credentials.
	var rootSource oauth2.TokenSource

	// You must provide the "https://www.googleapis.com/auth/cloud-platform" scope.
	rootSource, err := google.DefaultTokenSource(ctx, "https://www.googleapis.com/auth/cloud-platform")
	if err != nil {
		return fmt.Errorf("failed to generate rootSource: %w", err)
	}

	// downscope.NewTokenSource constructs the token source with the configuration provided.
	dts, err := downscope.NewTokenSource(ctx, downscope.DownscopingConfig{RootSource: rootSource, Rules: accessBoundary})
	if err != nil {
		return fmt.Errorf("failed to generate downscoped token source: %w", err)
	}
	// Token() uses the previously declared TokenSource to generate a downscoped token.
	tok, err := dts.Token()
	if err != nil {
		return fmt.Errorf("failed to generate token: %w", err)
	}
	// Pass this token back to the token consumer.
	_ = tok
	return nil
}

以下範例說明權杖消費者如何使用重新整理處理常式,自動取得及重新整理範圍縮減的權杖:


import (
	"context"
	"fmt"
	"io"

	"golang.org/x/oauth2/google"
	"golang.org/x/oauth2/google/downscope"

	"cloud.google.com/go/storage"
	"golang.org/x/oauth2"
	"google.golang.org/api/option"
)

// A token consumer should define their own tokenSource. In the Token() method,
// it should send a query to a token broker requesting a downscoped token.
// The token broker holds the root credential that is used to generate the
// downscoped token.
type localTokenSource struct {
	ctx        context.Context
	bucketName string
	brokerURL  string
}

func (lts localTokenSource) Token() (*oauth2.Token, error) {
	var remoteToken *oauth2.Token
	// Usually you would now retrieve remoteToken, an oauth2.Token, from token broker.
	// This snippet performs the same functionality locally.
	accessBoundary := []downscope.AccessBoundaryRule{
		{
			AvailableResource:    "//storage.googleapis.com/projects/_/buckets/" + lts.bucketName,
			AvailablePermissions: []string{"inRole:roles/storage.objectViewer"},
		},
	}
	rootSource, err := google.DefaultTokenSource(lts.ctx, "https://www.googleapis.com/auth/cloud-platform")
	if err != nil {
		return nil, fmt.Errorf("failed to generate rootSource: %w", err)
	}
	dts, err := downscope.NewTokenSource(lts.ctx, downscope.DownscopingConfig{RootSource: rootSource, Rules: accessBoundary})
	if err != nil {
		return nil, fmt.Errorf("failed to generate downscoped token source: %w", err)
	}
	// Token() uses the previously declared TokenSource to generate a downscoped token.
	remoteToken, err = dts.Token()
	if err != nil {
		return nil, fmt.Errorf("failed to generate token: %w", err)
	}

	return remoteToken, nil
}

// getObjectContents will read the contents of an object in Google Storage
// named objectName, contained in the bucket "bucketName".
func getObjectContents(output io.Writer, bucketName string, objectName string) error {
	// bucketName := "foo"
	// prefix := "profile-picture-"

	ctx := context.Background()

	thisTokenSource := localTokenSource{
		ctx:        ctx,
		bucketName: bucketName,
		brokerURL:  "yourURL.com/internal/broker",
	}

	// Wrap the TokenSource in an oauth2.ReuseTokenSource to enable automatic refreshing.
	refreshableTS := oauth2.ReuseTokenSource(nil, thisTokenSource)
	// You can now use the token source to access Google Cloud Storage resources as follows.
	storageClient, err := storage.NewClient(ctx, option.WithTokenSource(refreshableTS))
	if err != nil {
		return fmt.Errorf("failed to create the storage client: %w", err)
	}
	defer storageClient.Close()
	bkt := storageClient.Bucket(bucketName)
	obj := bkt.Object(objectName)
	rc, err := obj.NewReader(ctx)
	if err != nil {
		return fmt.Errorf("failed to retrieve the object: %w", err)
	}
	defer rc.Close()
	data, err := io.ReadAll(rc)
	if err != nil {
		return fmt.Errorf("could not read the object's contents: %w", err)
	}
	// Data now contains the contents of the requested object.
	output.Write(data)
	return nil
}

Java

如果是 Java,您可以使用 1.1.0 以上版本的 com.google.auth:google-auth-library-oauth2-http 構件,自動交換及重新整理權杖。

如要檢查構件版本,請在應用程式目錄中執行下列 Maven 指令:

mvn dependency:list -DincludeArtifactIds=google-auth-library-oauth2-http

以下範例說明權杖代理人如何產生範圍縮減的權杖:

public static AccessToken getTokenFromBroker(String bucketName, String objectPrefix)
    throws IOException {
  // Retrieve the source credentials from ADC.
  GoogleCredentials sourceCredentials =
      GoogleCredentials.getApplicationDefault()
          .createScoped("https://www.googleapis.com/auth/cloud-platform");

  // Initialize the Credential Access Boundary rules.
  String availableResource = "//storage.googleapis.com/projects/_/buckets/" + bucketName;

  // Downscoped credentials will have readonly access to the resource.
  String availablePermission = "inRole:roles/storage.objectViewer";

  // Only objects starting with the specified prefix string in the object name will be allowed
  // read access.
  String expression =
      "resource.name.startsWith('projects/_/buckets/"
          + bucketName
          + "/objects/"
          + objectPrefix
          + "')";

  // Build the AvailabilityCondition.
  CredentialAccessBoundary.AccessBoundaryRule.AvailabilityCondition availabilityCondition =
      CredentialAccessBoundary.AccessBoundaryRule.AvailabilityCondition.newBuilder()
          .setExpression(expression)
          .build();

  // Define the single access boundary rule using the above properties.
  CredentialAccessBoundary.AccessBoundaryRule rule =
      CredentialAccessBoundary.AccessBoundaryRule.newBuilder()
          .setAvailableResource(availableResource)
          .addAvailablePermission(availablePermission)
          .setAvailabilityCondition(availabilityCondition)
          .build();

  // Define the Credential Access Boundary with all the relevant rules.
  CredentialAccessBoundary credentialAccessBoundary =
      CredentialAccessBoundary.newBuilder().addRule(rule).build();

  // Create the downscoped credentials.
  DownscopedCredentials downscopedCredentials =
      DownscopedCredentials.newBuilder()
          .setSourceCredential(sourceCredentials)
          .setCredentialAccessBoundary(credentialAccessBoundary)
          .build();

  // Retrieve the token.
  // This will need to be passed to the Token Consumer.
  AccessToken accessToken = downscopedCredentials.refreshAccessToken();
  return accessToken;
}

以下範例說明權杖消費者如何使用重新整理處理常式,自動取得及重新整理範圍縮減的權杖:

public static void tokenConsumer(final String bucketName, final String objectName)
    throws IOException {
  // You can pass an `OAuth2RefreshHandler` to `OAuth2CredentialsWithRefresh` which will allow the
  // library to seamlessly handle downscoped token refreshes on expiration.
  OAuth2CredentialsWithRefresh.OAuth2RefreshHandler handler =
      new OAuth2CredentialsWithRefresh.OAuth2RefreshHandler() {
        @Override
        public AccessToken refreshAccessToken() throws IOException {
          // The common pattern of usage is to have a token broker pass the downscoped short-lived
          // access tokens to a token consumer via some secure authenticated channel.
          // For illustration purposes, we are generating the downscoped token locally.
          // We want to test the ability to limit access to objects with a certain prefix string
          // in the resource bucket. objectName.substring(0, 3) is the prefix here. This field is
          // not required if access to all bucket resources are allowed. If access to limited
          // resources in the bucket is needed, this mechanism can be used.
          return getTokenFromBroker(bucketName, objectName.substring(0, 3));
        }
      };

  // Downscoped token retrieved from token broker.
  AccessToken downscopedToken = handler.refreshAccessToken();

  // Create the OAuth2CredentialsWithRefresh from the downscoped token and pass a refresh handler
  // which will handle token expiration.
  // This will allow the consumer to seamlessly obtain new downscoped tokens on demand every time
  // token expires.
  OAuth2CredentialsWithRefresh credentials =
      OAuth2CredentialsWithRefresh.newBuilder()
          .setAccessToken(downscopedToken)
          .setRefreshHandler(handler)
          .build();

  // Use the credentials with the Cloud Storage SDK.
  StorageOptions options = StorageOptions.newBuilder().setCredentials(credentials).build();
  Storage storage = options.getService();

  // Call Cloud Storage APIs.
  Blob blob = storage.get(bucketName, objectName);
  String content = new String(blob.getContent());
  System.out.println(
      "Retrieved object, "
          + objectName
          + ", from bucket,"
          + bucketName
          + ", with content: "
          + content);
}

Node.js

如果是 Node.js,您可以使用 google-auth-library 套件 7.9.0 以上版本,自動交換及重新整理權杖。

如要檢查套件版本,請在應用程式目錄中執行下列指令:

npm list google-auth-library

以下範例說明權杖代理人如何產生範圍縮減的權杖:

// Imports the Google Auth libraries.
const {GoogleAuth, DownscopedClient} = require('google-auth-library');
/**
 * Simulates token broker generating downscoped tokens for specified bucket.
 *
 * @param bucketName The name of the Cloud Storage bucket.
 * @param objectPrefix The prefix string of the object name. This is used
 *        to ensure access is restricted to only objects starting with this
 *        prefix string.
 */
async function getTokenFromBroker(bucketName, objectPrefix) {
  const googleAuth = new GoogleAuth({
    scopes: 'https://www.googleapis.com/auth/cloud-platform',
  });

  // Define the Credential Access Boundary object.
  const cab = {
    // Define the access boundary.
    accessBoundary: {
      // Define the single access boundary rule.
      accessBoundaryRules: [
        {
          availableResource: `//storage.googleapis.com/projects/_/buckets/${bucketName}`,
          // Downscoped credentials will have readonly access to the resource.
          availablePermissions: ['inRole:roles/storage.objectViewer'],
          // Only objects starting with the specified prefix string in the object name
          // will be allowed read access.
          availabilityCondition: {
            expression:
              "resource.name.startsWith('projects/_/buckets/" +
              `${bucketName}/objects/${objectPrefix}')`,
          },
        },
      ],
    },
  };

  // Obtain an authenticated client via ADC.
  const client = await googleAuth.getClient();

  // Use the client to create a DownscopedClient.
  const cabClient = new DownscopedClient(client, cab);

  // Refresh the tokens.
  const refreshedAccessToken = await cabClient.getAccessToken();

  // This will need to be passed to the token consumer.
  return refreshedAccessToken;
}

以下範例說明權杖消費者如何提供重新整理處理常式,自動取得及重新整理範圍縮減的權杖:

// Imports the Google Auth and Google Cloud libraries.
const {OAuth2Client} = require('google-auth-library');
const {Storage} = require('@google-cloud/storage');
/**
 * Simulates token consumer generating calling GCS APIs using generated
 * downscoped tokens for specified bucket.
 *
 * @param bucketName The name of the Cloud Storage bucket.
 * @param objectName The name of the object in the Cloud Storage bucket
 *        to read.
 */
async function tokenConsumer(bucketName, objectName) {
  // Create the OAuth credentials (the consumer).
  const oauth2Client = new OAuth2Client();
  // We are defining a refresh handler instead of a one-time access
  // token/expiry pair.
  // This will allow the consumer to obtain new downscoped tokens on
  // demand every time a token is expired, without any additional code
  // changes.
  oauth2Client.refreshHandler = async () => {
    // The common pattern of usage is to have a token broker pass the
    // downscoped short-lived access tokens to a token consumer via some
    // secure authenticated channel. For illustration purposes, we are
    // generating the downscoped token locally. We want to test the ability
    // to limit access to objects with a certain prefix string in the
    // resource bucket. objectName.substring(0, 3) is the prefix here. This
    // field is not required if access to all bucket resources are allowed.
    // If access to limited resources in the bucket is needed, this mechanism
    // can be used.
    const refreshedAccessToken = await getTokenFromBroker(
      bucketName,
      objectName.substring(0, 3)
    );
    return {
      access_token: refreshedAccessToken.token,
      expiry_date: refreshedAccessToken.expirationTime,
    };
  };

  const storageOptions = {
    projectId: process.env.GOOGLE_CLOUD_PROJECT,
    authClient: oauth2Client,
  };

  const storage = new Storage(storageOptions);
  const downloadFile = await storage
    .bucket(bucketName)
    .file(objectName)
    .download();
  console.log(downloadFile.toString('utf8'));
}

Python

如果是 Python,您可以使用 google-auth 套件 2.0.0 以上版本,自動交換及重新整理權杖。

如要檢查套件版本,請在安裝套件的環境中執行下列指令:

pip show google-auth

以下範例說明權杖代理人如何產生範圍縮減的權杖:

import google.auth

from google.auth import downscoped
from google.auth.transport import requests

def get_token_from_broker(bucket_name, object_prefix):
    """Simulates token broker generating downscoped tokens for specified bucket.

    Args:
        bucket_name (str): The name of the Cloud Storage bucket.
        object_prefix (str): The prefix string of the object name. This is used
            to ensure access is restricted to only objects starting with this
            prefix string.

    Returns:
        Tuple[str, datetime.datetime]: The downscoped access token and its expiry date.
    """
    # Initialize the Credential Access Boundary rules.
    available_resource = f"//storage.googleapis.com/projects/_/buckets/{bucket_name}"
    # Downscoped credentials will have readonly access to the resource.
    available_permissions = ["inRole:roles/storage.objectViewer"]
    # Only objects starting with the specified prefix string in the object name
    # will be allowed read access.
    availability_expression = (
        "resource.name.startsWith('projects/_/buckets/{}/objects/{}')".format(
            bucket_name, object_prefix
        )
    )
    availability_condition = downscoped.AvailabilityCondition(availability_expression)
    # Define the single access boundary rule using the above properties.
    rule = downscoped.AccessBoundaryRule(
        available_resource=available_resource,
        available_permissions=available_permissions,
        availability_condition=availability_condition,
    )
    # Define the Credential Access Boundary with all the relevant rules.
    credential_access_boundary = downscoped.CredentialAccessBoundary(rules=[rule])

    # Retrieve the source credentials via ADC.
    source_credentials, _ = google.auth.default()
    if source_credentials.requires_scopes:
        source_credentials = source_credentials.with_scopes(
            ["https://www.googleapis.com/auth/cloud-platform"]
        )

    # Create the downscoped credentials.
    downscoped_credentials = downscoped.Credentials(
        source_credentials=source_credentials,
        credential_access_boundary=credential_access_boundary,
    )

    # Refresh the tokens.
    downscoped_credentials.refresh(requests.Request())

    # These values will need to be passed to the token consumer.
    access_token = downscoped_credentials.token
    expiry = downscoped_credentials.expiry
    return (access_token, expiry)

以下範例說明權杖消費者如何提供重新整理處理常式,自動取得及重新整理範圍縮減的權杖:

from google.cloud import storage
from google.oauth2 import credentials

def token_consumer(bucket_name, object_name):
    """Tests token consumer readonly access to the specified object.

    Args:
        bucket_name (str): The name of the Cloud Storage bucket.
        object_name (str): The name of the object in the Cloud Storage bucket
            to read.
    """

    # Create the OAuth credentials from the downscoped token and pass a
    # refresh handler to handle token expiration. We are passing a
    # refresh_handler instead of a one-time access token/expiry pair.
    # This will allow the consumer to obtain new downscoped tokens on
    # demand every time a token is expired, without any additional code
    # changes.
    def refresh_handler(request, scopes=None):
        # The common pattern of usage is to have a token broker pass the
        # downscoped short-lived access tokens to a token consumer via some
        # secure authenticated channel.
        # For illustration purposes, we are generating the downscoped token
        # locally.
        # We want to test the ability to limit access to objects with a certain
        # prefix string in the resource bucket. object_name[0:3] is the prefix
        # here. This field is not required if access to all bucket resources are
        # allowed. If access to limited resources in the bucket is needed, this
        # mechanism can be used.
        return get_token_from_broker(bucket_name, object_prefix=object_name[0:3])

    creds = credentials.Credentials(
        None,
        scopes=["https://www.googleapis.com/auth/cloud-platform"],
        refresh_handler=refresh_handler,
    )

    # Initialize a Cloud Storage client with the oauth2 credentials.
    storage_client = storage.Client(credentials=creds)
    # The token broker has readonly access to the specified bucket object.
    bucket = storage_client.bucket(bucket_name)
    blob = bucket.blob(object_name)
    print(blob.download_as_bytes().decode("utf-8"))

手動交換及更新存取權杖

權杖代理商可以使用 Security Token Service API,將存取權杖換成範圍縮減的存取權杖。然後將範圍縮減的權杖提供給權杖消費者。

如要交換存取權杖,請使用下列 HTTP 方法和網址:

POST https://sts.googleapis.com/v1/token

將要求中的 Content-Type 標頭設為 application/x-www-form-urlencoded。在要求主體中加入下列欄位:

欄位
grant_type

string

請使用 urn:ietf:params:oauth:grant-type:token-exchange 值。

options

string

採用 JSON 格式的憑證存取邊界,並以百分比編碼編碼。

requested_token_type

string

請使用 urn:ietf:params:oauth:token-type:access_token 值。

subject_token

string

要交換的 OAuth 2.0 存取權杖。

subject_token_type

string

請使用 urn:ietf:params:oauth:token-type:access_token 值。

回覆為 JSON 物件,內含下列欄位:

欄位
access_token

string

遵守憑證存取權範圍的範圍縮減 OAuth 2.0 存取權杖。

expires_in

number

縮減範圍的權杖失效前的時間長度 (以秒為單位)。

只有在原始存取權杖代表服務帳戶時,才會包含這個欄位。如果未納入這個欄位,縮減範圍的權杖到期時間會與原始存取權杖相同。

issued_token_type

string

包含 urn:ietf:params:oauth:token-type:access_token 值。

token_type

string

包含值 Bearer

舉例來說,如果 JSON 格式的憑證存取權界線儲存在 ./access-boundary.json 檔案中,您可以使用下列 curl 指令交換存取權權杖。將 original-token 替換為原始存取權杖:

curl -H "Content-Type:application/x-www-form-urlencoded" \
    -X POST \
    https://sts.googleapis.com/v1/token \
    -d "grant_type=urn:ietf:params:oauth:grant-type:token-exchange&subject_token_type=urn:ietf:params:oauth:token-type:access_token&requested_token_type=urn:ietf:params:oauth:token-type:access_token&subject_token=original-token" \
    --data-urlencode "options=$(cat ./access-boundary.json)"

回應類似下列範例:

{
  "access_token": "ya29.dr.AbCDeFg-123456...",
  "issued_token_type": "urn:ietf:params:oauth:token-type:access_token",
  "token_type": "Bearer",
  "expires_in": 3600
}

當權杖消費者要求縮減範圍的權杖時,權杖代理程式會回覆縮減範圍的權杖,以及權杖到期前的秒數。如果權杖已過期,伺服器會拒絕要求。如要重新整理範圍縮減的權杖,消費者可以在現有權杖到期前,向代理商要求範圍縮減的權杖。

後續步驟