【问题标题】:Google API: getting Credentials from refresh token with oauth2client.clientGoogle API:使用 oauth2client.client 从刷新令牌中获取凭据
【发布时间】:2015-03-02 12:09:20
【问题描述】:

我正在使用谷歌官方 oauth2client.client 访问谷歌 加上api。我有一个存储在数据库中的刷新令牌(不会过期),并且需要 从中重新创建临时“凭据”(访问令牌)。

但我无法通过谷歌提供的官方图书馆找到一种方法。

所以我绕过它:使用 urllib 访问 API,它给了我一个新的 来自 refresh_token 的 access_token。使用 access_token 我可以使用该库。

我一定是错过了什么!

from apiclient import discovery
from oauth2client.client import AccessTokenCredentials
from urllib import urlencode
from urllib2 import Request , urlopen, HTTPError
import json

# ==========================================

def access_token_from_refresh_token(client_id, client_secret, refresh_token):
  request = Request('https://accounts.google.com/o/oauth2/token',
    data=urlencode({
      'grant_type':    'refresh_token',
      'client_id':     client_id,
      'client_secret': client_secret,
      'refresh_token': refresh_token
    }),
    headers={
      'Content-Type': 'application/x-www-form-urlencoded',
      'Accept': 'application/json'
    }
  )
  response = json.load(urlopen(request))
  return response['access_token']

# ==========================================

access_token = access_token_from_refresh_token(CLIENT_ID, CLIENT_SECRET, REFRESH_TOKEN)

# now I can use the library properly
credentials = AccessTokenCredentials(access_token, "MyAgent/1.0", None)
http = credentials.authorize(httplib2.Http())
service = discovery.build('plus', 'v1', http=http)
google_request = service.people().get(userId='me')
result = google_request.execute(http=http)

【问题讨论】:

  • 嗨,您是否找到了在使用 AccessTokenCredentials 时刷新令牌的解决方案?我正在尝试找出如何做到这一点,但周围没有可见的文档......
  • 只是为了在历史上有所作为,以防其他人撞到墙上:Google 的文档在这里很复杂(或不完整)。 AccessTokenCredentials 不提供刷新令牌的方法。因此,您需要使用其他类型的凭据,正如以下答案中所解释的那样,我可以使用以下两个可能的选项:OAuth2CredentialsGoogleCredentialsGoogleCredentials 扩展 OAuth2Credentials)。

标签: python google-api google-plus


【解决方案1】:

我使用:oauth2client.client.GoogleCredentials

    cred = oauth2client.client.GoogleCredentials(access_token,client_id,client_secret,
                                          refresh_token,expires_at,"https://accounts.google.com/o/oauth2/token",some_user_agent)
    http = cred.authorize(httplib2.Http())
    cred.refresh(http)
    self.gmail_service = discovery.build('gmail', 'v1', credentials=cred)

【讨论】:

  • 我想稍后使用 refresh_token(保存在数据库中)检索新的 access_token。如果我使用 oauth2client.client.GoogleCredentials,正如您在此处所建议的那样,我是否应该将 access_code 连同 refresh_code 一起保存在我的数据库中以备将来使用?
  • 您可以将access_token 设置为None(因为无论如何您都在更新令牌),并且expires_at 也可以设置为None
【解决方案2】:

你可以像这样直接构造一个OAuth2Credentials实例:

import httplib2
from oauth2client import GOOGLE_REVOKE_URI, GOOGLE_TOKEN_URI, client

CLIENT_ID = '<client_id>'
CLIENT_SECRET = '<client_secret>'
REFRESH_TOKEN = '<refresh_token>'

credentials = client.OAuth2Credentials(
    access_token=None,  # set access_token to None since we use a refresh token
    client_id=CLIENT_ID,
    client_secret=CLIENT_SECRET,
    refresh_token=REFRESH_TOKEN,
    token_expiry=None,
    token_uri=GOOGLE_TOKEN_URI,
    user_agent=None,
    revoke_uri=GOOGLE_REVOKE_URI)

credentials.refresh(httplib2.Http())  # refresh the access token (optional)
print(credentials.to_json())
http = credentials.authorize(httplib2.Http())  # apply the credentials

【讨论】:

    【解决方案3】:

    我很容易解决了这个问题(你肯定想念this documentation)。这是我的代码的 sn-p,它尝试使用 Picasa API 从活跃用户那里获取所有专辑:

        http = httplib2.Http(ca_certs=os.environ['REQUESTS_CA_BUNDLE'])
        try:
            http = self.oauth.credentials.authorize(http)
            response, album_list = http.request(Picasa.PHOTOS_URL, 'GET')
            if response['status'] == '403':
                self.oauth.credentials.refresh(http)
                response, album_list = http.request(Picasa.PHOTOS_URL, 'GET')
            album_list = json.load(StringIO(album_list))
        except Exception as ex:
            Logger.debug('Picasa: error %s' % ex)
            return {}
    

    使用来自oauth2client.client.OAuth2Credentialsrefresh 方法。我认为使用if response['status'] != '200' 甚至可以。一定要检查一下!

    【讨论】:

    • 链接失效了,能否刷新一下?
    【解决方案4】:

    如果有人正在寻找有关如何将刷新令牌与 google_auth_oauthlib 一起使用的答案,以下方法对我有用:

    flow.oauth2session.refresh_token(flow.client_config['token_uri'],
                                     refresh_token=refresh_token,
                                     client_id=<MY_CLIENT_ID>,
                                     client_secret=flow.client_config['client_secret'])
    creds = google_auth_oauthlib.helpers.credentials_from_session(
        flow.oauth2session, flow.client_config)
    

    不过,我找不到任何记录在案的地方。

    【讨论】:

      【解决方案5】:

      如果您通过 google-auth 使用 2018 Youtube Python Quickstart demo app,则不能使用 oauth2client 的存储。

      So here is the correct way of storing the credentials

      这是 google-auth 的部分工作解决方案,缺少对令牌过期情况的正确处理:

      import os
      import json
      import os.path
      import google.oauth2.credentials
      from google.oauth2.credentials import Credentials
      from googleapiclient.discovery import build
      from googleapiclient.errors import HttpError
      from google_auth_oauthlib.flow import InstalledAppFlow
      
      CLIENT_SECRETS_FILE = "client_secret.json"
      SCOPES = ['https://www.googleapis.com/auth/youtube.force-ssl']
      API_SERVICE_NAME = 'youtube'
      API_VERSION = 'v3'
      
      def get_authenticated_service():
      
        if os.path.isfile("credentials.json"):
          with open("credentials.json", 'r') as f:
            creds_data = json.load(f)
          creds = Credentials(creds_data['token'])
      
        else:
          flow = InstalledAppFlow.from_client_secrets_file(CLIENT_SECRETS_FILE, SCOPES)
          creds = flow.run_console()
          creds_data = {
                'token': creds.token,
                'refresh_token': creds.refresh_token,
                'token_uri': creds.token_uri,
                'client_id': creds.client_id,
                'client_secret': creds.client_secret,
                'scopes': creds.scopes
            }
          print(creds_data)
          with open("credentials.json", 'w') as outfile:
            json.dump(creds_data, outfile)
        return build(API_SERVICE_NAME, API_VERSION, credentials = creds)
      
      def channels_list(service, **kwargs):
        results = service.channels().list(**kwargs).execute()
        print('This channel\'s ID is %s. Its title is %s, and it has %s views.' %
             (results['items'][0]['id'],
              results['items'][0]['snippet']['title'],
              results['items'][0]['statistics']['viewCount']))
         
      if __name__ == '__main__':
        os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1'
        service = get_authenticated_service()
      
        channels_list(service, part='snippet,contentDetails,statistics', forUsername='GoogleDevelopers')
        # or if the above doesn't work
        channels_list(service, part='snippet,contentDetails,statistics', id='YOUR_YOUTUBE_CHANNEL_ID')
      

      【讨论】:

      【解决方案6】:

      我推荐这种方法。

      from oauth2client import client, GOOGLE_TOKEN_URI
      
      CLIENT_ID = "client_id"
      CLIENT_SECRET = "client_secret"
      REFRESH_TOKEN = "refresh_token"
      
      
      credentials = client.OAuth2Credentials(
          access_token = None, 
          client_id = CLIENT_ID, 
          client_secret = CLIENT_SECRET, 
          refresh_token = REFRESH_TOKEN, 
          token_expiry = None, 
          token_uri = GOOGLE_TOKEN_URI,
          token_ id = None, 
          revoke_uri= None)
      
      http = credentials.authorize(httplib2.Http())
      

      即使访问令牌已过期,由于刷新令牌,凭据仍然是授权的。

      【讨论】:

        【解决方案7】:

        您也可以使用requests 库:

        import google.auth.transport.requests
        import requests
        
        request = google.auth.transport.requests.Request()
        
        credentials.refresh(request)
        

        这是我在一个活动项目中的示例代码:

        acct_creds = {
          'token': self.attachment.account.google_drive_access_token,
          'refresh_token': self.attachment.account.google_drive_refresh_token,
          'client_id': settings.GOOGLE_CLIENT_ID,
          'client_secret': settings.GOOGLE_CLIENT_SECRET,
          'token_uri': 'https://37947.ngrok.io/authenticate/google/callback/',
          'scopes': 'https://www.googleapis.com/auth/drive.appdata https://www.googleapis.com/auth/drive.file https://www.googleapis.com/auth/drive.install',
        }
        credentials = google.oauth2.credentials.Credentials(**acct_creds)
        if credentials.valid:
            print("Credentials valid")
        else:
            request = google.auth.transport.requests.Request()
            credentials.refresh(request)
        

        google.auth.transport.requests module

        【讨论】:

          【解决方案8】:

          您可以存储整个凭据,而不仅仅是刷新令牌:

          json = credentials.to_json()
          credentials = Credentials.new_from_json(json)
          

          看看Storage object是这样做的。

          【讨论】:

          • 这将如何帮助我以后获得访问令牌?凭据中包含的访问令牌过期,而刷新令牌则不会;所以我必须稍后再获得一个新的访问令牌。
          • 我相信 credentials.authorize(http) 将处理在 401 响应时自动刷新令牌。
          • 我收到了403 响应,insufficientPermissions Indicates that the user does not have sufficient permissions for the entity specified in the query,我认为它需要生成访问令牌。任何想法?我目前正在研究它
          • 我用这个,它似乎对刷新令牌工作正常。
          • 我可以批准该库在获得 401(已调试)时自动刷新访问令牌,因此答案是正确的
          【解决方案9】:

          哇.. 2 年前的问题,不是一个好的答案.. 鉴于 Google 文档在这方面是废话,这不足为奇。

          正确的做法是扩展存储类oauth2client.client.Storage

          一个示例实现(使用 mongodb 集合 _google_credentials)将类似于:

          class Storage(oauth2client.client.Storage):
          
          def __init__(self, key):
              super(Storage, self).__init__()
              self._key = key
          
          def locked_get(self):
              if not self._key: return None
              data = _google_credentials.find_one({'_id': self._key})
              if not data: return None
              credentials = oauth2client.client.Credentials.new_from_json(json.dumps(data))
              credentials.set_store(self)
              return credentials
          
          def locked_put(self, credentials):
              data = json.loads(credentials.to_json())
              _google_credentials.update_one({'_id': self._key}, {'$set': data}, 
                  upsert=True)
              credentials.set_store(self)
          
          def locked_delete(self):
              bucket.delete(self._key)
          

          那么当您最初在step2_exchange 之后获取凭据时,您需要使用Storage().put 存储它们:

          例如:

          credentials = flow.step2_exchange(code)
          Storage(user_id).put(credentials)
          

          当您再次需要凭据时,只需执行以下操作:

          credentials = Storage(user_id).get()
          

          【讨论】:

          【解决方案10】:

          如果您已经有一个 Credentials 对象,那么您可以像这样刷新它:

          if refresh:
              import google_auth_httplib2
              # credentials instanceof google.oauth2.credentials.Credentials
              credentials.refresh(google_auth_httplib2.Request(httplib2.Http()))
          

          我从一个旧的令牌 JSON 文件创建了 Credentials 对象,如下所示:

              credentials = google.oauth2.credentials.Credentials(
                  token=token_json['access_token'],
                  refresh_token=token_json['refresh_token'],
                  id_token=token_json['id_token'],
                  token_uri=token_json['token_uri'],
                  client_id=token_json['client_id'],
                  client_secret=token_json['client_secret'],
                  scopes=token_json['scopes'])
          

          通过这种方式,我能够修改一些旧的oauth2client 代码。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2019-06-18
            • 2013-06-14
            • 2012-02-15
            • 2016-10-13
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-07-15
            相关资源
            最近更新 更多