允许 JWT 令牌过期,前提是用户来自受信任的 IP 地址
Allow JWT Tokens if Expired, Provided User is from Trusted IP address
使用 flask-jwt-extended,我的情况是 API 必须同时为用户和一系列网络应用程序(例如,后者之一是聊天机器人)提供服务。
对于用户来说,开箱即用的包功能非常完美,但是,对于 Web 应用程序,我希望 JWT 令牌的行为更像 API-keys,而它们不是一定会在一段时间后过期。
所以我想做的是,如果请求来自预定义且受信任的 IP 地址,则抑制对 'expiry' 的检查。
我有一个存储可信'ip addresses'的sqlalchemy模型,它与用户模型有外键关系,这意味着用户可以指定一个(或多个)白名单IP地址。
现在 decode_token
函数:
有一个参数 allow_expired
,它允许覆盖到期时间,但是,在 _decode_jwt_from_request(...)
函数中没有以任何方式使用它,这似乎在验证 JWT 令牌时很有帮助。
最终,我在 @jwt_required
的装饰器替换之后允许使用过期的令牌,前提是请求来自列入白名单的 IP 地址。
我的问题有两个:
- 从安全的角度来看,上述结构是否可以?,并且,
- 无需复制(并稍微修改)库中的整个函数,我该如何处理上述内容?
除非有人告诉我更好的方法,否则我最终会猴子修补 decode_token
函数:
我已经突出显示了'patched'区域,它拦截了'ExpiredSignatureError',并检查该ip地址是否在用户ip-whitelist中,如果是,则允许照常进行。
def decode_token(encoded_token, csrf_value=None, allow_expired=False):
"""
Returns the decoded token (python dict) from an encoded JWT. This does all
the checks to insure that the decoded token is valid before returning it.
:param encoded_token: The encoded JWT to decode into a python dict.
:param csrf_value: Expected CSRF double submit value (optional)
:param allow_expired: Options to ignore exp claim validation in token
:return: Dictionary containing contents of the JWT
"""
jwt_manager = _get_jwt_manager()
unverified_claims = jwt.decode(
encoded_token, verify=False, algorithms=config.decode_algorithms
)
unverified_headers = jwt.get_unverified_header(encoded_token)
# Attempt to call callback with both claims and headers, but fallback to just claims
# for backwards compatibility
try:
secret = jwt_manager._decode_key_callback(unverified_claims, unverified_headers)
except TypeError:
msg = (
"The single-argument (unverified_claims) form of decode_key_callback ",
"is deprecated. Update your code to use the two-argument form ",
"(unverified_claims, unverified_headers)."
)
warn(msg, DeprecationWarning)
secret = jwt_manager._decode_key_callback(unverified_claims)
try:
return decode_jwt(
encoded_token=encoded_token,
secret=secret,
algorithms=config.decode_algorithms,
identity_claim_key=config.identity_claim_key,
user_claims_key=config.user_claims_key,
csrf_value=csrf_value,
audience=config.audience,
issuer=config.issuer,
leeway=config.leeway,
allow_expired=allow_expired
)
except ExpiredSignatureError:
expired_token = decode_jwt(
encoded_token=encoded_token,
secret=secret,
algorithms=config.decode_algorithms,
identity_claim_key=config.identity_claim_key,
user_claims_key=config.user_claims_key,
csrf_value=csrf_value,
audience=config.audience,
issuer=config.issuer,
leeway=config.leeway,
allow_expired=True
)
# ------------------------------------------------------------
# Author: Nicholas E. Hamilton
# Date: 25th August 2019
# Patch: Check if ip address is in the whitelist,
# and if so, permit an expired token
# ------------------------------------------------------------
user = user_loader(expired_token[config.identity_claim_key])
ip_address = request.remote_addr
if user and ip_address:
ip_whitelist = [x.ip_address for x in user.ip_whitelist]
if ip_address in ip_whitelist:
return expired_token
# >>>> END PATCH
# Proceed as normal
ctx_stack.top.expired_jwt = expired_token
raise
flask_jwt_extended.view_decorators.decode_token = flask_jwt_extended.utils.decode_token = decode_token
使用 flask-jwt-extended,我的情况是 API 必须同时为用户和一系列网络应用程序(例如,后者之一是聊天机器人)提供服务。
对于用户来说,开箱即用的包功能非常完美,但是,对于 Web 应用程序,我希望 JWT 令牌的行为更像 API-keys,而它们不是一定会在一段时间后过期。
所以我想做的是,如果请求来自预定义且受信任的 IP 地址,则抑制对 'expiry' 的检查。
我有一个存储可信'ip addresses'的sqlalchemy模型,它与用户模型有外键关系,这意味着用户可以指定一个(或多个)白名单IP地址。
现在 decode_token
函数:
有一个参数 allow_expired
,它允许覆盖到期时间,但是,在 _decode_jwt_from_request(...)
函数中没有以任何方式使用它,这似乎在验证 JWT 令牌时很有帮助。
最终,我在 @jwt_required
的装饰器替换之后允许使用过期的令牌,前提是请求来自列入白名单的 IP 地址。
我的问题有两个:
- 从安全的角度来看,上述结构是否可以?,并且,
- 无需复制(并稍微修改)库中的整个函数,我该如何处理上述内容?
除非有人告诉我更好的方法,否则我最终会猴子修补 decode_token
函数:
我已经突出显示了'patched'区域,它拦截了'ExpiredSignatureError',并检查该ip地址是否在用户ip-whitelist中,如果是,则允许照常进行。
def decode_token(encoded_token, csrf_value=None, allow_expired=False):
"""
Returns the decoded token (python dict) from an encoded JWT. This does all
the checks to insure that the decoded token is valid before returning it.
:param encoded_token: The encoded JWT to decode into a python dict.
:param csrf_value: Expected CSRF double submit value (optional)
:param allow_expired: Options to ignore exp claim validation in token
:return: Dictionary containing contents of the JWT
"""
jwt_manager = _get_jwt_manager()
unverified_claims = jwt.decode(
encoded_token, verify=False, algorithms=config.decode_algorithms
)
unverified_headers = jwt.get_unverified_header(encoded_token)
# Attempt to call callback with both claims and headers, but fallback to just claims
# for backwards compatibility
try:
secret = jwt_manager._decode_key_callback(unverified_claims, unverified_headers)
except TypeError:
msg = (
"The single-argument (unverified_claims) form of decode_key_callback ",
"is deprecated. Update your code to use the two-argument form ",
"(unverified_claims, unverified_headers)."
)
warn(msg, DeprecationWarning)
secret = jwt_manager._decode_key_callback(unverified_claims)
try:
return decode_jwt(
encoded_token=encoded_token,
secret=secret,
algorithms=config.decode_algorithms,
identity_claim_key=config.identity_claim_key,
user_claims_key=config.user_claims_key,
csrf_value=csrf_value,
audience=config.audience,
issuer=config.issuer,
leeway=config.leeway,
allow_expired=allow_expired
)
except ExpiredSignatureError:
expired_token = decode_jwt(
encoded_token=encoded_token,
secret=secret,
algorithms=config.decode_algorithms,
identity_claim_key=config.identity_claim_key,
user_claims_key=config.user_claims_key,
csrf_value=csrf_value,
audience=config.audience,
issuer=config.issuer,
leeway=config.leeway,
allow_expired=True
)
# ------------------------------------------------------------
# Author: Nicholas E. Hamilton
# Date: 25th August 2019
# Patch: Check if ip address is in the whitelist,
# and if so, permit an expired token
# ------------------------------------------------------------
user = user_loader(expired_token[config.identity_claim_key])
ip_address = request.remote_addr
if user and ip_address:
ip_whitelist = [x.ip_address for x in user.ip_whitelist]
if ip_address in ip_whitelist:
return expired_token
# >>>> END PATCH
# Proceed as normal
ctx_stack.top.expired_jwt = expired_token
raise
flask_jwt_extended.view_decorators.decode_token = flask_jwt_extended.utils.decode_token = decode_token