AccessTokenRefreshError:带有App Engine应用程序的oAuth 2.0服务帐户的Google Spreadsheet API

时间:2014-11-14 07:38:54

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

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

  1. 该应用程序使用Google最新的gdata-python-client,v.2.1.18(gdata和atom)。
  2. 该应用使用最近的google-api-python-client-gae,v.1.2。
  3. 在此项目的Google Developer Console中(在此示例中称为" my-gae-app"),我创建了一个服务帐户,并将域范围的权限委派给服务帐户{{ 3}}
  4. Google云端硬盘中的所需电子表格属于Google Apps for Work域名,此处称为" mygoogleappsdomain.com"。
  5. 我已将电子表格的读取/写入权限授予my-gae-app@appspot.gserviceaccount.com以及此服务帐户显示的电子邮件地址,并在下面的代码中分配给clientEmail变量。不确定实际需要哪两个电子邮件地址,所以我分配了两个。具有impersonateUser电子邮件地址的用户也具有对此电子表格的读写权限。
  6. 借助Google API Python客户端AppAssertionCredentials,我可以通过Google Drive API访问所需电子表格的元数据。但是,如果我尝试使用gdata访问电子表格的内容,我会收到错误。到目前为止,我使用服务帐户获得的最佳结果是使用SignedJwtAssertionCredentialsas described 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
    

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

1 个答案:

答案 0 :(得分:3)

我已经发现,调出SignedJwtAssertionCredentials而不使用sub参数(对于&#34;模仿&#34;用户)将不会产生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!