【问题标题】:How to logout user when he changes password from all browsers (Django-rest-auth, JWT)?当他从所有浏览器(Django-rest-auth,JWT)更改密码时如何注销用户?
【发布时间】:2019-09-03 11:32:45
【问题描述】:

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

我正在与django-rest-authdjango-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)

在阅读了一些文档和帖子之后,似乎只有jwt 并不容易,因为它是无状态的,但是有人可以指出我的方向吗?

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

有什么解决方法可以帮助我解决这个问题吗?

非常感谢。


编辑:

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

当用户更改令牌时使令牌失效的常用方法 密码是用他们密码的哈希值对令牌进行签名。因此,如果 密码更改,任何以前的令牌都会自动失败 核实。您可以通过包含 last-logout-time 将其扩展到注销 在用户的记录中并使用 last-logout-time 的组合 和密码哈希来签署令牌。这需要每个数据库查找 您需要验证令牌签名的时间,但大概您是 无论如何都要查找用户

我正在尝试实现它..如果它有效,我将更新我的帖子。 否则,我仍然愿意接受建议。

【问题讨论】:

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


    【解决方案1】:

    嗯,

    这都是关于令牌到期时间的 - 如果您保持这个时间很短(例如 10-15 分钟) - 当密码或某些权限发生更改时,您不必费心使其失效。令牌总是会在一段时间后失效,并会发行一个新的。

    如果您使用 JWT 作为长寿令牌(这不是好的做法) - 您会遇到问题。

    因为更改密码和使其他令牌(不同会话)无效(或强制重新创建)等操作需要存储在其他地方(如某些 NoSQL 存储) - 并检查每个会话是否需要一些特殊操作 - 然后你正在失去 JWT 的无状态优势。

    【讨论】:

    • 感谢您的回复,我应该用什么代替?我应该删除 JWT 的身份验证吗?就我而言,我的前端和后端是分开的:前面是angular,后面是python/django 谢谢
    • 好吧,在前端和后端实现更新短令牌周期 :) 否则 - 允许您的会话存储一些状态 - 或将 JWT 令牌存储在某处并在密码更改时将它们列入黑名单。
    【解决方案2】:

    经过几天的工作,我最终覆盖了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)然后注销用户 我希望它对未来的人有所帮助。

    【讨论】:

      猜你喜欢
      • 2016-05-15
      • 2017-06-17
      • 1970-01-01
      • 1970-01-01
      • 2017-06-05
      • 1970-01-01
      • 1970-01-01
      • 2016-10-30
      • 2017-05-02
      相关资源
      最近更新 更多