프로그래매틱 인증

이 페이지에서는 사용자 계정 또는 서비스 계정에서IAP(Identity-Aware Proxy) 보안 리소스를 인증하는 방법에 대해 설명합니다.

  • 사용자 계정은 개별 사용자에 속합니다. 애플리케이션에서 사용자를 대신하여 IAP 보안 리소스에 액세스해야 하는 경우 사용자 계정을 인증합니다. 사용자 계정 사용자 인증 정보를 읽어보세요.
  • 서비스 계정은 개별 사용자 대신 애플리케이션에 속합니다. 애플리케이션이 IAP 보안 리소스에 액세스하도록 허용하려는 경우 서비스 계정을 인증합니다. 서비스 계정 이해 방법을 알아보세요.

시작하기 전에

시작하기 전에 다음이 필요합니다.

  • 개발자 계정, 서비스 계정 또는 모바일 앱 사용자 인증 정보를 사용하여 프로그래매틱 방식으로 연결하려는 IAP 보안 애플리케이션입니다.

사용자 계정 인증

데스크톱 또는 모바일 앱에서 사용자가 앱에 액세스하여 프로그램이 IAP 보안 리소스와 상호작용하도록 할 수 있습니다.

모바일 앱에서 인증

  1. IAP 보안 리소스와 동일한 프로젝트에서 모바일 앱에 대한 OAuth 2.0 클라이언트 ID를 만듭니다.
    1. 사용자 인증 정보 페이지로 이동합니다.
      사용자 인증 정보 페이지로 이동
    2. IAP 보안 리소스가 포함된 프로젝트를 선택합니다.
    3. 사용자 인증 정보 만들기를 클릭하고 OAuth 클라이언트 ID를 선택합니다.
    4. 사용자 인증 정보를 만들려는 애플리케이션 유형을 선택합니다.
    5. 필요에 따라 이름제한사항을 추가한 후 만들기를 클릭합니다.
  2. 표시되는 OAuth 클라이언트 창에서 연결하려는 IAP 보안 리소스의 클라이언트 ID를 확인합니다.
  3. Cloud IAP 보안 클라이언트 ID에 대한 ID 토큰을 가져옵니다.
  4. IAP 보안 리소스에 인증된 요청을 하려면 Authorization: Bearer 헤더에 ID 토큰을 포함합니다.

데스크톱 앱에서 인증

이 섹션에서는 데스크톱 명령줄에서 사용자 계정을 인증하는 방법을 설명합니다.

클라이언트 ID 설정

개발자가 명령줄에서 애플리케이션에 액세스하도록 허용하려면 먼저 기타 유형의 OAuth 클라이언트 ID 사용자 인증 정보를 만들어야 합니다.

  1. 사용자 인증 정보 페이지로 이동합니다.
    사용자 인증 정보 페이지로 이동
  2. IAP 보안 리소스가 포함된 프로젝트를 선택합니다.
  3. 사용자 인증 정보 만들기를 클릭하고 OAuth 클라이언트 ID를 선택합니다.
  4. 애플리케이션 유형 아래에서 기타를 선택하고 이름을 추가한 후 만들기를 클릭합니다.
  5. 표시된 OAuth 클라이언트 창에서 클라이언트 ID클라이언트 보안 비밀을 기록해둡니다. 사용자 인증 정보를 관리하거나 개발자들과 공유하기 위해 스크립트에서 이를 사용해야 합니다.
  6. 사용자 인증 정보 창에서 새로운 기타 사용자 인증 정보가 애플리케이션에 액세스하기 위해 사용되는 기본 클라이언트 ID와 함께 표시됩니다.

애플리케이션에 로그인

IAP 보안 앱에 액세스하려는 각 개발자는 먼저 로그인해야 합니다. Cloud SDK를 사용하는 것과 같이 프로세스를 스크립트로 패키지화할 수 있습니다. 다음은 curl을 사용하여 로그인하고 애플리케이션에 액세스하기 위해 사용할 수 있는 토큰을 만드는 예시입니다.

  1. Google Cloud 리소스에 액세스할 수 있는 계정에 로그인합니다.
  2. 다음 URI로 이동합니다. 여기서 OTHER_CLIENT_ID는 위에서 만든 기타 클라이언트 ID입니다.

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

  3. 표시된 창에서 위에서 만든 기타 클라이언트 ID 및 보안 비밀번호와 함께, 아래의 AUTH_CODE를 대체할 승인 코드를 기록해둡니다.

    curl --verbose \
          --data client_id=OTHER_CLIENT_ID \
          --data client_secret=OTHER_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 토큰은 약 1시간 동안 유효하며 이 기간 동안 특정 앱에 여러 요청을 할 수 있습니다. 다음은 curl을 사용하여 토큰을 사용하고 애플리케이션에 액세스하는 예시입니다.

  1. 아래 코드를 사용합니다. 여기서 REFRESH_TOKEN은 로그인 과정의 토큰이고, IAP_CLIENT_ID는 애플리케이션에 액세스하기 위해 사용되는 기본 클라이언트 ID이고, OTHER_CLIENT_IDOTHER_CLIENT_SECRET은 위에서 클라이언트 ID를 설정할 때 만든 클라이언트 ID와 보안 비밀입니다.

    curl --verbose \
          --data client_id=OTHER_CLIENT_ID \
          --data client_secret=OTHER_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

서비스 계정에서 인증

OIDC(OpenID Connect) 토큰을 사용하여 IAP 보안 리소스에 대한 서비스 계정을 인증합니다.

  1. IAP 보안 프로젝트의 액세스 목록에 서비스 계정을 추가합니다.
  2. JWT 기반 액세스 토큰을 생성합니다. 이 토큰은 클라이언트 ID가 필요한 target_audience 추가 클레임을 사용합니다. 클라이언트 ID를 찾으려면 아래 단계를 따르세요.

    1. IAP 페이지로 이동합니다.
    2. 액세스하려는 리소스를 찾은 후 더보기 > OAuth 클라이언트 수정을 클릭합니다.
      더보기 메뉴에서 OAuth 클라이언트 편집

    3. 표시된 사용자 인증 정보 페이지에서 클라이언트 ID를 기록해둡니다.

  3. IAP 보안 클라이언트 ID에 대해 OIDC 토큰을 요청합니다.

  4. IAP 보안 리소스에 인증된 요청을 하려면 Authorization: Bearer 헤더에 OIDC 토큰을 포함합니다.

다음 샘플 코드는 기본 서비스 계정을 IAP 보안 리소스로 인증합니다.

C#


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

namespace GoogleCloudSamples
{
    class IAPClient
    {
        /// <summary>
        /// Authenticates using the client id and credentials, then fetches
        /// the uri.
        /// </summary>
        /// <param name="iapClientId">The client id observed on
        /// https://console.cloud.google.com/apis/credentials. </param>
        /// <param name="credentialsFilePath">Path to the credentials .json file
        /// downloaded from https://console.cloud.google.com/apis/credentials.
        /// </param>
        /// <param name="uri">HTTP uri to fetch.</param>
        /// <returns>The http response body as a string.</returns>
        public async Task<string> InvokeRequestAsync(string iapClientId, string credentialsFilePath, string uri)
        {
            // Read credentials from the credentials .json file.
            ServiceAccountCredential saCredential;
            using (var fs = new FileStream(credentialsFilePath, FileMode.Open, FileAccess.Read))
            {
                saCredential = ServiceAccountCredential.FromServiceAccountData(fs);
            }

            // Request an OIDC token for the Cloud IAP-secured client ID.
            OidcToken oidcToken = await saCredential.GetOidcTokenAsync(OidcTokenOptions.FromTargetAudience(iapClientId)).ConfigureAwait(false);
            // Always request the string token from the OIDC token, the OIDC token will refresh the string token if it expires.
            string token = await oidcToken.GetAccessTokenAsync().ConfigureAwait(false);

            // Include the OIDC token in an Authorization: Bearer header to
            // IAP-secured resource
            using (var httpClient = new HttpClient())
            {
                httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
                string response = await httpClient.GetStringAsync(uri).ConfigureAwait(false);
                return response;
            }
        }
    }
}

자바


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 java.time.Clock;
import java.util.Collections;

public class BuildIapRequest {
  private static final String IAM_SCOPE = "https://www.googleapis.com/auth/iam";
  private static final String OAUTH_TOKEN_URI = "https://www.googleapis.com/oauth2/v4/token";
  private static final String JWT_BEARER_TOKEN_GRANT_TYPE =
      "urn:ietf:params:oauth:grant-type:jwt-bearer";
  private static final long EXPIRATION_TIME_IN_SECONDS = 3600L;

  private static final HttpTransport httpTransport = new NetHttpTransport();

  private static Clock clock = Clock.systemUTC();

  private BuildIapRequest() {}

  private static IdTokenProvider getIdTokenProvider() throws Exception {
    GoogleCredentials credentials =
        GoogleCredentials.getApplicationDefault().createScoped(Collections.singleton(IAM_SCOPE));
    // service account credentials are required to sign the jwt token
    if (credentials == null || !(credentials instanceof IdTokenProvider)) {
      throw new Exception("Google credentials : credentials that can provide id tokens expected");
    }
    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 Exception exception creating signed JWT
   */
  public static HttpRequest buildIapRequest(HttpRequest request, String iapClientId)
      throws Exception {

    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.
    google_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(
            google_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

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
}

다음 단계