zoukankan      html  css  js  c++  java
  • Django REST framework学习之JWT失效方式

    前因

    项目通过JWT 来实现用户的验证,在注销和异设备登入或密码修改的时候都需要让旧的JWT 失效,但是 DRF JWT 没有内置失效方法,官方推荐通过设置“JWT_GET_USER_SECRET_KEY” 为一个使每次SECRET_KEY 不相同的方法,从而使每次生成的Token 都不一样。

    后果

    具体方式如下:


    1.首先修改用户模型类users.models.py 添加user_secret 字段,如下:

     1 from django.db import models
     2 from django.contrib.auth.models import AbstractUser
     3 from uuid import uuid4
     4 
     5 class User(AbstractUser):
     6   """用户模型类"""
     7   user_secret = models.UUIDField(default=uuid4(), verbose_name='用户JWT秘钥')
     8 
     9   class Meta:
    10     db_table = 'tb_users'
    11     verbose_name = '用户'
    12     verbose_name_plural = verbose_name

    2.并在项目的settings 中指定使用该模型类,如下:

    1 # Custom Model
    2 AUTH_USER_MODEL = 'users.User'

    3.终端执行迁移命令

    python manage.py makemigrations
    python manage.py migrate

    4.在utils.users.py 中定义获取user_secret 的方法,如:

    1 def jwt_get_user_secret(user):
    2 
    3   return user.user_secret

    5.在项目的settings 的JWT_AUTH 里添加一个属性 'JWT_GET_USER_SECRET_KEY'

    1 # JWT_AUTH settings
    2 JWT_AUTH = {
    3   # JWT expiration time one day
    4   'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1),
    5   # Custom Return
    6   'JWT_RESPONSE_PAYLOAD_HANDLER': 'utils.users.jwt_response_payload_handler',
    7   # Custom Get User SECRET
    8   'JWT_GET_USER_SECRET_KEY': 'utils.users.jwt_get_user_secret'
    9 }

    构思
    保证一个用户登录的的业务逻辑,就是每次登录的时候都会对token 进行校验,通过就给该用户一个user_jwt 的属性并且在每个请求的时候都去判断请求是否携带合法token ,且该token是否和user.user_jwt 相等,如果不相等,说明有异设备登录,更改了user_jwt,此时根据需求,需要两个用户都重新登录,则重新生成user_secret,让之前的JWT 都失效,从而保证用户只有一个人在线上。同理用户注销或者修改密码的时候,也重新生成一个新的user_secret,这样就能保证旧的JWT 在这三种情况下失效。


    6.使用中间件来实现,在项目的settings 里“MIDDLEWARE” 添加一个中间件类,用于每次请求和登录请求的逻辑扩展,如:

    1 # MIDDLEWARE_CALSSES = [           # Django 1.4.x ---- 1.9.x
    2 MIDDLEWARE = [                     # Django 1.11.11
    3        ...,
    4     'utils.check_token_middleware.CheckTokenMiddleware',
    5 
    6 ]

    7.1 utils.check_token_middleware.py (Django 1.4.x ---- Django 1.9.x)

     1 from uuid import uuid4
     2 from django.http import HttpResponse
     3 from django.utils.deprecation import MiddlewareMixin
     4 from jwt import InvalidSignatureError
     5 from rest_framework.exceptions import ValidationError
     6 from rest_framework_jwt.serializers import VerifyJSONWebTokenSerializer
     7 
     8 class CheckTokenMiddleware(MiddlewareMixin):
     9   """
    10   Django 1.4.x ---- Django 1.9.x
    11   每次请求时 判断 JWT 是否与 User.user_jwt 相等
    12   相等的话,说明没有以设备登录,且没有修改密码
    13   不相等,则说明异常设备登录,或修改了密码,修改用户的uuid并提示用户重新登录
    14   每次登录时记录更新JWT 为User 的一个属性user_jwt
    15   每次修改密码时 更新修改uuid 
    16   """
    17   def process_request(self, request):
    18     # 处理所有带JWT 的请求
    19     jwt_token = request.META.get('Authorization', None)
    20     if jwt_token is not None and jwt_token != '':
    21     data = {
    22     'token': jwt_token.split(' ')[1], # [0] 是前缀,默认为JWT
    23      }
    24     try:
    25       valid_data = VerifyJSONWebTokenSerializer().validate(data)
    26       user = valid_data['user']
    27     except (InvalidSignatureError, ValidationError):
    28       # 找不到用户,说明token 不合法或者身份过期
    29       return HttpResponse({'msg': '身份已经过期,请重新登入'}, content_type='application/json', status=400)
    30     else:
    31       # 说明进行了第二次登录, user.user_jwt 已经被重新赋值,需要更换签名。注意,此种方法将使无论是第一次登录还是第二次登录的人的 验证信息都失效,从而保证只有一个人在线上
    32       if user.user_jwt != data['token']:
    33       user.user_secret = uuid4()
    34       user.save()
    35       return HttpResponse({'msg': '异设备登录,请重新登入或修改密码'}, content_type='application/json', status=400)
    36     return None
    37 
    38   def process_response(self, request, response):
    39     # 处理login 请求
    40     if request.META['PATH_INFO'] == '/users/auths/':
    41     # 因为登录认证ObtainJSONWebToken 继承自JSONWebTokenAPIView,所以是Response对象,不是HttpResponse对象,所以使用response.data,而不是response.content
    42     rep_data = response.data
    43     # 默认response.data 里面必有token ,根据序列化器VerifyJSONWebTokenSerializer()返回token和user
    44     valid_data = VerifyJSONWebTokenSerializer().validate(rep_data)
    45     user = valid_data['user']
    46     user.user_jwt = rep_data['token']
    47     user.save()
    48     return response

    7.2 utils.check_token_middleware.py (Django 1.11.11)

     1 from uuid import uuid4
     2 from django.http import HttpResponse
     3 from jwt import InvalidSignatureError
     4 from rest_framework.exceptions import ValidationError
     5 from rest_framework_jwt.serializers import VerifyJSONWebTokenSerializer
     6 
     7 class CheckTokenMiddleware(object):
     8   """
     9   Django 1.11.11
    10   每次请求时 判断 JWT 是否与 User.user_jwt 相等
    11   相等的话,说明没有以设备登录,且没有修改密码
    12   不相等,则说明异常设备登录,或修改了密码,修改用户的uuid并提示用户重新登录
    13 
    14   每次登录时记录更新JWT 为User 的一个属性user_jwt
    15   每次修改密码时 更新修改uuid 并记录更新JWT 为User 的一个属性user_jwt
    16   """
    17   def __init__(self, get_response):
    18     # 第一次请求初始化和配置
    19     self.get_response = get_response
    20 
    21   def __call__(self, request):
    22     # 请求前被调用
    23     # 处理所有带JWT 的请求
    24     jwt_token = request.META.get('Authorization', None)
    25     if jwt_token is not None and jwt_token != '':
    26       data = {
    27       'token': jwt_token.split(' ')[1], # [0] 是前缀,默认为JWT
    28       }
    29       try:
    30         valid_data = VerifyJSONWebTokenSerializer().validate(data)
    31         user = valid_data['user']
    32       except (InvalidSignatureError, ValidationError):
    33         # 找不到用户,说明token 不合法或者身份过期
    34         return HttpResponse({'msg': '身份已经过期,请重新登入'}, content_type='application/json', status=400)
    35       else:
    36         # 说明进行了第二次登录, user.user_jwt 已经被重新赋值,需要更换签名
    37         if user.user_jwt != data['token']:
    38           user.user_secret = uuid4()
    39           user.save()
    40           return HttpResponse({'msg': '异设备登录,请重新登入或修改密码'}, content_type='application/json', status=400)
    41 
    42        response = self.get_response(request)
    43        # 请求后被调用
    44        # 处理login 请求
    45        if request.META['PATH_INFO'] == '/users/auths/':     
    46         # 因为登录认证ObtainJSONWebToken 继承自JSONWebTokenAPIView,所以是Response对象,不是HttpResponse对象
    47         # 所以使用response.data,而不是response.content
    48         rep_data = response.data
    49         # 默认response.data 里面必有token ,根据序列化器VerifyJSONWebTokenSerializer()返回token和user
    50         valid_data = VerifyJSONWebTokenSerializer().validate(rep_data)
    51         user = valid_data['user']
    52         user.user_jwt = rep_data['token']
    53         user.save()
    54         return response

    8.在注销用户和修改密码的业务逻辑后面添加:

      # 注销用户
      user = request.user
      user.user_secret = uuid4()
      user.save()

      # 修改密码
      user.user_secret = uuid4()
      user.save()


    9.测试

  • 相关阅读:
    验证车牌警车能源车
    两个二维数组组合并且日期相同的其他值相加
    运用phpExcel导出查询的数据
    JQ加减乘除运算
    获取url传递的参数、鼠标滚动加载事件
    SQL查询将数据按字段(2017-08-15 11:34:05)进行按月分组
    关于角色和权限控制数据表相关
    有关无限极分类并排序问题
    前端解决用户登录时,禁止chrome提示用户保存密码
    前端学习之iframe配合hash做菜单导航的问题:修改src跳转与contentWindow.location.replace('')的问题
  • 原文地址:https://www.cnblogs.com/yungiu/p/10255444.html
Copyright © 2011-2022 走看看