【问题标题】:AccessTokenRefreshError: Google Spreadsheet API with oAuth 2.0 Service Account of App Engine AppAccessTokenRefreshError: Google Spreadsheet API with oAuth 2.0 Service Account of App Engine App
【发布时间】:2015-01-11 13:22:25
【问题描述】:

我正在尝试使用为托管在 Google App Engine 上的 Python 2.7 应用创建的 oAuth 2.0 服务帐户凭据通过 GData API 访问 Google 电子表格。

  1. 该应用使用来自 Google 的最新 gdata-python-client,v. 2.0.18(gdata 和 atom)。
  2. 该应用使用最新的 google-api-python-client-gae,v. 1.2。
  3. 在此项目的 Google Developer Console 中(在此示例中称为“my-gae-app”),我创建了一个服务帐户并将域范围的权限委托给服务帐户 as described here
  4. Google 云端硬盘中所需的电子表格属于 Google Apps for Work 域,此处称为“mygoogleappsdomain.com”。
  5. 我已将电子表格的读写权限授予my-gae-app@appspot.gserviceaccount.com 和为此服务帐户显示的电子邮件地址,该电子邮件地址在下面的代码中分配给clientEmail 变量。不确定实际上需要这两个电子邮件地址中的哪一个,所以我都分配了。拥有impersonateUser 电子邮件地址的用户也拥有此电子表格的读写权限。

使用 Google API Python 客户端的AppAssertionCredentials,我可以通过 Google Drive API 访问所需电子表格的元数据。但是,如果我尝试使用 gdata 访问电子表格的内容,则会出现错误。到目前为止,我使用服务帐户可以获得的最佳结果是使用 SignedJwtAssertionCredentialsas suggested here。但是,我被这个AccessRefreshTokenError: access denied 卡住了,我不明白出了什么问题。

import os
import httplib2
from google.appengine.api import memcache
from apiclient.discovery import build
from oauth2client.client import SignedJwtAssertionCredentials
import gdata.spreadsheets.client
import gdata.spreadsheet.service

# AppAssertionCredentials is not supported in gdata python client library,
# so we use SignedJwtAssertionCredentials with the credential 
# file of this service account.
# Load the key in PKCS 12 format that you downloaded from the Google API
# Console when you created your Service account.
clientEmail = '10346........-g3dp......................3m1em8@developer.gserviceaccount.com'
p12File = 'app.p12'
path = os.path.join(ROOT_DIR, 'data', 'oAuth2', p12File)
impersonateUser = 'user@mygoogleappsdomain.com'
spreadsheetKey = '1mcJHJ...................................juQMw' # ID copied from URL of desired spreadsheet in Google Drive
with open(path) as f:
    privateKey = f.read()
    f.close()

# Getting credentials with AppAssertionCredentials only worked successfully
# for Google API Client Library for Python, e.g. accessing file's meta-data.
# So we use SignedJwtAssertionCredentials, as suggested in
# https://stackoverflow.com/questions/16026286/using-oauth2-with-service-account-on-gdata-in-python
credentials = SignedJwtAssertionCredentials(
    clientEmail,
    privateKey,
    scope=(
        'https://www.googleapis.com/auth/drive.file ',
        # added the scope above as suggested somewhere else,
        # but error occurs with and with-out this scope
        'https://www.googleapis.com/auth/drive',
        'https://spreadsheets.google.com/feeds',
        'https://docs.google.com/feeds'
    ),
    sub=impersonateUser
)

http = httplib2.Http()
http = credentials.authorize(http)
auth2token = gdata.gauth.OAuth2TokenFromCredentials(credentials)
# error will occur, wether using SpreadsheetsService() or SpreadsheetsClient()
#srv = gdata.spreadsheet.service.SpreadsheetsService()
#srv = auth2token.authorize(srv)

clt = gdata.spreadsheets.client.SpreadsheetsClient()
clt = auth2token.authorize(clt)
# Until here no errors

wks = clt.get_worksheets(spreadsheetKey)
# AccessTokenRefreshError: access_denied

这是我在远程 shell 中遇到的错误:

s~my-gae-app> wks = clt.get_worksheets(spreadsheetKey)
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "gdata/spreadsheets/client.py", line 108, in get_worksheets
    **kwargs)
  File "gdata/client.py", line 640, in get_feed
    **kwargs)
  File "gdata/client.py", line 267, in request
    uri=uri, auth_token=auth_token, http_request=http_request, **kwargs)
  File "atom/client.py", line 122, in request
    return self.http_client.request(http_request)
  File "gdata/gauth.py", line 1344, in new_request
    refresh_response = self._refresh(request_orig)
  File "gdata/gauth.py", line 1485, in _refresh
    self.credentials._refresh(httplib2.Http().request)
  File "/usr/local/lib/python2.7/dist-packages/oauth2client/client.py", line 653, in _refresh
    self._do_refresh_request(http_request)
  File "/usr/local/lib/python2.7/dist-packages/oauth2client/client.py", line 710, in _do_refresh_request
    raise AccessTokenRefreshError(error_msg)
AccessTokenRefreshError: access_denied

我不确定这是否表明此服务帐户被拒绝访问电子表格,或者刷新访问令牌时是否出错。你知道这段代码或设置有什么问题吗?

【问题讨论】:

  • 它可能访问电子表格。将服务帐户视为一个人。如果随机用户 X 无权访问该工作表,则服务帐户也无权访问。我不确定您是否可以,但请尝试获取服务帐户电子邮件并像随机用户 X 一样授予它访问权限。
  • 在添加读写访问权限时,我不知道在电子表格文件中使用哪个电子邮件地址,所以我使用了两个地址,即 GAE 应用程序的实际电子邮件地址,例如“my-gae-app@appspot.gserviceaccount.com”以及服务帐户的那个很长的看起来很神秘的电子邮件地址,例如“10346.......-g3dp............3m1em8@developer.gserviceaccount.com”。我不确定在调用SignedJwtAssertionCredentials() 时是否必须使用其中一个作为“子”参数。目前,它使用我们 Google Apps 域中的普通用户电子邮件,该用户也对该文件具有读写权限。
  • 创建服务帐户时获得的那个看起来很长,看起来很神秘的电子邮件地址。是您应该使用的,因为它是服务帐户的用户标识。它与您下载的密钥文件配对构成服务帐户登录

标签: python google-app-engine google-oauth google-spreadsheet-api gdata-python-client


【解决方案1】:

我发现在没有sub 参数(对于“模拟”用户)的情况下调用SignedJwtAssertionCredentials 不会产生AccessTokenRefreshError: access_denied

import os
import httplib2
from google.appengine.api import memcache
from apiclient.discovery import build
from oauth2client.client import SignedJwtAssertionCredentials
import gdata.spreadsheets.client
import gdata.spreadsheet.service

# AppAssertionCredentials is not supported in gdata python client library,
# so we use SignedJwtAssertionCredentials with the credential 
# file of this service account.
# Load the key in PKCS 12 format that you downloaded from the Google API
# Console when you created your Service account.
clientEmail = '10346........-g3dp......................3m1em8@developer.gserviceaccount.com'
p12File = 'app.p12'
path = os.path.join(ROOT_DIR, 'data', 'oAuth2', p12File)
impersonateUser = 'user@mygoogleappsdomain.com'
spreadsheetKey = '1mcJHJ...................................juQMw' # ID copied from URL of desired spreadsheet in Google Drive
with open(path) as f:
    privateKey = f.read()
    f.close()

# Getting credentials with AppAssertionCredentials only worked successfully
# for Google API Client Library for Python, e.g. accessing file's meta-data.
# So we use SignedJwtAssertionCredentials, as suggested in
# http://stackoverflow.com/questions/16026286/using-oauth2-with-service-account-on-gdata-in-python
# but with-out the sub parameter!
credentials = SignedJwtAssertionCredentials(
    clientEmail,
    privateKey,
    scope=(
        'https://www.googleapis.com/auth/drive.file ',
        # added the scope above as suggested somewhere else,
        # but error occurs with and with-out this scope
        'https://www.googleapis.com/auth/drive',
        'https://spreadsheets.google.com/feeds',
        'https://docs.google.com/feeds'
    ),
#    sub=impersonateUser
)

http = httplib2.Http()
http = credentials.authorize(http)
auth2token = gdata.gauth.OAuth2TokenFromCredentials(credentials)
# this pattern would eventually also work using SpreadsheetsService()
# SpreadsheetsService methods are different from SpreadsheetsClient, though
#srv = gdata.spreadsheet.service.SpreadsheetsService()
#srv = auth2token.authorize(srv)

clt = gdata.spreadsheets.client.SpreadsheetsClient()
clt = auth2token.authorize(clt)

wks = clt.get_worksheets(spreadsheetKey)
# works now!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-08-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-02
    • 2011-08-26
    • 1970-01-01
    • 2014-07-30
    相关资源
    最近更新 更多