Gmail API服务帐户未授权的_client错误即使在域范围内

发布于 2025-01-26 22:03:43 字数 1946 浏览 4 评论 0原文

我已经大量阅读了有关如何使用服务帐户访问GCP Gmail API并给予其范围范围内的权威的信息,请在此处使用说明:

https://support.google.com/a/answer/162106

这是我的服务帐户:

“在此处输入映像说明”

这是添加到域范围内的范围。您可以看到ID与服务帐户匹配。

我注意到的一件事是我的GCP项目是一个内部项目,我没有发布它或任何内容,但是当我添加范围时,它没有显示服务帐户电子邮件名称,而是项目名称。有什么区别吗?我需要在这里设置任何东西吗?在OAuth同意屏幕中,我看到该项目的名称正在此处定义。我在此屏幕上也添加了所有相同的范围,不确定是否有任何区别。

这是我的代码:

from google.oauth2 import service_account
from googleapiclient import discovery
credentials_file = get_credentials('gmail.json')
scopes = ['https://www.googleapis.com/auth/gmail.readonly', 'https://www.googleapis.com/auth/gmail.labels', 'https://www.googleapis.com/auth/gmail.modify']
credentials = service_account.Credentials.from_service_account_info(credentials_file, scopes=scopes)
delegated_credentials = credentials.with_subject("[email protected]")
GMAIL_SERVICE = discovery.build('gmail', 'v1', credentials=delegated_credentials)
labels = GMAIL_SERVICE.users().labels().list(userId='me').execute()

错误消息:

google.auth.exceptions.refresherror :('unuthorized_client:客户端是 未经授权使用此方法检索访问令牌或客户端 未针对所请求的任何范围授权。',{'error': 'unuthorized_client','error_description':'客户端未经授权到 使用此方法检索访问令牌,或未授权的客户 请求的任何范围。'})

I have read extensively on how to access GCP Gmail API using service account and have given it domain-wide authority, using the instruction here:

https://support.google.com/a/answer/162106

Here is my service account:

enter image description here

Here is the scopes added to the domain-wide authority. You can see that the ID matches the service account.

enter image description here

One thing I notice is that my GCP project is an internal project, I havent' published it or anything, yet when I added the scope, it is not showing the service account email name but the project name. Does it make any difference? Do I need to set anything here? In the OAuth Consent Screen, I see the name of the project is being defined there. I have added all same scope on this screen too, not sure if it make any difference.

enter image description here

Here is my code:

from google.oauth2 import service_account
from googleapiclient import discovery
credentials_file = get_credentials('gmail.json')
scopes = ['https://www.googleapis.com/auth/gmail.readonly', 'https://www.googleapis.com/auth/gmail.labels', 'https://www.googleapis.com/auth/gmail.modify']
credentials = service_account.Credentials.from_service_account_info(credentials_file, scopes=scopes)
delegated_credentials = credentials.with_subject("[email protected]")
GMAIL_SERVICE = discovery.build('gmail', 'v1', credentials=delegated_credentials)
labels = GMAIL_SERVICE.users().labels().list(userId='me').execute()

Error message:

Google.auth.exceptions.RefreshError: ('unauthorized_client: Client is
unauthorized to retrieve access tokens using this method, or client
not authorized for any of the scopes requested.', {'error':
'unauthorized_client', 'error_description': 'Client is unauthorized to
retrieve access tokens using this method, or client not authorized for
any of the scopes requested.'})

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

薆情海 2025-02-02 22:03:43

不确定我是否可以准确地回答原始问题(我认为不是),但是在这里,在我开发的云功能中如何完成事情。以下特定代码段是为此答案编写/采用的,并且没有进行测试:

import os
import google.auth
import google.auth.iam
from google.oauth2 import service_account
from google.auth.exceptions import MutualTLSChannelError
from google.auth.transport import requests
import googleapiclient.discovery

from google.cloud import error_reporting

GMAIL_SERV_ACCOUNT = "A service account which makes the API CALL"
OAUTH_TOKEN_URI = "https://accounts.google.com/o/oauth2/token"
GMAIL_SCOPES_LIST = ["https://mail.google.com/"] # for example
GMAIL_USER = "User's email address, who's email we would like to access. [email protected] - from your question"

# inside the cloud function code:

    local_credentials, project_id = google.auth.default()
    local_credentials.refresh(requests.Request())

    signer = google.auth.iam.Signer(requests.Request(), local_credentials, GMAIL_SERV_ACCOUNT)
    delegate_credentials = service_account.Credentials(
        signer, GMAIL_SERV_ACCOUNT, OAUTH_TOKEN_URI, scopes=GMAIL_SCOPES_LIST, subject=GMAIL_USER)
    delegate_credentials.refresh(requests.Request())

    try:
        email_api_service = googleapiclient.discovery.build(
            'gmail', 'v1', credentials=delegate_credentials, cache_discovery=False)
    except MutualTLSChannelError as err:
        # handle it somehow, for example (stupid, artificial)
        ER = error_reporting.Client(service="my-app", version=os.getenv("K_REVISION", "0"))
        ER.report_exception()
        return 0

因此,想法是使用我的(或“本地”)云函数的服务帐户来创建专用服务帐户的凭据(gmail_serv_account-在许多不同的“本地”服务帐户下运行的许多不同的云功能中使用);然后使用该“委托”服务帐户以获取API服务访问权限。

我不记得gmail_serv_account是否应该具有任何特定的IAM角色。但是我认为“本地”云函数的服务帐户应该掌握角色/iam.serviceaccounttokencreator

更新:

关于IAM角色的一些澄清。在Terraform(我将其用于我的CICD)作为给定功能组件中,它看起来:

# this service account is an 'external' for the given functional component, 
# it is managed in another repository and terraform state file
# so we should get it at first 
data "google_service_account" "gmail_srv_account" {
 project    = "some project id"
 account_id = "actual GMAIL_SERV_ACCOUNT account"
}

# now we provide IAM role for that working with it
# where 'google_service_account.local_cf_sa' is the service account, 
# under which the given cloud function is running
resource "google_service_account_iam_member" "iam_token_creator_gmail_sa" {
 service_account_id = data.google_service_account.gmail_srv_account.name
 role               = "roles/iam.serviceAccountTokenCreator"
 member             = "serviceAccount:${google_service_account.local_cf_sa.email}"
 depends_on         = [
   google_service_account.local_cf_sa,
 ]
}

Not sure I can answer precisely on the original question (I think not), but here how things are done in cloud functions developed by me. The following particular code snippet is written/adopted for this answer, and it was not tested:

import os
import google.auth
import google.auth.iam
from google.oauth2 import service_account
from google.auth.exceptions import MutualTLSChannelError
from google.auth.transport import requests
import googleapiclient.discovery

from google.cloud import error_reporting

GMAIL_SERV_ACCOUNT = "A service account which makes the API CALL"
OAUTH_TOKEN_URI = "https://accounts.google.com/o/oauth2/token"
GMAIL_SCOPES_LIST = ["https://mail.google.com/"] # for example
GMAIL_USER = "User's email address, who's email we would like to access. [email protected] - from your question"

# inside the cloud function code:

    local_credentials, project_id = google.auth.default()
    local_credentials.refresh(requests.Request())

    signer = google.auth.iam.Signer(requests.Request(), local_credentials, GMAIL_SERV_ACCOUNT)
    delegate_credentials = service_account.Credentials(
        signer, GMAIL_SERV_ACCOUNT, OAUTH_TOKEN_URI, scopes=GMAIL_SCOPES_LIST, subject=GMAIL_USER)
    delegate_credentials.refresh(requests.Request())

    try:
        email_api_service = googleapiclient.discovery.build(
            'gmail', 'v1', credentials=delegate_credentials, cache_discovery=False)
    except MutualTLSChannelError as err:
        # handle it somehow, for example (stupid, artificial)
        ER = error_reporting.Client(service="my-app", version=os.getenv("K_REVISION", "0"))
        ER.report_exception()
        return 0

So, the idea is to use my (or 'local') cloud function's service account to create credentials of a dedicated service account (GMAIL_SERV_ACCOUNT - which is used in many different cloud functions running under many different 'local' service accounts); then use that 'delegate' service account to get API service access.

I don't remember if the GMAIL_SERV_ACCOUNT should have any specific IAM roles. But I think the 'local' cloud function's service account should get roles/iam.serviceAccountTokenCreator for it.

Updated:

Some clarification on the IAM role. In terraform (I use it for my CICD) for a given functional component, it looks:

# this service account is an 'external' for the given functional component, 
# it is managed in another repository and terraform state file
# so we should get it at first 
data "google_service_account" "gmail_srv_account" {
 project    = "some project id"
 account_id = "actual GMAIL_SERV_ACCOUNT account"
}

# now we provide IAM role for that working with it
# where 'google_service_account.local_cf_sa' is the service account, 
# under which the given cloud function is running
resource "google_service_account_iam_member" "iam_token_creator_gmail_sa" {
 service_account_id = data.google_service_account.gmail_srv_account.name
 role               = "roles/iam.serviceAccountTokenCreator"
 member             = "serviceAccount:${google_service_account.local_cf_sa.email}"
 depends_on         = [
   google_service_account.local_cf_sa,
 ]
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文