程序化身份验证

本页面介绍如何从用户帐号或服务帐号向受 Identity-Aware Proxy (IAP) 保护的资源进行身份验证。

  • 用户帐号属于个别用户。当您的应用需要代表用户访问受 IAP 保护的资源,则对用户帐号进行身份验证。请参阅用户帐号凭据
  • 服务帐号属于应用而非个别用户。 如果要允许应用访问受 IAP 保护的资源,则对服务帐号进行身份验证。请参阅了解服务帐号

准备工作

在开始之前,您需要做好以下准备:

  • 要使用开发者帐号、服务帐号或移动应用凭据以编程方式连接并且受 IAP 保护的应用。

对用户帐号进行身份验证

您可以允许用户从桌面应用或移动应用访问您的应用,从而支持程序与受 IAP 保护的资源进行交互。

从移动应用进行身份验证

  1. 在受 IAP 保护的资源所在的项目中,为您的移动应用创建一个 OAuth 2.0 客户端 ID:
    1. 转到凭据页面。
      转到“凭据”页面
    2. 选择受 IAP 保护的资源所属的项目。
    3. 点击创建凭据,然后选择 OAuth 客户端 ID
    4. 选择要为其创建凭据的应用类型
    5. 添加名称限制(如果适用),然后点击创建
  2. 在随即显示的 OAuth 客户端窗口上,记下要连接的受 IAP 保护的资源的客户端 ID
  3. 为受 IAP 保护的客户端 ID 获取一个 ID 令牌:
  4. Authorization: Bearer 标头中包含 ID 令牌以向受 IAP 保护的资源发出经过身份验证的请求。

从桌面应用进行身份验证

本部分介绍如何使用桌面命令行对用户帐号进行身份验证。

设置客户端 ID

要允许开发者使用命令行访问您的应用,您首先需要创建桌面应用类型的 OAuth 客户端 ID 凭据:

  1. 转到凭据页面。
    转到“凭据”页面
  2. 选择受 IAP 保护的资源所属的项目。
  3. 点击创建凭据,然后选择 OAuth 客户端 ID
  4. 应用类型下,选择桌面应用,添加一个名称,然后点击创建
  5. 在显示的 OAuth 客户端窗口中,记下客户端 ID客户端密钥。您将需要在脚本中使用这些信息来管理凭据,或以其他方式分享给开发者。
  6. 凭据窗口将显示新的桌面应用凭据以及用于访问应用的主客户端 ID。

登录应用

每个想要访问受 IAP 保护的应用的开发者都需要先登录。您可以使用 Cloud SDK 等工具将流程打包至脚本中。以下示例演示了如何使用 curl 登录并生成一个可用于访问应用的令牌:

  1. 登录到有权访问 Google Cloud 资源的帐号。
  2. 转到以下 URI,其中 DESKTOP_CLIENT_ID 是您在上面创建的桌面应用客户端 ID:

    https://accounts.google.com/o/oauth2/v2/auth?client_id=DESKTOP_CLIENT_ID&response_type=code&scope=openid%20email&access_type=offline&redirect_uri=urn:ietf:wg:oauth:2.0:oob

  3. 在显示的窗口中,记下授权代码(用于替换下面的 AUTH_CODE)以及您在上面创建的桌面应用客户端 ID 和密钥:

    curl --verbose \
          --data client_id=DESKTOP_CLIENT_ID \
          --data client_secret=DESKTOP_CLIENT_SECRET \
          --data code=AUTH_CODE \
          --data redirect_uri=urn:ietf:wg:oauth:2.0:oob \
          --data grant_type=authorization_code \
          https://oauth2.googleapis.com/token

    此代码会返回一个带有 refresh_token 字段的 JSON 对象,您可以将该字段另存为登录令牌以用于访问应用。

访问应用

要访问应用,您需要将在登录流程中生成的 refresh_token 替换成 ID 令牌。ID 令牌的有效期约为一小时,在此期间,您可以向特定应用发出多个请求。以下示例演示了如何使用 curl 获取令牌并访问应用:

  1. 使用下面的代码,其中,REFRESH_TOKEN 是登录流程中生成的令牌,IAP_CLIENT_ID 是用于访问应用的主客户端 ID,DESKTOP_CLIENT_IDDESKTOP_CLIENT_SECRET 是您在设置客户端 ID 时创建的客户端 ID 和密码(如上所示):

    curl --verbose \
          --data client_id=DESKTOP_CLIENT_ID \
          --data client_secret=DESKTOP_CLIENT_SECRET \
          --data refresh_token=REFRESH_TOKEN \
          --data grant_type=refresh_token \
          --data audience=IAP_CLIENT_ID \
          https://oauth2.googleapis.com/token

    此代码会返回一个带有 id_token 字段的 JSON 对象,您可以使用该字段访问应用。

  2. 如需访问该应用,请使用 id_token,如下所示:

    curl --verbose --header 'Authorization: Bearer ID_TOKEN' URL

从服务帐号进行身份验证

您可以使用 OpenID Connect (OIDC) 令牌向受 IAP 保护的资源对服务帐号进行身份验证。如需找到您的客户端 ID,请按以下步骤操作:

  1. 转到 IAP 页面
  2. 找到要访问的资源,然后点击更多 > 修改 OAuth 客户端
    在“更多”菜单上修改 OAuth 客户端

  3. 在显示的凭据页面上,记下客户端 ID。

您还需要将服务帐号添加到受 IAP 保护的项目的访问列表中。以下代码示例显示了如何获取 OIDC 令牌。无论您选择哪一种方法,您都需要在 Authorization: Bearer 标头中添加令牌,以便向受 IAP 保护的资源发出经过身份验证的请求。

获取默认服务帐号的 OIDC 令牌

如果您希望为 Compute Engine、App Engine 或 Cloud Run 的默认服务帐号获取 OIDC 令牌,则可以使用以下代码示例生成令牌,以访问受 IAP 保护的资源:

C#


using Google.Apis.Auth.OAuth2;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;

public class IAPClient
{
    /// <summary>
    /// Makes a request to a IAP secured application by first obtaining
    /// an OIDC token.
    /// </summary>
    /// <param name="iapClientId">The client ID observed on
    /// https://console.cloud.google.com/apis/credentials. </param>
    /// <param name="uri">HTTP URI to fetch.</param>
    /// <param name="cancellationToken">The token to propagate operation cancel notifications.</param>
    /// <returns>The HTTP response message.</returns>
    public async Task<HttpResponseMessage> InvokeRequestAsync(
        string iapClientId, string uri, CancellationToken cancellationToken = default)
    {
        // Get the OidcToken.
        // You only need to do this once in your application
        // as long as you can keep a reference to the returned OidcToken.
        OidcToken oidcToken = await GetOidcTokenAsync(iapClientId, cancellationToken);

        // Before making an HTTP request, always obtain the string token from the OIDC token,
        // the OIDC token will refresh the string token if it expires.
        string token = await oidcToken.GetAccessTokenAsync(cancellationToken);

        // Include the OIDC token in an Authorization: Bearer header to
        // IAP-secured resource
        // Note: Normally you would use an HttpClientFactory to build the httpClient.
        // For simplicity we are building the HttpClient directly.
        using HttpClient httpClient = new HttpClient();
        httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
        return await httpClient.GetAsync(uri, cancellationToken);
    }

    /// <summary>
    /// Obtains an OIDC token for authentication an IAP request.
    /// </summary>
    /// <param name="iapClientId">The client ID observed on
    /// https://console.cloud.google.com/apis/credentials. </param>
    /// <param name="cancellationToken">The token to propagate operation cancel notifications.</param>
    /// <returns>The HTTP response message.</returns>
    public async Task<OidcToken> GetOidcTokenAsync(string iapClientId, CancellationToken cancellationToken)
    {
        // Obtain the application default credentials.
        GoogleCredential credential = await GoogleCredential.GetApplicationDefaultAsync(cancellationToken);

        // Request an OIDC token for the Cloud IAP-secured client ID.
       return await credential.GetOidcTokenAsync(OidcTokenOptions.FromTargetAudience(iapClientId), cancellationToken);
    }
}

Go

import (
	"context"
	"fmt"
	"io"
	"net/http"

	"google.golang.org/api/idtoken"
)

// makeIAPRequest makes a request to an application protected by Identity-Aware
// Proxy with the given audience.
func makeIAPRequest(w io.Writer, request *http.Request, audience string) error {
	// request, err := http.NewRequest("GET", "http://example.com", nil)
	// audience := "IAP_CLIENT_ID.apps.googleusercontent.com"
	ctx := context.Background()

	// client is a http.Client that automatically adds an "Authorization" header
	// to any requests made.
	client, err := idtoken.NewClient(ctx, audience)
	if err != nil {
		return fmt.Errorf("idtoken.NewClient: %v", err)
	}

	response, err := client.Do(request)
	if err != nil {
		return fmt.Errorf("client.Do: %v", err)
	}
	defer response.Body.Close()
	if _, err := io.Copy(w, response.Body); err != nil {
		return fmt.Errorf("io.Copy: %v", err)
	}

	return nil
}

Java


import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpRequestInitializer;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.auth.http.HttpCredentialsAdapter;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.auth.oauth2.IdTokenCredentials;
import com.google.auth.oauth2.IdTokenProvider;
import com.google.common.base.Preconditions;
import java.io.IOException;
import java.util.Collections;

public class BuildIapRequest {
  private static final String IAM_SCOPE = "https://www.googleapis.com/auth/iam";

  private static final HttpTransport httpTransport = new NetHttpTransport();

  private BuildIapRequest() {}

  private static IdTokenProvider getIdTokenProvider() throws IOException {
    GoogleCredentials credentials =
        GoogleCredentials.getApplicationDefault().createScoped(Collections.singleton(IAM_SCOPE));

    Preconditions.checkNotNull(credentials, "Expected to load credentials");
    Preconditions.checkState(
        credentials instanceof IdTokenProvider,
        String.format(
            "Expected credentials that can provide id tokens, got %s instead",
            credentials.getClass().getName()));

    return (IdTokenProvider) credentials;
  }

  /**
   * Clone request and add an IAP Bearer Authorization header with signed JWT token.
   *
   * @param request Request to add authorization header
   * @param iapClientId OAuth 2.0 client ID for IAP protected resource
   * @return Clone of request with Bearer style authorization header with signed jwt token.
   * @throws IOException exception creating signed JWT
   */
  public static HttpRequest buildIapRequest(HttpRequest request, String iapClientId)
      throws IOException {

    IdTokenProvider idTokenProvider = getIdTokenProvider();
    IdTokenCredentials credentials =
        IdTokenCredentials.newBuilder()
            .setIdTokenProvider(idTokenProvider)
            .setTargetAudience(iapClientId)
            .build();

    HttpRequestInitializer httpRequestInitializer = new HttpCredentialsAdapter(credentials);

    return httpTransport
        .createRequestFactory(httpRequestInitializer)
        .buildRequest(request.getRequestMethod(), request.getUrl(), request.getContent());
  }
}

Node.js

/**
 * TODO(developer): Uncomment these variables before running the sample.
 */
// const url = 'https://some.iap.url';
// const targetAudience = 'IAP_CLIENT_ID.apps.googleusercontent.com';

const {GoogleAuth} = require('google-auth-library');
const auth = new GoogleAuth();

async function request() {
  console.info(`request IAP ${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;
});

PHP

namespace Google\Cloud\Samples\Iap;

# Imports Auth libraries and Guzzle HTTP libraries.
use Google\Auth\ApplicationDefaultCredentials;
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;

/**
 * Make a request to an application protected by Identity-Aware Proxy.
 *
 * @param string $url The Identity-Aware Proxy-protected URL to fetch.
 * @param string $clientId The client ID used by Identity-Aware Proxy.
 *
 * @return The response body.
 */
function make_iap_request($url, $clientId)
{
    // create middleware, using the client ID as the target audience for IAP
    $middleware = ApplicationDefaultCredentials::getIdTokenMiddleware($clientId);
    $stack = HandlerStack::create();
    $stack->push($middleware);

    // create the HTTP client
    $client = new Client([
        'handler' => $stack,
        'auth' => 'google_auth'
    ]);

    // make the request
    return $client->get($url);
}

Python

from google.auth.transport.requests import Request
from google.oauth2 import id_token
import requests

def make_iap_request(url, client_id, method='GET', **kwargs):
    """Makes a request to an application protected by Identity-Aware Proxy.

    Args:
      url: The Identity-Aware Proxy-protected URL to fetch.
      client_id: The client ID used by Identity-Aware Proxy.
      method: The request method to use
              ('GET', 'OPTIONS', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE')
      **kwargs: Any of the parameters defined for the request function:
                https://github.com/requests/requests/blob/master/requests/api.py
                If no timeout is provided, it is set to 90 by default.

    Returns:
      The page body, or raises an exception if the page couldn't be retrieved.
    """
    # Set the default timeout, if missing
    if 'timeout' not in kwargs:
        kwargs['timeout'] = 90

    # Obtain an OpenID Connect (OIDC) token from metadata server or using service
    # account.
    open_id_connect_token = id_token.fetch_id_token(Request(), client_id)

    # Fetch the Identity-Aware Proxy-protected URL, including an
    # Authorization header containing "Bearer " followed by a
    # Google-issued OpenID Connect token for the service account.
    resp = requests.request(
        method, url,
        headers={'Authorization': 'Bearer {}'.format(
            open_id_connect_token)}, **kwargs)
    if resp.status_code == 403:
        raise Exception('Service account does not have permission to '
                        'access the IAP-protected application.')
    elif resp.status_code != 200:
        raise Exception(
            'Bad response from application: {!r} / {!r} / {!r}'.format(
                resp.status_code, resp.headers, resp.text))
    else:
        return resp.text

Ruby

# url = "The Identity-Aware Proxy-protected URL to fetch"
# client_id = "The client ID used by Identity-Aware Proxy"
require "googleauth"
require "faraday"

# The client ID as the target audience for IAP
id_token_creds = Google::Auth::Credentials.default target_audience: client_id

headers = {}
id_token_creds.client.apply! headers

resp = Faraday.get url, nil, headers

if resp.status == 200
  puts "X-Goog-Iap-Jwt-Assertion:"
  puts resp.body
else
  puts "Error requesting IAP"
  puts resp.status
  puts resp.headers
end

从本地服务帐号密钥文件获取 OIDC 令牌

如果您有服务帐号密钥文件,则可以调整上述代码示例以提供服务帐号密钥文件

在所有其他情况下获取 OIDC 令牌

在其他所有情况下,请在访问受 IAP 保护的资源之前,根据另一个服务账号的访问令牌,使用 IAM Credentials API 生成 OIDC 令牌

  1. 将访问令牌中具有角色 service account token creator 的账号添加到目标帐号。这可确保该帐号具备为目标服务帐号创建 OIDC 令牌所需的 IAM 权限
  2. 使用访问令牌对目标服务帐号调用 generateIdToken。请特别注意将 audience 字段设置为您的客户端 ID。

从 Proxy-Authorization 标头进行身份验证

如果您的应用占用了 Authorization 请求标头,您可以改为在 Proxy-Authorization: Bearer 标头中添加 ID 令牌。如果在 Proxy-Authorization 标头中找到了有效的 ID 令牌,则 IAP 会用它授权请求。授权请求后,IAP 会将 Authorization 标头传递给您的应用,而不处理内容。

如果在 Proxy-Authorization 标头中未找到有效的 ID 令牌,则 IAP 会继续处理 Authorization 标头并在将请求传递给应用之前去除 Proxy-Authorization 标头。

后续步骤