개발자, 함수, 최종 사용자 인증

기본적으로 프로젝트 소유자와 편집자만 함수를 생성, 업데이트 또는 삭제할 수 있습니다. 다른 사용자 또는 그룹에 이러한 작업 수행 권한을 부여하려면 ID 및 액세스 관리(IAM)를 사용하여 여러 구성원에 대해 역할을 부여할 수 있습니다.

이와 같은 방법으로 함수를 호출하는 권한을 부여하거나 제한할 수 있습니다. HTTP 함수백그라운드 함수의 경우에는 이 동작이 다릅니다.

자주 활용되는 인증의 사용 사례로는 다음 3가지를 들 수 있습니다.

  • 특정 사용자만이 테스트 중에 함수를 호출할 수 있도록 개발자 액세스를 보호하는 경우

  • 승인된 함수만이 함수를 호출할 수 있도록 함수-함수 액세스를 보호하는 경우

  • 모바일 또는 웹 클라이언트에서 애플리케이션에 대한 최종 사용자 액세스를 보호하는 경우

개발자

개발자는 함수 생성, 업데이트, 삭제와 같은 관리 작업 외에도 함수를 공개하기 전에 비공개로 함수를 테스트할 수 있습니다.

curl 또는 유사한 도구를 사용하는 경우에는 이를 최종 사용자 요청으로 처리하고 요청의 Authorization 헤더에 Google OAuth 토큰을 제공해야 합니다. 예를 들어 다음과 같이 gcloud를 통해 토큰을 가져올 수 있습니다.

curl https://REGION-PROJECT_ID.cloudfunctions.net/FUNCTION_NAME \
  -H "Authorization: bearer $(gcloud auth print-identity-token)"

이 요청이 작동하려면 개발자에게 할당된 역할에 cloudfunctions.functions.invoke 권한이 있어야 합니다. 기본적으로 Cloud Functions 관리자 및 Cloud Functions 개발자 역할에는 이 권한이 있습니다. 전체 역할 목록과 이와 관련된 권한은 Cloud Functions IAM 역할을 참조하세요.

마지막으로, 함수를 개발하고 사용하는 데 필요한 최소한의 권한 집합을 할당하는 것이 좋습니다. 함수에 대한 IAM 정책이 최소한의 사용자 수와 서비스 계정 수로 제한되어 있는지 확인합니다.

함수-함수

여러 함수를 연결하는 서비스를 빌드하는 경우 각 함수가 특정 함수에만 요청할 수 있도록 하는 것이 좋습니다. 예를 들어 login 함수가 있다면 user profiles 함수에는 액세스할 수 있지만 search 함수에는 액세스할 수 없어야 합니다.

먼저 호출 함수의 요청을 수락하도록 수신 함수를 구성해야 합니다.

  1. 수신 함수의 호출 함수 ID에 Cloud Functions 호출자(roles/cloudfunctions.invoker) 역할을 부여합니다. 기본적으로 이 ID는 PROJECT_ID@appspot.gserviceaccount.com입니다.

Console

  1. Google Cloud Console로 이동합니다.

    Google Cloud Console로 이동

  2. 권한을 수정하고자 하는 함수 옆의 체크박스를 클릭합니다.

  3. 오른쪽 상단 모서리의 정보 패널 표시를 클릭하여 함수에 할당된 역할을 표시하는 권한 탭을 엽니다.

  4. 구성원 추가 필드에 호출 함수의 런타임 ID를 입력합니다. 서비스 계정 이메일이어야 합니다.

  5. 역할 선택 드롭다운 메뉴에서 Cloud Functions > Cloud Functions 호출자 역할을 선택합니다.

  6. 저장을 클릭합니다.

gcloud

gcloud functions add-iam-policy-binding 명령어를 사용합니다.

gcloud functions add-iam-policy-binding RECEIVING_FUNCTION \
  --member='serviceAccount:CALLING_FUNCTION_IDENTITY' \
  --role='roles/cloudfunctions.invoker'

여기서 RECEIVING_FUNCTION은 수신 함수이고 CALLING_FUNCTION_IDENTITY는 호출 함수 ID입니다.

호출 함수에서 다음을 수행해야 합니다.

  1. 대상(aud)이 수신 함수의 URL로 설정된 Google이 서명한 OAuth ID 토큰을 만듭니다.

  2. ID 토큰을 함수에 대한 요청의 Authorization: Bearer ID_TOKEN 헤더에 포함합니다.

Node.js

const fetch = require('node-fetch');

// TODO(developer): set these values
const REGION = 'us-central1';
const PROJECT_ID = 'my-project-id';
const RECEIVING_FUNCTION = 'myFunction';

// Constants for setting up metadata server request
// See https://cloud.google.com/functions/docs/securing/function-identity#identity_tokens
const functionURL = `https://${REGION}-${PROJECT_ID}.cloudfunctions.net/${RECEIVING_FUNCTION}`;
const metadataServerURL =
  'http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=';
const tokenUrl = metadataServerURL + functionURL;

exports.callingFunction = async (req, res) => {
  // Fetch the token
  const tokenResponse = await fetch(tokenUrl, {
    headers: {
      'Metadata-Flavor': 'Google',
    },
  });
  const token = await tokenResponse.text();

  // Provide the token in the request to the receiving function
  try {
    const functionResponse = await fetch(functionURL, {
      headers: {Authorization: `bearer ${token}`},
    });
    res.status(200).send(await functionResponse.text());
  } catch (err) {
    console.error(err);
    res.status(500).send('An error occurred! See logs for more details.');
  }
};

Python

import requests

# TODO<developer>: set these values
REGION = 'us-central1'
PROJECT_ID = 'my-project'
RECEIVING_FUNCTION = 'my-function'

# Constants for setting up metadata server request
# See https://cloud.google.com/compute/docs/instances/verifying-instance-identity#request_signature
function_url = f'https://{REGION}-{PROJECT_ID}.cloudfunctions.net/{RECEIVING_FUNCTION}'
metadata_server_url = \
    'http://metadata/computeMetadata/v1/instance/service-accounts/default/identity?audience='
token_full_url = metadata_server_url + function_url
token_headers = {'Metadata-Flavor': 'Google'}

def calling_function(request):
    # Fetch the token
    token_response = requests.get(token_full_url, headers=token_headers)
    jwt = token_response.text

    # Provide the token in the request to the receiving function
    function_headers = {'Authorization': f'bearer {jwt}'}
    function_response = requests.get(function_url, headers=function_headers)

    return function_response.text

Go


import (
	"context"
	"fmt"
	"io"

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

// makeGetRequest makes a request to the provided targetURL with an authenticated client.
func makeGetRequest(w io.Writer, targetURL string) error {
	// functionURL := "https://TARGET_URL"
	ctx := context.Background()

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

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

	return nil
}

자바


import com.google.cloud.functions.HttpFunction;
import com.google.cloud.functions.HttpRequest;
import com.google.cloud.functions.HttpResponse;
import java.io.BufferedWriter;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.time.Duration;

public class BearerTokenHttp implements HttpFunction {

  // TODO<developer> specify values for these environment variables
  private static String REGION = System.getenv("TARGET_REGION");
  private static String PROJECT_ID = System.getenv("GCP_PROJECT");
  private static String RECEIVING_FUNCTION_NAME = "myFunction";

  private static String receivingFunctionUrl = String.format(
      "https://%s-%s.cloudfunctions.net/%s", REGION, PROJECT_ID, RECEIVING_FUNCTION_NAME);
  private static String metadataTokenEndpoint =
      "http://metadata/computeMetadata/v1/instance/service-accounts/default/identity?audience=";

  private static HttpClient client =
      HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build();

  @Override
  public void service(HttpRequest request, HttpResponse response)
      throws IOException, InterruptedException {

    // Set up metadata server request
    // See https://cloud.google.com/compute/docs/instances/verifying-instance-identity#request_signature
    java.net.http.HttpRequest tokenRequest = java.net.http.HttpRequest.newBuilder()
        .uri(URI.create(metadataTokenEndpoint + receivingFunctionUrl))
        .GET()
        .header("Metadata-Flavor", "Google")
        .build();

    // Fetch the bearer token
    java.net.http.HttpResponse<String> tokenReponse =
        client.send(tokenRequest, java.net.http.HttpResponse.BodyHandlers.ofString());
    String token = tokenReponse.body();

    // Pass the token along to receiving function
    java.net.http.HttpRequest functionRequest = java.net.http.HttpRequest.newBuilder()
        .uri(URI.create(receivingFunctionUrl))
        .GET()
        .header("Authorization", "Bearer " + token)
        .build();
    java.net.http.HttpResponse<String> functionResponse =
        client.send(functionRequest, java.net.http.HttpResponse.BodyHandlers.ofString());

    // Write the results to the output:
    BufferedWriter writer = response.getWriter();
    writer.write(functionResponse.body());
  }
}

서비스-함수

컴퓨팅 메타데이터(예: 서버)에 액세스할 수 없는 컴퓨팅 인스턴스에서 함수를 호출하는 경우 적절한 토큰을 수동으로 생성해야 합니다.

  1. target_audience 클레임이 수신 함수의 URL로 설정된 서비스 계정 JWT에 자체 서명합니다.

  2. Google이 서명한 ID 토큰과 자체 서명한 JWT를 교환합니다. 이 토큰에는 위 URL로 설정된 aud 클레임이 있습니다.

  3. ID 토큰을 함수에 대한 요청의 Authorization: Bearer ID_TOKEN 헤더에 포함합니다.

Cloud IAP 문서에는 이 기능을 보여주는 샘플 코드가 있습니다.

최종 사용자

대부분의 애플리케이션은 최종 사용자의 요청을 처리하므로 허용된 최종 사용자로만 액세스를 제한하는 것이 가장 좋습니다. 이를 위해 Google 로그인을 통합하고 사용자에게 Cloud Functions 호출자 IAM 역할(roles/cloudfunctions.invoker)을 부여하거나 Firebase 인증을 구현하고 사용자 인증 정보 유효성을 수동으로 검사할 수 있습니다.

Google 로그인

먼저 프로젝트에서 Google 로그인을 사용 설정해야 합니다.

  1. 보호할 함수와 동일한 프로젝트에서 앱의 OAuth 2.0 클라이언트 ID를 만듭니다.
    1. 사용자 인증 정보 페이지로 이동합니다.

      사용자 인증 정보 페이지로 이동

    2. 보호할 함수가 있는 프로젝트를 선택합니다.
    3. 사용자 인증 정보 만들기를 클릭하고 OAuth 클라이언트 ID를 선택합니다.
      1. 클라이언트 ID를 만들기 전에 OAuth 동의 화면을 구성해야 할 수도 있습니다. 필요한 경우 화면을 구성하여 계속 진행합니다.
    4. 사용자 인증 정보를 만들 애플리케이션 유형을 선택합니다.
    5. 필요에 따라 이름제한사항을 추가한 후 만들기를 클릭합니다.
  2. 보호할 함수를 다시 배포합니다. 이렇게 하면 함수에 올바른 클라이언트 ID가 설정됩니다.

OAuth 클라이언트 ID가 여러 개인 경우(예: Android, iOS, 웹에 각각 하나씩), 각 ID를 추가한 후 함수를 다시 배포해야 함수에 변경사항이 반영됩니다. 마찬가지로 클라이언트 ID를 삭제하는 경우, 함수를 다시 배포해야 해당 클라이언트 ID가 삭제되고 요청이 거부됩니다. 프로젝트 내의 모든 클라이언트 ID가 수락됩니다.

웹 또는 모바일 앱에서 다음을 수행해야 합니다.

  1. OAuth 클라이언트 ID의 ID 토큰을 가져옵니다.
  2. ID 토큰을 함수에 대한 요청의 Authorization: Bearer ID_TOKEN 헤더에 포함합니다.

Cloud Functions는 함수를 시작하기 전에 인증 토큰 유효성을 검사하고 요청을 수락하거나 거부합니다. 요청이 거부되면 해당 요청에 대한 요금이 청구되지 않습니다.

사용자 프로필 정보 가져오기

사용자 프로필 정보에 액세스하기 위해 Authorization 헤더에서 토큰을 가져와 JSON 웹 토큰을 디코딩하고 본문을 추출합니다.

ID 토큰 본문에는 다음 정보가 포함됩니다.

{
 // These six fields are included in all Google ID Tokens.
 "iss": "https://accounts.google.com",
 "sub": "110169484474386276334",
 "azp": "1008719970978-hb24n2dstb40o45d4feuo2ukqmcc6381.apps.googleusercontent.com",
 "aud": "1008719970978-hb24n2dstb40o45d4feuo2ukqmcc6381.apps.googleusercontent.com",
 "iat": "1433978353",
 "exp": "1433981953",

 // These seven fields are only included when the user has granted the "profile"
 // and "email" OAuth scopes to the application.
 "email": "testuser@gmail.com",
 "email_verified": "true",
 "name" : "Test User",
 "picture": "https://lh4.googleusercontent.com/-kYgzyAWpZzJ/ABCDEFGHI/AAAJKLMNOP/tIXL9Ir44LE/s99-c/photo.jpg",
 "given_name": "Test",
 "family_name": "User",
 "locale": "en"
}

토큰은 이미 IAM에 의해 검증되었으므로 토큰의 유효성을 검사할 필요는 없습니다.

문제 해결

수락되어야 하는 사용자 요청이 거부되는 경우 사용자가 roles/cloudfunctions.invoker 역할을 부여받았거나 cloudfunctions.functions.invoke 권한을 가지고 있는지 확인합니다. Cloud Functions IAM 참조에서 자세히 알아보세요.

웹 애플리케이션, 인증, CORS

Google 로그인과 Cloud Functions IAM으로 보호되는 웹 앱을 빌드하려면 교차 출처 리소스 공유(CORS)를 처리해야 할 수 있습니다. CORS 실행 전 요청은 Authorization 헤더 없이 전송되므로 비공개 HTTP 함수에서는 모두 거부됩니다. 실행 전 요청이 실패하므로 주 요청도 실패합니다.

이 문제를 해결하려면 웹 앱 및 함수를 동일한 도메인에서 호스팅하여 CORS 실행 전 요청이 발생하지 않도록 합니다. 그렇지 않으면 함수를 공개로 설정하고 함수 코드에서 CORS와 인증을 처리해야 합니다.

또는 Cloud Endpoints 프록시를 배포하고 CORS를 사용 설정할 수 있습니다. 인증 기능을 사용하려면 Google ID 토큰 유효성 검증을 사용 설정하여 동일한 인증 토큰의 유효성을 확인합니다.

Firebase 인증

이메일/비밀번호, 전화번호, Facebook이나 GitHub와 같은 소셜 공급자 또는 커스텀 인증 메커니즘으로 사용자를 인증하려는 경우에는 Firebase 인증을 사용하면 됩니다.

먼저 프로젝트와 함수에서 Firebase 인증을 설정해야 합니다.

  1. Firebase 콘솔에서 Firebase 인증을 설정합니다.

    Firebase Console로 이동

  2. 적절한 Firebase Admin SDK를 가져와 올바르게 구성합니다.

  3. 미들웨어를 코드에 추가하여 Firebase ID 토큰을 확인합니다.

  4. 함수를 공개적으로 배포합니다.

웹 또는 모바일 앱에서 다음을 수행해야 합니다.

  1. 적절한 Firebase 인증 클라이언트 라이브러리를 사용하여 ID 토큰을 가져옵니다.
  2. ID 토큰을 함수에 대한 요청의 Authorization: Bearer ID_TOKEN 헤더에 포함합니다.

또는 Cloud Endpoints 프록시를 배포하고 Firebase ID 토큰 유효성 검증을 사용 설정하여 동일한 인증 토큰의 유효성을 확인합니다.

사용자 프로필 정보 가져오기

사용자 프로필 정보에 액세스하려면 Firebase Admin SDK를 사용하여 사용자 데이터를 검색하면 됩니다.

API 키

API 키를 사용하면 API를 호출하는 클라이언트 애플리케이션과 GCP 프로젝트를 식별하여 간단한 승인뿐만 아니라 할당량 확인 및 비율 제한 작업도 처리할 수 있습니다.

하지만 다음과 같은 이유로 API 키는 위에 나열된 다른 인증 방법만큼 안전하지 않습니다.

  1. API 키는 오랫동안 유지됩니다. 즉, API 키가 유출되면 무제한으로 사용되거나 최소한 모든 클라이언트가 키를 업데이트하여 키가 변경될 때까지는 계속해서 사용될 수 있습니다.
  2. API 키는 일반적으로 클라이언트 측에 저장되기 때문에 악의적인 사용자가 재사용하기 쉽습니다.

API 키를 할당량 및 비율 제한에 사용하려면 인증 토큰과 함께 사용하는 것이 좋습니다.

API 키 액세스를 구성하려면 Cloud Endpoints 프록시를 배포하고 securityDefinitions에서 API 키 유효성 검사를 사용하도록 설정합니다.

다음 단계

인증하는 함수에 대한 액세스 관리 방법 알아보기