程序化身份验证

本页面介绍如何从用户帐号或服务帐号向受 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 保护的应用的开发者都需要先登录。您可以将进程打包到脚本中,例如使用 gcloud CLI。以下示例演示了如何使用 curl 登录并生成一个可用于访问应用的令牌:

  1. 登录有权访问 Google Cloud 资源的帐号。
  2. 启动可回显传入请求的本地服务器。$ nc -k -l 4444注意:该命令使用 [NetCat](http://netcat.sourceforge.net/) 实用程序。您可以使用自己选择的实用程序。
  3. 转到以下 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=http://localhost:4444

  4. 在本地服务器输出中,查找请求参数。您应该会看到如下内容: GET /?code=\$CODE&scope=email%20openid%20https://www.googleapis.com/auth/userinfo.email&hd=google.com&prompt=consent HTTP/1.1 复制 CODE 以替换下面的 AUTH_CODE 以及您在上述步骤中创建的桌面应用客户端 ID 和密钥:

    curl --verbose \
          --data client_id=DESKTOP_CLIENT_ID \
          --data client_secret=DESKTOP_CLIENT_SECRET \
          --data code=AUTH_CODE \
          --data redirect_uri=http://localhost:4444 \
          --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.
 */
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
    $response = $client->get($url);
    print('Printing out response body:');
    print($response->getBody());
}

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 令牌

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

Bash

  #!/usr/bin/env bash
  set -euo pipefail

  get_token() {
    # Get the bearer token in exchange for the service account credentials.
    local service_account_key_file_path="${1}"
    local iap_client_id="${2}"

    local iam_scope="https://www.googleapis.com/auth/iam"
    local oauth_token_uri="https://www.googleapis.com/oauth2/v4/token"

    local private_key_id="$(cat "${service_account_key_file_path}" | jq -r '.private_key_id')"
    local client_email="$(cat "${service_account_key_file_path}" | jq -r '.client_email')"
    local private_key="$(cat "${service_account_key_file_path}" | jq -r '.private_key')"
    local issued_at="$(date +%s)"
    local expires_at="$((issued_at + 3600))"
    local header="{'alg':'RS256','typ':'JWT','kid':'${private_key_id}'}"
    local header_base64="$(echo "${header}" | base64)"
    local payload="{'iss':'${client_email}','aud':'${oauth_token_uri}','exp':${expires_at},'iat':${issued_at},'sub':'${client_email}','target_audience':'${iap_client_id}'}"
    local payload_base64="$(echo "${payload}" | base64)"
    local signature_base64="$(printf %s "${header_base64}.${payload_base64}" | openssl dgst -binary -sha256 -sign <(printf '%s\n' "${private_key}")  | base64)"
    local assertion="${header_base64}.${payload_base64}.${signature_base64}"
    local token_payload="$(curl -s \
      --data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer" \
      --data-urlencode "assertion=${assertion}" \
      https://www.googleapis.com/oauth2/v4/token)"
    local bearer_id_token="$(echo "${token_payload}" | jq -r '.id_token')"
    echo "${bearer_id_token}"
  }

  main(){
    # TODO: Replace the following variables:
    SERVICE_ACCOUNT_KEY="service_account_key_file_path"
    IAP_CLIENT_ID="iap_client_id"
    URL="application_url"

    # Obtain the Bearer ID token.
    ID_TOKEN=$(get_token "${SERVICE_ACCOUNT_KEY}" "${IAP_CLIENT_ID}")
    # Access the application with the Bearer ID token.
    curl --header "Authorization: Bearer ${ID_TOKEN}" "${URL}"
  }

  main "$@"

在所有其他情况下获取 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 标头。

后续步骤