我一直在尝试设置谷歌云功能(带有http触发器的第二代),但似乎无法通过未经授权的401。我使用通过 gcloud cli 生成的身份令牌使其正常工作,但我需要使用服务帐户来避免身份令牌的 1 小时过期时间。
我已经在服务帐户上设置了云函数调用者和云运行调用者权限,并将服务帐户直接连接到云函数。不管我做什么,我都无法让它授权使用服务帐户。
我正在尝试使用 googleauth gem 从 Rails 应用程序进行身份验证。以下是我的服务。
# app/services/cloud_function_service.rb
class CloudFunctionService
# Use the credentials and URL from Rails credentials or environment
def self.api_request(function_type, body_data)
new.api_request(function_type, body_data)
end
def api_request(function_type, body_data)
access_token = fetch_access_token
uri = Rails.application.credentials.CLOUD_FUNCTION_URL
body = {
'function_type': function_type,
'body': body_data
}.to_json
headers = {
'Authorization' => "Bearer #{access_token}",
'Content-Type' => 'application/json'
}
# Make the POST request with headers and body
result = HTTParty.post(uri, body: body, headers: headers)
JSON.parse(result.body)
rescue => e
Rails.logger.error "Failed to call cloud function: #{e}"
nil
end
private
def fetch_access_token
authorizer = Google::Auth::ServiceAccountCredentials.make_creds(
json_key_io: File.open('./google-service-account-keys.json'),
scope: 'https://www.googleapis.com/auth/cloud-platform'
)
response = authorizer.fetch_access_token!
response['access_token']
end
end
在这里回答我自己的问题。实际上有两个问题可能对任何从 Ruby on Rails 应用程序调用云函数的人有所帮助。
首先,我误解了访问令牌与身份令牌的用法,但实际上您确实需要两者。需要访问令牌才能访问 google API 端点(云函数端点不被视为其中的一部分)。需要身份令牌才能访问云功能端点。但是...您使用访问令牌来获取身份令牌。
其次,与 Google Auth 的其他语言库不同,Ruby 库不提供获取身份令牌的方法。它仅提供获取访问令牌的方法。您需要为身份令牌端点构建自己的 api 处理程序。
基本上,流程如下:
这是我最终的有效代码!
class CloudFunctionService
def self.api_request(function_type, body_data)
new.api_request(function_type, body_data)
end
def api_request(function_type, body_data)
access_token = fetch_access_token
service_account_email = _your_gcp_service_account_email_
target_audience = _your_cloud_function_uri_
identity_token = fetch_identity_token(service_account_email, target_audience, access_token)
uri = _your_cloud_function_uri_
body = {
# Your body data
}.to_json
headers = {
'Authorization' => "Bearer #{identity_token}",
'Content-Type' => 'application/json'
}
result = HTTParty.post(uri, body: body, headers: headers)
JSON.parse(result.body)
rescue => e
Rails.logger.error "Failed to call cloud function: #{e}"
nil
end
private
def fetch_access_token
decoded_json_key = Base64.decode64(_your_base64_encoded_json_key_file)
json_key_io = StringIO.new(decoded_json_key)
authorizer = Google::Auth::ServiceAccountCredentials.make_creds(
json_key_io: json_key_io,
scope: 'https://www.googleapis.com/auth/cloud-platform'
)
authorizer.fetch_access_token!['access_token']
end
def fetch_identity_token(service_account_email, target_audience, access_token)
url = "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/#{service_account_email}:generateIdToken"
body = {
audience: target_audience,
includeEmail: true
}.to_json
response = HTTParty.post(
url,
body: body,
headers: {
'Content-Type' => 'application/json',
'Authorization' => "Bearer #{access_token}"
}
)
if response.code == 200
JSON.parse(response.body)['token']
else
raise "Failed to fetch identity token: #{response.body}"
end
end
end