当用户从所有浏览器(Django-rest-auth,JWT)更改密码时,如何注销用户?

时间:2019-04-12 16:33:08

标签: django-rest-framework jwt django-rest-auth django-rest-framework-jwt

首先,我是django-rest-framework的新手,如果我错了,请原谅。

我正在与django-rest-authdjango-restframework-jwt进行身份验证。每当用户登录时,我都会将jwt令牌保存在localStorage中。

我现在面临的问题是,当我在两个浏览器中使用相同的凭据登录,然后在其中一个浏览器中更改密码时,另一个帐户仍然有效,即使浏览器仍可以浏览并查看所有页面,密码已更改。

我想让他的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)

在阅读了一些文档和文章之后,似乎仅凭jwt来说这是不容易的,因为它是无状态的,但是有人可以向我指出前进的方向吗?

我应该删除JWT身份验证吗?

有没有可以解决这个问题的方法?

非常感谢。


编辑:

我在@Travis的a similar post on SO中发现了一条评论,指出

  

当用户更改其令牌时使令牌失效的一种通用方法   密码是使用密码的哈希值对令牌进行签名。因此,如果   密码更改后,以前的所有令牌都会自动失败   校验。您可以通过添加上次注销时间来将其扩展到注销   在用户记录中,并结合了上次注销时间   和密码散列来对令牌进行签名。这需要每个数据库查找   您需要验证令牌签名的时间,但大概是   仍在查找用户

我正在尝试实现该功能。 否则,我仍然愿意提出建议。

2 个答案:

答案 0 :(得分:0)

好吧

这全都与令牌到期时间有关-如果您保持较短的时间(例如10至15分钟),那么当密码或某些权限会发生变化时,您就不必为使其无效而烦恼。令牌将始终在短时间后失效,并会发行新的令牌。

如果您将JWT用作长期令牌(这不是一个好习惯),则会遇到问题。

因为诸如更改密码和使其他令牌无效(不同的会话)(或强制重新创建)之类的操作需要存储在其他位置(如NoSQL存储),并检查每个会话是否需要一些特殊的操作-然后您正在失去JWT的无国籍优势。

答案 1 :(得分:0)

经过几天的工作,我最终通过覆盖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

要注销用户,我已经从前端读取了请求“在这种情况下为403”的状态代码:(我使用的是Angular),然后注销了该用户 我希望它对以后的人有所帮助。