7

我正在使用 Flask 框架构建一个网站 + 后端,在该框架中我使用Flask-OAuthlib向谷歌进行身份验证。认证后,后端需要定期扫描用户他的Gmail。所以目前用户可以对我的应用程序进行身份验证,我存储access_tokenrefresh_token. 一access_token小时后过期,所以在一小时内我可以像这样获得用户信息:

google = oauthManager.remote_app(
        'google',
        consumer_key='xxxxxxxxx.apps.googleusercontent.com',
        consumer_secret='xxxxxxxxx',
        request_token_params={
            'scope': ['https://www.googleapis.com/auth/userinfo.email', 'https://www.googleapis.com/auth/gmail.readonly'],
            'access_type': 'offline'
        },
        base_url='https://www.googleapis.com/oauth2/v1/',
        request_token_url=None,
        access_token_method='POST',
        access_token_url='https://accounts.google.com/o/oauth2/token',
        authorize_url='https://accounts.google.com/o/oauth2/auth'
    )

token = (the_stored_access_token, '')
userinfoObj = google.get('userinfo', token=token).data
userinfoObj['id']  # Prints out my google id

一个小时结束后,我需要使用 refresh_token(我已经存储在我的数据库中)来请求一个新的access_token. 我尝试用 替换the_stored_access_tokenthe_stored_refresh_token但这只是给了我一个Invalid Credentials-error。

这个 github 问题中,我阅读了以下内容:

无论您如何获得访问令牌/刷新令牌(无论是通过授权代码授予还是资源所有者密码凭据),您都可以通过将刷新令牌作为 refresh_token 并将 grant_type 设置为“refresh_token”来交换它们。

从这里我明白我必须像这样创建一个远程应用程序:

google = oauthManager.remote_app(
        'google',
        #  also the consumer_key, secret, request_token_params, etc..
        grant_type='refresh_token',
        refresh_token=u'1/xK_ZIeFn9quwvk4t5VRtE2oYe5yxkRDbP9BQ99NcJT0'
    )

但这会导致TypeError: __init__() got an unexpected keyword argument 'refresh_token'. 所以从这里我有点迷路了。

有人知道我如何使用refresh_token来获得新的access_token吗?欢迎所有提示!

4

5 回答 5

1

这就是我为谷歌获得新的 access_token 的方式:

from urllib2 import Request, urlopen, URLError
from webapp2_extras import json
import mimetools
BOUNDARY = mimetools.choose_boundary()
def refresh_token()
    url = google_config['access_token_url']
    headers = [
             ("grant_type", "refresh_token"),
             ("client_id", <client_id>),
             ("client_secret", <client_secret>),
             ("refresh_token", <refresh_token>),
             ]

    files = []
    edata = EncodeMultiPart(headers, files, file_type='text/plain')
    headers = {}
    request = Request(url, headers=headers)
    request.add_data(edata)

    request.add_header('Content-Length', str(len(edata)))
    request.add_header('Content-Type', 'multipart/form-data;boundary=%s' % BOUNDARY)
    try:
        response = urlopen(request).read()
        response = json.decode(response)
    except URLError, e:
        ...

EncodeMultipart 函数取自这里: https ://developers.google.com/cloud-print/docs/pythonCode

确保使用相同的 BOUNDARY

于 2015-10-29T10:22:28.527 回答
0

查看OAuthRemoteApp的源代码。构造函数不采用名为 refresh_token 的关键字参数。然而,它确实需要一个名为access_token_paramswhich is的参数an optional dictionary of parameters to forward to the access token url

由于 url 相同,但授权类型不同。我想这样的电话应该可以工作:

google = oauthManager.remote_app(
    'google',
    #  also the consumer_key, secret, request_token_params, etc..
    grant_type='refresh_token',
    access_token_params = {
       refresh_token=u'1/xK_ZIeFn9quwvk4t5VRtE2oYe5yxkRDbP9BQ99NcJT0'
    }
)
于 2015-04-17T20:04:48.170 回答
0

有了你的refresh_token,你可以得到一个新的access_token喜欢:

from google.oauth2.credentials import Credentials
from google.auth.transport import requests

creds = {"refresh_token": "<goes here>",
  "token_uri": "https://accounts.google.com/o/oauth2/token",
  "client_id": "<YOUR_CLIENT_ID>.apps.googleusercontent.com",
  "client_secret": "<goes here>",
  "scopes": ["https://www.googleapis.com/auth/userinfo.email"]}
cred = Credentials.from_authorized_user_info(creds)
cred.refresh(requests.Request())
my_new_access_token = cred.token 
于 2021-07-07T07:16:28.290 回答
0

flask-oauthlib.contrib 在 remote_app 中包含一个名为auto_refresh_url / refresh_token_url 的参数,它完全符合您的要求。如何使用它的示例如下所示:

app= oauth.remote_app(
    [...]
    refresh_token_url='https://www.douban.com/service/auth2/token',
    authorization_url='https://www.douban.com/service/auth2/auth',
    [...]
)

但是我没有设法让它以这种方式运行。尽管如此,这在没有 contrib 包的情况下是可能的。我的解决方案是捕获 401 API 调用并在 refresh_token 可用时重定向到刷新页面。我的刷新端点代码如下所示:

@app.route('/refresh/')
def refresh():
    data = {}
    data['grant_type'] = 'refresh_token'
    data['refresh_token'] = session['refresh_token'][0]
    data['client_id'] = CLIENT_ID
    data['client_secret'] = CLIENT_SECRET
    # make custom POST request to get the new token pair
    resp = remote.post(remote.access_token_url, data=data)

    # checks the response status and parses the new tokens
    # if refresh failed will redirect to login
    parse_authorized_response(resp)

    return redirect('/')

def parse_authorized_response(resp):
    if resp is None:
        return 'Access denied: reason=%s error=%s' % (
            request.args['error_reason'],
            request.args['error_description']
        )
    if isinstance(resp, dict):
            session['access_token'] = (resp['access_token'], '')
            session['refresh_token'] = (resp['refresh_token'], '')  
    elif isinstance(resp, OAuthResponse):
        print(resp.status)
        if resp.status != 200:
            session['access_token'] = None
            session['refresh_token'] = None
            return redirect(url_for('login'))
        else:
            session['access_token'] = (resp.data['access_token'], '')
            session['refresh_token'] = (resp.data['refresh_token'], '')
    else:
        raise Exception()
    return redirect('/')

希望这会有所帮助。代码当然可以增强,肯定有比捕捉 401ers 更优雅的方法,但这是一个开始;)

另一件事:不要将令牌存储在 Flask 会话 Cookie 中。而是使用我在代码中所做的“Flask Session”中的服务器端会话!

于 2016-08-16T11:48:46.020 回答
0

这就是我获得新访问令牌的方式。

from urllib2 import Request, urlopen, URLError
import json
import mimetools
BOUNDARY = mimetools.choose_boundary()
CRLF = '\r\n'
def EncodeMultiPart(fields, files, file_type='application/xml'):
    """Encodes list of parameters and files for HTTP multipart format.

    Args:
      fields: list of tuples containing name and value of parameters.
      files: list of tuples containing param name, filename, and file contents.
      file_type: string if file type different than application/xml.
    Returns:
      A string to be sent as data for the HTTP post request.
    """
    lines = []
    for (key, value) in fields:
      lines.append('--' + BOUNDARY)
      lines.append('Content-Disposition: form-data; name="%s"' % key)
      lines.append('')  # blank line
      lines.append(value)
    for (key, filename, value) in files:
      lines.append('--' + BOUNDARY)
      lines.append(
          'Content-Disposition: form-data; name="%s"; filename="%s"'
          % (key, filename))
      lines.append('Content-Type: %s' % file_type)
      lines.append('')  # blank line
      lines.append(value)
    lines.append('--' + BOUNDARY + '--')
    lines.append('')  # blank line
    return CRLF.join(lines)
def refresh_token():
    url = "https://oauth2.googleapis.com/token"
    headers = [
             ("grant_type",  "refresh_token"),
             ("client_id", "xxxxxx"),
             ("client_secret", "xxxxxx"),
             ("refresh_token", "xxxxx"),
             ]

    files = []
    edata = EncodeMultiPart(headers, files, file_type='text/plain')
    #print(EncodeMultiPart(headers, files, file_type='text/plain'))
    headers = {}
    request = Request(url, headers=headers)
    request.add_data(edata)

    request.add_header('Content-Length', str(len(edata)))
    request.add_header('Content-Type', 'multipart/form-data;boundary=%s' % BOUNDARY)
    response = urlopen(request).read()
    print(response)
refresh_token()
#response = json.decode(response)
#print(refresh_token())
于 2020-07-19T22:57:34.563 回答