在 requests-oauthlib 的 oauth2session 中包含授权
Include authorization in a oauth2session for requests-oauthlib
从阅读各种文档来看,似乎 oauth2 提供者可选地需要授权来请求刷新令牌。我正在使用似乎需要授权的 FitBit API。
我按照此处的说明使用 requests-oauthlib
刷新令牌:
https://requests-oauthlib.readthedocs.io/en/latest/oauth2_workflow.html#refreshing-tokens
一些设置代码(不是我正在使用的,但你明白了:
>>> token = {
... 'access_token': 'eswfld123kjhn1v5423',
... 'refresh_token': 'asdfkljh23490sdf',
... 'token_type': 'Bearer',
... 'expires_in': '-30', # initially 3600, need to be updated by you
... }
>>> client_id = r'foo'
>>> refresh_url = 'https://provider.com/token'
>>> protected_url = 'https://provider.com/secret'
>>> # most providers will ask you for extra credentials to be passed along
>>> # when refreshing tokens, usually for authentication purposes.
>>> extra = {
... 'client_id': client_id,
... 'client_secret': r'potato',
... }
>>> # After updating the token you will most likely want to save it.
>>> def token_saver(token):
... # save token in database / session
from requests_oauthlib import OAuth2Session
client = OAuth2Session(client_id, token=token, auto_refresh_url=refresh_url,
auto_refresh_kwargs=extra, token_updater=token_saver)
r = client.get(protected_url)
但是,通过这个调用我得到:
MissingTokenError: (missing_token) Missing access token parameter.
我知道我的令牌已过期,但为什么刷新不起作用?
编辑:下面仍然有一些有用的信息,但是覆盖 auth 函数意味着我的实际 API 请求现在失败了(即下面不是正确的答案) 我不确定我是如何得到我上次尝试工作的一个请求的。它可能只是返回了一个错误(在 json 中)而不是抛出错误,我只是假设没有出现错误意味着它实际上在工作。 查看 OrangeDog 的正确解决方法(直到修复库)。
嗯,我检查了 FitBit 服务器的响应,就在抛出 MissingTokenError 之前。结果我收到一个错误,说身份验证不正确。
这可能是一个很有用的观点,值得仔细研究一下。 MissingTokenError 似乎在响应不包含预期令牌时发生。如果您可以更仔细地调试和查看响应,您可能会发现服务器提供了更多关于请求格式错误原因的详细信息。我去了错误的位置并添加了打印语句。这使我能够看到来自 FitBit 的 JSON 消息。无论如何,这种方法可能对其他人获得 MissingTokenError 有用。
if not 'access_token' in params:
print(params)
raise MissingTokenError(description="Missing access token parameter.")
反正调试了一番,还是没有设置认证。此外,我的客户端 ID 和密码已发布在正文中(这可能不是问题)。在 FitBit 示例中,客户端 ID 和密码未发布在正文中,而是通过身份验证传递。所以我需要客户端将身份验证传递给 FitBit。
那么问题来了,我如何进行身份验证。目前缺乏这方面的文档。但是,查看会话对象时,我发现了一个正在设置的 .auth 属性 和对问题的引用 (#278)。在那个问题中,为手动身份验证设置提供了一个解决方法(如下所示,我的代码):https://github.com/requests/requests-oauthlib/issues/278
请注意,oauth 会话继承自请求会话,因此对于真正了解请求的人来说,这可能是显而易见的...
无论如何,解决办法就是在初始化会话后设置auth参数。由于 FitBit 不需要主体中的客户端 ID 和密码,我也删除了额外的传递(同样,这可能是一个小问题,不会真正影响事情):
import os
import json
from requests.auth import HTTPBasicAuth
from requests_oauthlib import OAuth2Session
client_id = ""
client_secret = ""
with open("tokens.json", "r") as read_file:
token = json.load(read_file)
save_file_path = os.path.abspath('tokens.json')
refresh_url = 'https://api.fitbit.com/oauth2/token'
def token_saver(token):
with open(save_file_path, "w") as out_file:
json.dump(token, out_file, indent = 6)
#Note, I've removed the 'extras' input
client = OAuth2Session(client_id, token=token, auto_refresh_url=refresh_url, token_updater=token_saver)
#This was the magic line ...
auth = HTTPBasicAuth(client_id, client_secret)
client.auth = auth
url = 'https://api.fitbit.com/1.2/user/-/sleep/date/2021-01-01-2021-01-23.json'
wtf = client.get(url)
好的,我想我正确地复制了那个代码,目前我这边有点乱。关键部分只是一行:
client.auth = auth
客户端启动后。
请注意,我的令牌包含一个 expires_at
字段。我不认为会话在确切时间方面处理 expires_in
。换句话说,我认为 expires_in
仅在其值小于 0 时才会导致刷新。我认为它不会查看对象的创建时间并启动计时器或将 属性 设置为知道 expires_in
是相对于什么的。另一方面,expires_at
字段似乎提供(我认为)一个字段,该字段被检查以确保令牌在请求时尚未过期,因为 expires_at
是一个真实世界,非亲属,时间。这是我的令牌字典(带有假令牌和 user_id):
{'access_token': '1234',
'expires_in': 28800,
'refresh_token': '5678',
'scope': ['heartrate',
'profile',
'settings',
'nutrition',
'location',
'weight',
'activity',
'sleep',
'social'],
'token_type': 'Bearer',
'user_id': 'abcd',
'expires_at': 1611455442.4566112}
图书馆在这方面有问题。参见 #379。
您可以像这样解决它:
def _wrap_refresh(func):
def wrapper(*args, **kwargs):
kwargs['auth'] = (client_id, client_secret)
kwargs.pop('allow_redirects', None)
return func(*args, **kwargs)
return wrapper
client = OAuth2Session(client_id, token=token,
auto_refresh_url=refresh_url,
token_updater=token_saver)
client.refresh_token = _wrap_refresh(client.refresh_token)
从阅读各种文档来看,似乎 oauth2 提供者可选地需要授权来请求刷新令牌。我正在使用似乎需要授权的 FitBit API。
我按照此处的说明使用 requests-oauthlib
刷新令牌:
https://requests-oauthlib.readthedocs.io/en/latest/oauth2_workflow.html#refreshing-tokens
一些设置代码(不是我正在使用的,但你明白了:
>>> token = {
... 'access_token': 'eswfld123kjhn1v5423',
... 'refresh_token': 'asdfkljh23490sdf',
... 'token_type': 'Bearer',
... 'expires_in': '-30', # initially 3600, need to be updated by you
... }
>>> client_id = r'foo'
>>> refresh_url = 'https://provider.com/token'
>>> protected_url = 'https://provider.com/secret'
>>> # most providers will ask you for extra credentials to be passed along
>>> # when refreshing tokens, usually for authentication purposes.
>>> extra = {
... 'client_id': client_id,
... 'client_secret': r'potato',
... }
>>> # After updating the token you will most likely want to save it.
>>> def token_saver(token):
... # save token in database / session
from requests_oauthlib import OAuth2Session
client = OAuth2Session(client_id, token=token, auto_refresh_url=refresh_url,
auto_refresh_kwargs=extra, token_updater=token_saver)
r = client.get(protected_url)
但是,通过这个调用我得到:
MissingTokenError: (missing_token) Missing access token parameter.
我知道我的令牌已过期,但为什么刷新不起作用?
编辑:下面仍然有一些有用的信息,但是覆盖 auth 函数意味着我的实际 API 请求现在失败了(即下面不是正确的答案) 我不确定我是如何得到我上次尝试工作的一个请求的。它可能只是返回了一个错误(在 json 中)而不是抛出错误,我只是假设没有出现错误意味着它实际上在工作。 查看 OrangeDog 的正确解决方法(直到修复库)。
嗯,我检查了 FitBit 服务器的响应,就在抛出 MissingTokenError 之前。结果我收到一个错误,说身份验证不正确。
这可能是一个很有用的观点,值得仔细研究一下。 MissingTokenError 似乎在响应不包含预期令牌时发生。如果您可以更仔细地调试和查看响应,您可能会发现服务器提供了更多关于请求格式错误原因的详细信息。我去了错误的位置并添加了打印语句。这使我能够看到来自 FitBit 的 JSON 消息。无论如何,这种方法可能对其他人获得 MissingTokenError 有用。
if not 'access_token' in params:
print(params)
raise MissingTokenError(description="Missing access token parameter.")
反正调试了一番,还是没有设置认证。此外,我的客户端 ID 和密码已发布在正文中(这可能不是问题)。在 FitBit 示例中,客户端 ID 和密码未发布在正文中,而是通过身份验证传递。所以我需要客户端将身份验证传递给 FitBit。
那么问题来了,我如何进行身份验证。目前缺乏这方面的文档。但是,查看会话对象时,我发现了一个正在设置的 .auth 属性 和对问题的引用 (#278)。在那个问题中,为手动身份验证设置提供了一个解决方法(如下所示,我的代码):https://github.com/requests/requests-oauthlib/issues/278
请注意,oauth 会话继承自请求会话,因此对于真正了解请求的人来说,这可能是显而易见的...
无论如何,解决办法就是在初始化会话后设置auth参数。由于 FitBit 不需要主体中的客户端 ID 和密码,我也删除了额外的传递(同样,这可能是一个小问题,不会真正影响事情):
import os
import json
from requests.auth import HTTPBasicAuth
from requests_oauthlib import OAuth2Session
client_id = ""
client_secret = ""
with open("tokens.json", "r") as read_file:
token = json.load(read_file)
save_file_path = os.path.abspath('tokens.json')
refresh_url = 'https://api.fitbit.com/oauth2/token'
def token_saver(token):
with open(save_file_path, "w") as out_file:
json.dump(token, out_file, indent = 6)
#Note, I've removed the 'extras' input
client = OAuth2Session(client_id, token=token, auto_refresh_url=refresh_url, token_updater=token_saver)
#This was the magic line ...
auth = HTTPBasicAuth(client_id, client_secret)
client.auth = auth
url = 'https://api.fitbit.com/1.2/user/-/sleep/date/2021-01-01-2021-01-23.json'
wtf = client.get(url)
好的,我想我正确地复制了那个代码,目前我这边有点乱。关键部分只是一行:
client.auth = auth
客户端启动后。
请注意,我的令牌包含一个 expires_at
字段。我不认为会话在确切时间方面处理 expires_in
。换句话说,我认为 expires_in
仅在其值小于 0 时才会导致刷新。我认为它不会查看对象的创建时间并启动计时器或将 属性 设置为知道 expires_in
是相对于什么的。另一方面,expires_at
字段似乎提供(我认为)一个字段,该字段被检查以确保令牌在请求时尚未过期,因为 expires_at
是一个真实世界,非亲属,时间。这是我的令牌字典(带有假令牌和 user_id):
{'access_token': '1234',
'expires_in': 28800,
'refresh_token': '5678',
'scope': ['heartrate',
'profile',
'settings',
'nutrition',
'location',
'weight',
'activity',
'sleep',
'social'],
'token_type': 'Bearer',
'user_id': 'abcd',
'expires_at': 1611455442.4566112}
图书馆在这方面有问题。参见 #379。
您可以像这样解决它:
def _wrap_refresh(func):
def wrapper(*args, **kwargs):
kwargs['auth'] = (client_id, client_secret)
kwargs.pop('allow_redirects', None)
return func(*args, **kwargs)
return wrapper
client = OAuth2Session(client_id, token=token,
auto_refresh_url=refresh_url,
token_updater=token_saver)
client.refresh_token = _wrap_refresh(client.refresh_token)