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.測試

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM