当他从所有浏览器(Django-rest-auth,JWT)更改密码时如何注销用户?
How to logout user when he changes password from all browsers (Django-rest-auth, JWT)?
首先,我是django-rest-framework
的新手,所以如果我错了,请原谅。
我正在使用 django-rest-auth
和 django-restframework-jwt
对用户进行身份验证。每次用户登录时,我都会在 localStorage 中保存 jwt
令牌。
我现在面临的问题是,当我在两个浏览器中使用相同的凭据登录,然后在其中一个浏览器中更改密码时,另一个帐户仍然有效,用户仍然可以导航并查看所有页面,甚至虽然密码变了。
我想在他更改密码时使他的 JWT
令牌无效,以便他将自动注销。但是我在 official documentation of Django REST framework JWT
中找不到让他的令牌过期的方法
我试图通过为用户手动生成新的 JWT 令牌来跟踪更改密码的时刻,但这不起作用(可能是因为现有令牌仍然有效)
@receiver(signals.pre_save, sender=User)
def revoke_tokens(sender, instance, **kwargs):
existing_user = User.objects.get(pk=instance.pk)
if getattr(settings, 'REST_USE_JWT', False):
if instance.password != existing_user.password:
# If user has changed his password, generate manually a new token for him
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
payload = jwt_payload_handler(instance)
payload['orig_iat'] = timegm(datetime.utcnow().utctimetuple())
instance.token = jwt_encode_handler(payload)
在阅读了一些文档和 posts 之后,似乎只使用 jwt
并不容易,因为它是无状态的,但是有人可以指出我去哪里的方向吗?
我应该删除 JWT
身份验证吗?
是否有解决方法可以帮助我解决这个问题?
非常感谢。
编辑:
我在@Travis 的 a similar post on SO 中发现了一条评论,指出
A common approach for invalidating tokens when a user changes their
password is to sign the token with a hash of their password. Thus if
the password changes, any previous tokens automatically fail to
verify. You can extend this to logout by including a last-logout-time
in the user's record and using a combination of the last-logout-time
and password hash to sign the token. This requires a DB lookup each
time you need to verify the token signature, but presumably you're
looking up the user anyway
我正在尝试实现它..如果它有效,我会更新我的 post。
否则,我仍然愿意接受建议。
嗯,
一切都与令牌过期时间有关 - 如果您保持这个时间较短(例如 10-15 分钟)- 当密码或某些权限发生变化时,您不必费心使它失效。令牌总是会在一段时间后失效,并会发行一个新的令牌。
如果您使用 JWT 作为长期有效的令牌(这不是好的做法)- 您会遇到问题。
因为像更改密码和使其他令牌(不同会话)(或强制重新创建)无效这样的操作需要存储在其他地方(比如一些 NoSQL 存储)——并检查每个会话是否需要一些特殊操作——并且那么你就失去了 JWT 的无状态优势。
经过几天的工作,我最终覆盖了 JWT_PAYLOAD_HANDLER
并在 JWT
令牌的有效负载中添加了用户密码哈希的最后几位(因为添加了所有密码哈希在有效载荷中不是一个好习惯)
然后创建一个拦截所有请求的 custom middleware
。
在每个请求中,我从 jwt token
检查密码的散列值是否与现有用户的散列值匹配(如果不匹配,则表示用户已更改其密码)
如果它们不同,那么我会引发错误并使用旧密码哈希注销用户。
在配置文件中:
'JWT_PAYLOAD_HANDLER': 'your.path.jwt.jwt_payload_handler',
并且在配置文件中声明的根目录中:
def jwt_payload_handler(user):
username_field = get_username_field()
username = get_username(user)
payload = {
'user_id': user.pk,
'username': username,
'pwd': user.password[-10:],
'exp': datetime.utcnow() + api_settings.JWT_EXPIRATION_DELTA
}
if hasattr(user, 'email'):
payload['email'] = user.email
if isinstance(user.pk, uuid.UUID):
payload['user_id'] = str(user.pk)
payload[username_field] = username
return payload
然后这是自定义中间件:
from django.http.response import HttpResponseForbidden
from django.utils.deprecation import MiddlewareMixin
from rest_framework_jwt.utils import jwt_decode_handler
from config.settings.base import JWT_AUTH
from trp.users.models import User
class JWTAuthenticationMiddleware(MiddlewareMixin):
def process_request(self, request):
jwt_user_pwd = self.get_jwt_user_pwd(request)
# check if last digits of password read from jwt token matches the hash of the current user in DB
if jwt_user_pwd is not None:
if jwt_user_pwd['pwd'] != jwt_user_pwd['user'].password[-10:]:
return HttpResponseForbidden()
@staticmethod
def get_jwt_user_pwd(request):
token = request.META.get('HTTP_AUTHORIZATION', None)
# Remove the prefix from token name so that decoding the token gives us correct credentials
token = str(token).replace(JWT_AUTH['JWT_AUTH_HEADER_PREFIX'] + ' ', '')
if token:
try:
payload = jwt_decode_handler(token)
authenticated_user = User.objects.get(id=payload['user_id'])
except Exception as e:
authenticated_user = None
payload = {}
if authenticated_user and payload:
return {'user': authenticated_user, 'pwd': payload.get('pwd')}
return None
要注销用户,我已经从前端读取了请求 'in this case 403' 的状态代码:(在我的例子中我使用的是 Angular
),然后注销用户
我希望它能帮助将来的人。
首先,我是django-rest-framework
的新手,所以如果我错了,请原谅。
我正在使用 django-rest-auth
和 django-restframework-jwt
对用户进行身份验证。每次用户登录时,我都会在 localStorage 中保存 jwt
令牌。
我现在面临的问题是,当我在两个浏览器中使用相同的凭据登录,然后在其中一个浏览器中更改密码时,另一个帐户仍然有效,用户仍然可以导航并查看所有页面,甚至虽然密码变了。
我想在他更改密码时使他的 JWT
令牌无效,以便他将自动注销。但是我在 official documentation of Django REST framework JWT
我试图通过为用户手动生成新的 JWT 令牌来跟踪更改密码的时刻,但这不起作用(可能是因为现有令牌仍然有效)
@receiver(signals.pre_save, sender=User)
def revoke_tokens(sender, instance, **kwargs):
existing_user = User.objects.get(pk=instance.pk)
if getattr(settings, 'REST_USE_JWT', False):
if instance.password != existing_user.password:
# If user has changed his password, generate manually a new token for him
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
payload = jwt_payload_handler(instance)
payload['orig_iat'] = timegm(datetime.utcnow().utctimetuple())
instance.token = jwt_encode_handler(payload)
在阅读了一些文档和 posts 之后,似乎只使用 jwt
并不容易,因为它是无状态的,但是有人可以指出我去哪里的方向吗?
我应该删除 JWT
身份验证吗?
是否有解决方法可以帮助我解决这个问题?
非常感谢。
编辑:
我在@Travis 的 a similar post on SO 中发现了一条评论,指出
A common approach for invalidating tokens when a user changes their password is to sign the token with a hash of their password. Thus if the password changes, any previous tokens automatically fail to verify. You can extend this to logout by including a last-logout-time in the user's record and using a combination of the last-logout-time and password hash to sign the token. This requires a DB lookup each time you need to verify the token signature, but presumably you're looking up the user anyway
我正在尝试实现它..如果它有效,我会更新我的 post。 否则,我仍然愿意接受建议。
嗯,
一切都与令牌过期时间有关 - 如果您保持这个时间较短(例如 10-15 分钟)- 当密码或某些权限发生变化时,您不必费心使它失效。令牌总是会在一段时间后失效,并会发行一个新的令牌。
如果您使用 JWT 作为长期有效的令牌(这不是好的做法)- 您会遇到问题。
因为像更改密码和使其他令牌(不同会话)(或强制重新创建)无效这样的操作需要存储在其他地方(比如一些 NoSQL 存储)——并检查每个会话是否需要一些特殊操作——并且那么你就失去了 JWT 的无状态优势。
经过几天的工作,我最终覆盖了 JWT_PAYLOAD_HANDLER
并在 JWT
令牌的有效负载中添加了用户密码哈希的最后几位(因为添加了所有密码哈希在有效载荷中不是一个好习惯)
然后创建一个拦截所有请求的 custom middleware
。
在每个请求中,我从 jwt token
检查密码的散列值是否与现有用户的散列值匹配(如果不匹配,则表示用户已更改其密码)
如果它们不同,那么我会引发错误并使用旧密码哈希注销用户。
在配置文件中:
'JWT_PAYLOAD_HANDLER': 'your.path.jwt.jwt_payload_handler',
并且在配置文件中声明的根目录中:
def jwt_payload_handler(user):
username_field = get_username_field()
username = get_username(user)
payload = {
'user_id': user.pk,
'username': username,
'pwd': user.password[-10:],
'exp': datetime.utcnow() + api_settings.JWT_EXPIRATION_DELTA
}
if hasattr(user, 'email'):
payload['email'] = user.email
if isinstance(user.pk, uuid.UUID):
payload['user_id'] = str(user.pk)
payload[username_field] = username
return payload
然后这是自定义中间件:
from django.http.response import HttpResponseForbidden
from django.utils.deprecation import MiddlewareMixin
from rest_framework_jwt.utils import jwt_decode_handler
from config.settings.base import JWT_AUTH
from trp.users.models import User
class JWTAuthenticationMiddleware(MiddlewareMixin):
def process_request(self, request):
jwt_user_pwd = self.get_jwt_user_pwd(request)
# check if last digits of password read from jwt token matches the hash of the current user in DB
if jwt_user_pwd is not None:
if jwt_user_pwd['pwd'] != jwt_user_pwd['user'].password[-10:]:
return HttpResponseForbidden()
@staticmethod
def get_jwt_user_pwd(request):
token = request.META.get('HTTP_AUTHORIZATION', None)
# Remove the prefix from token name so that decoding the token gives us correct credentials
token = str(token).replace(JWT_AUTH['JWT_AUTH_HEADER_PREFIX'] + ' ', '')
if token:
try:
payload = jwt_decode_handler(token)
authenticated_user = User.objects.get(id=payload['user_id'])
except Exception as e:
authenticated_user = None
payload = {}
if authenticated_user and payload:
return {'user': authenticated_user, 'pwd': payload.get('pwd')}
return None
要注销用户,我已经从前端读取了请求 'in this case 403' 的状态代码:(在我的例子中我使用的是 Angular
),然后注销用户
我希望它能帮助将来的人。