我正在使用Python
3.5.1与Requests
2.9.1。我的用例如下:我需要从服务T验证(获取令牌)并在向资源服务器R发出请求时将其用作Authorization
标头的值。令牌在某个时刻到期并且新的到期需要获取。
我有一个使用requests
的应用程序,它首先启动时获取一个令牌并记住它 - 在用于R的请求的Session
中设置。从那时起3分钟,一切都可以作为一个魅力。 3分钟后,由于令牌无效,我收到unauthorized
个回复。
我对{strong>除了之外的所有请求使用Session
进行身份验证;此调用会更新Authorization
上的Session
标头,以供其他请求使用。
我创建了代码,以便在检测到unauthorized
时使用response
挂钩(仅在Session
上设置)自动重新进行身份验证,以下是代码:
def __hook(self, res, *args, **kwargs):
if res.status_code == requests.codes.unauthorized:
print('Token expired, refreshing')
self.auth() # sets the token on self.__session
req = res.request
print('Resending request', req.method, req.url, req.headers)
req.headers['Authorization'] = self.__session.headers['Authorization'] # why is it needed?
return self.__session.send(res.request)
基本上,它甚至可以工作。但是有几个问题:
为什么有必要在请求中重新设置Authorization
标头,即使会话已更新并用于重新发送原始请求?如果没有该行,应用程序将继续刷新令牌,因为永远不会使用新令牌,这可以在输出中看到,令牌是导致自动刷新的原始令牌。
如何使代码更加健壮,即防止无休止的递归(我不确定它是否可能在现实中,但是在重试的请求中设置Authorization
标题的行将只是继续)?我正在考虑设置一个自定义标头,如果挂钩发现失败的请求有它,它将不会重新进行身份验证和重新发送。有更好的吗?
编辑:事实证明,如果配置错误,可能会得到(几乎)无限循环(毕竟它是递归的):令牌用于一个环境(如STAGING),但是资源服务器来自另一个(TEST) - auth
请求将成功,但令牌实际上对资源服务器不正确。目前我实施了上面提到的“特殊”标题解决方案。
这是一般的好方法还是更适合requests
中的任务?
答案 0 :(得分:1)
所以我与Requests作者讨论了一个不相关的问题,并询问了这一点,结果证明这种方法还可以。我做的唯一改变是删除自定义标头(它确实工作,也是好的)现在我只是在重新发送请求之前注销钩子内的响应挂钩。这打破了循环,一切都很好。
答案 1 :(得分:0)
我想出的最终代码来解决这个问题。它比OP更完整。它公开了可用于身份验证请求的会话属性。
您必须撤回auth的原因是因为.send
仅发送了PreparedRequest。
如果您不喜欢REATTEMPT标头,则添加了该标头以防止无限递归。您也可以像前面提到的OP一样取消注册该钩子(但您必须再次注册该钩子以备将来使用),或者在我的编码类中改为使用self._session
而不是self.session
进行新请求。 (self._session
只是一个没有钩子的会话。)
import requests
from urllib.parse import urljoin
class JWTAuth:
def __init__(self, user, passw, base):
self.base = base
self.user, self.passw = user, passw
self._session = requests.Session() # Session for tokens
self.authenticate()
self.session = requests.Session() # Authenticated session
self.session.auth = self.auth
self.session.hooks['response'].append(self.reauth)
def abs_url(self, path):
"""Combine the base url with a path."""
return urljoin(self.base, path)
def auth(self, req):
"""Just set the authentication token, on every request."""
req.headers['Authorization'] = f'JWT {self.access}'
return req
def reauth(self, res, *args, **kwargs):
"""Hook to re-authenticate whenever authentication expires."""
if res.status_code == requests.codes.unauthorized:
if res.request.headers.get('REATTEMPT'):
res.raise_for_status()
self.refresh_auth()
req = res.request
req.headers['REATTEMPT'] = 1
req = self.session.auth(req)
res = self.session.send(req)
return res
def refresh_auth(self):
"""Use the refresh token to get a new access token."""
res = self._session.post(self.abs_url("api/token/refresh/"), data={"refresh": self.refresh})
if res.status_code == 200:
self.access = res.json()['access']
else:
# Token expired -> re-authenticate
self.authenticate()
def authenticate(self):
res = self._session.post(
self.abs_url("api/token/"),
data={"username": self.user, "password": self.passw},
)
res.raise_for_status()
data = res.json()
self.refresh, self.access = data['refresh'], data['access']