如何使用refresh_token获取新的access_token(使用Flask-OAuthLib)?

时间:2014-11-26 10:15:44

标签: python oauth flask google-api flask-oauthlib

我正在使用FLask Framework构建一个网站+后端,我使用Flask-OAuthlib对Google进行身份验证。身份验证后,后端需要定期扫描用户的Gmail。因此,目前用户可以对我的应用进行身份验证,然后存储access_tokenrefresh_tokenaccess_token在一小时后过期,所以在这一小时内,我可以像这样获得userinfo:

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_token替换为the_stored_refresh_token,但这只会给我Invalid Credentials - 错误。

this github issue中,我阅读了以下内容:

  

无论您如何获得访问令牌/刷新令牌(无论是通过授权代码授予还是资源所有者密码凭据),您都可以通过将刷新令牌作为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吗?欢迎所有提示!

5 个答案:

答案 0 :(得分:1)

这就是我为google获取新的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

答案 1 :(得分:0)

查看OAuthRemoteApp的源代码。构造函数不接受名为refresh_token的关键字参数。然而,它确实采用名为access_token_params的{​​{1}}参数。

由于url相同,但授权类型不同。我想这样的电话会起作用:

an optional dictionary of parameters to forward to the access token url

答案 2 :(得分:0)

flask-oauthlib.contrib在remote_app中包含一个名为auto_refresh_url / refresh_token_url的参数,它完全符合您的想要做的事情。 An example如何使用它看起来像这样:

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”中的服务器端会话!

答案 3 :(得分: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())

答案 4 :(得分:0)

使用您的var companies = db.Set<Company>() .Include(e => e.Tags) .ToList(); var includedTags = companies .SelectMany(e => e.Tags) .ToList(); var actualTags = db.Set<Company>() .SelectMany(e => e.Tags) .ToList(); ,您可以获得一个新的refresh_token,例如:

access_token