如何使用 REST API 从服务帐户凭据创建访问令牌?

Posted

技术标签:

【中文标题】如何使用 REST API 从服务帐户凭据创建访问令牌?【英文标题】:How do I create an Access Token from Service Account Credentials using REST API? 【发布时间】:2019-05-22 06:21:30 【问题描述】:

我在 Google Cloud Platform 中创建了一个服务帐户并下载了 JSON 格式的私钥。我正在尝试通过 REST API 创建一个计算资源。出于身份验证的目的,我需要一个 AccessToken,它需要设置为创建计算资源 REST API 的标头。是否有 REST API 从私钥获取访问令牌(不使用 SDK 或 Google 客户端)?

【问题讨论】:

【参考方案1】:

有一种更简单的方法可以使用 Google 库从服务帐户生成令牌

from google.auth.transport import requests
from google.oauth2 import service_account

CREDENTIAL_SCOPES = ["https://www.googleapis.com/auth/cloud-platform"]
CREDENTIALS_KEY_PATH = '/PATH/TO/SERVICE_ACCOUNT.json'

def get_service_account_token():
  credentials = service_account.Credentials.from_service_account_file(
          CREDENTIALS_KEY_PATH, scopes=CREDENTIAL_SCOPES)
  credentials.refresh(requests.Request())
  return credentials.token

或者如果您想使用默认身份验证

import google
from google.auth.transport import requests

CREDENTIAL_SCOPES = ["https://www.googleapis.com/auth/cloud-platform"] 

def get_default_token():
  credentials, project_id = google.auth.default(scopes=CREDENTIAL_SCOPES)
  credentials.refresh(requests.Request())
  return credentials.token

credentials对象创建时,token为空,但刷新credentials后,包含了可以作为API请求头的访问token

【讨论】:

@Parzival - 这个问题需要一个没有 SDK 或 Google 客户端的解决方案。根据提出的问题,这个答案是错误的。 @JohnHanley 啊,我明白了。感谢您指出。删除了我的评论【参考方案2】:

以下示例向您展示了在不使用 Python 中的 SDK 的情况下调用 Google Cloud API 的几个重要步骤。类似的代码几乎适用于任何语言(c#、java、php、nodejs)。

使用您的服务帐户 Json 文件的文件名、您的 Google 区域和您的项目 ID 更改源代码。

此示例将列出指定项目的一个区域中的实例。通过这个示例,您将了解调用 API 以创建 GCE 实例的框架。

此代码将向您展示如何:

    如何从 Json 文件加载服务帐户凭据。 如何提取用于签署请求的私钥。 如何为 Google Oauth 2.0 创建 JWT(Json Web 令牌)。 如何设置 Google 范围(权限)。 如何签署 JWT 以创建 Signed-JWT (JWS)。 如何将 Signed-JWT 交换为 Google OAuth 2.0 访问令牌。 如何设置过期时间。此程序默认为 3600 秒(1 小时)。 如何调用 Google API 并设置授权标头。 如何处理返回的Json结果并显示每个实例的名称。

Python 3.x 中的示例程序:

'''
This program lists lists the Google Compute Engine Instances in one zone
'''
# Author: John Hanley
# https://www.jhanley.com

import time
import json
import jwt
import requests
import httplib2

# Project ID for this request.
project = 'development-123456'

# The name of the zone for this request.
zone = 'us-west1-a'

# Service Account Credentials, Json format
json_filename = 'service-account.json'

# Permissions to request for Access Token
scopes = "https://www.googleapis.com/auth/cloud-platform"

# Set how long this token will be valid in seconds
expires_in = 3600   # Expires in 1 hour

def load_json_credentials(filename):
    ''' Load the Google Service Account Credentials from Json file '''

    with open(filename, 'r') as f:
        data = f.read()

    return json.loads(data)

def load_private_key(json_cred):
    ''' Return the private key from the json credentials '''

    return json_cred['private_key']

def create_signed_jwt(pkey, pkey_id, email, scope):
    ''' Create a Signed JWT from a service account Json credentials file
    This Signed JWT will later be exchanged for an Access Token '''

    # Google Endpoint for creating OAuth 2.0 Access Tokens from Signed-JWT
    auth_url = "https://www.googleapis.com/oauth2/v4/token"

    issued = int(time.time())
    expires = issued + expires_in   # expires_in is in seconds

    # Note: this token expires and cannot be refreshed. The token must be recreated

    # JWT Headers
    additional_headers = 
            'kid': pkey_id,
            "alg": "RS256",
            "typ": "JWT"    # Google uses SHA256withRSA
    

    # JWT Payload
    payload = 
        "iss": email,       # Issuer claim
        "sub": email,       # Issuer claim
        "aud": auth_url,    # Audience claim
        "iat": issued,      # Issued At claim
        "exp": expires,     # Expire time
        "scope": scope      # Permissions
    

    # Encode the headers and payload and sign creating a Signed JWT (JWS)
    sig = jwt.encode(payload, pkey, algorithm="RS256", headers=additional_headers)

    return sig

def exchangeJwtForAccessToken(signed_jwt):
    '''
    This function takes a Signed JWT and exchanges it for a Google OAuth Access Token
    '''

    auth_url = "https://www.googleapis.com/oauth2/v4/token"

    params = 
        "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
        "assertion": signed_jwt
    

    r = requests.post(auth_url, data=params)

    if r.ok:
        return(r.json()['access_token'], '')

    return None, r.text

def gce_list_instances(accessToken):
    '''
    This functions lists the Google Compute Engine Instances in one zone
    '''

    # Endpoint that we will call
    url = "https://www.googleapis.com/compute/v1/projects/" + project + "/zones/" + zone + "/instances"

    # One of the headers is "Authorization: Bearer $TOKEN"
    headers = 
        "Host": "www.googleapis.com",
        "Authorization": "Bearer " + accessToken,
        "Content-Type": "application/json"
    

    h = httplib2.Http()

    resp, content = h.request(uri=url, method="GET", headers=headers)

    status = int(resp.status)

    if status < 200 or status >= 300:
        print('Error: HTTP Request failed')
        return

    j = json.loads(content.decode('utf-8').replace('\n', ''))

    print('Compute instances in zone', zone)
    print('------------------------------------------------------------')
    for item in j['items']:
        print(item['name'])

if __name__ == '__main__':
    cred = load_json_credentials(json_filename)

    private_key = load_private_key(cred)

    s_jwt = create_signed_jwt(
            private_key,
            cred['private_key_id'],
            cred['client_email'],
            scopes)

    token, err = exchangeJwtForAccessToken(s_jwt)

    if token is None:
        print('Error:', err)
        exit(1)

    gce_list_instances(token)

有关更多信息,请访问我的博客。我写这样的文章并发布源代码以帮助其他人了解如何为云编写软件。

www.jhanley.com

【讨论】:

如果令牌需要多个作用域怎么办? scopes 变量是复数形式,但它只接受一个字符串 @Parzival - 在每个范围之间使用空格字符。范围 A 和范围 B 的示例“A B”。 您无需将已签名的 jwt 交换为访问令牌。可以直接在授权承载头中使用developers.google.com/identity/protocols/oauth2/… @digenishjkl - 对于某些 Google API,您是正确的,但对于大多数 Google Cloud API 来说是错误的。此外,Google 的长期目标是为几乎所有 API 使用 OAuth 和 OIDC。 很棒的脚本,谢谢。它对我有用,唯一需要补充的是我必须安装一些我没有的依赖项。如果它对任何人有帮助,您可以拥有一个 requirements.txt 文件,其中包含以下内容: PyJWT==2.3.0 requests==2.27.1 httplib2==0.20.4 cryptography= =36.0.1 然后:pip3 install -r requirements.txt【参考方案3】:

使用 JAVA 的相同解决方案

import com.google.auth.oauth2.GoogleCredentials;
import java.io.FileInputStream;
import java.io.IOException;

public class GoogleHelper 
    public static String getAccessToken() throws IOException 
        return GoogleCredentials
                .fromStream(new FileInputStream("/PATH/TO/SERVICE_ACCOUNT.json"))
                .createScoped("https://www.googleapis.com/auth/cloud-platform")
                .refreshAccessToken()
                .getTokenValue();
    

【讨论】:

太棒了,我找了 2 天的代码!你能告诉我你是怎么找到这个 API 的吗?我觉得我不擅长谷歌搜索。 @kimchoky。实际上,我只是从另一种语言示例转换代码) 不错的 sn-p,但问题需要一个没有 SDK 或 Google 客户端库的解决方案,并且您在第一行导入它

以上是关于如何使用 REST API 从服务帐户凭据创建访问令牌?的主要内容,如果未能解决你的问题,请参考以下文章

无法访问通过 Google 电子表格 API 通过服务帐户创建的工作表

使用服务帐户通过 PHP API 访问 BigQuery 时出现“无效凭据”

您如何保护 API 密钥和第 3 方站点凭据 (LAMP)?

无法使用服务帐户访问 Google REST Api

PayPal:无法创建沙盒帐户和 REST API 帐户

403 使用有效的用户凭据访问 Leycloak REST API 时被禁止