drf☞jwt自動簽發與手動簽發

lxttt521發表於2020-07-15

一、自動簽發

urls

from rest_framework_jwt.views import obtain_jwt_token
# 使用jwt自帶的登入檢視
urlpatterns = [
    path('login/', obtain_jwt_token),
]

settings

import datetime
JWT_AUTH={
    # 配置響應格式,必須和自動簽發使用
  'JWT_RESPONSE_PAYLOAD_HANDLER':'app01.utils.my_jwt_response_payload_handler',
    'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7), # 過期時間,手動配置
}

utils

# 重寫jwt響應格式(需要到settings配置)
# 與之配合使用的必須是自動簽發
def my_jwt_response_payload_handler(token, user=None, request=None): # 返回什麼,前端就能看到什麼樣子
    return {
        'token': token,
        'msg':'登入成功',
        'status':100,
        'username':user.username

    }

然後直接在前端提交post請求傳送賬號和密碼,會返回我們定義好的響應格式

{
    "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6Imh6IiwiZXhwIjoxNTk1NDE1MTEyLCJlbWFpbCI6IiJ9.BxBdsm6oBz8iPAwSSpo_7IaU4pBp6RjK4c0GJ_FYN1E",
    "msg": "登入成功",
    "status": 100,
    "username": "hz"
}

然後拿出token對測試類傳送測試請求

class TestAPI(APIView):
    def get(self,request):
        print(request.user)
        return Response("ok")
# 因為內建的他沒有對匿名使用者設定攔截,素以匿名使用者也能看到ok
# 我們用request.user來區分
# 這裡可能會出現我登入了很多次,用每次不同的token都能登入
# 這是因為token校驗的是規則,是要加密規則符合且沒有超時,那用哪次token都一樣的

二、手動簽發

utils

from rest_framework_jwt.authentication import BaseJSONWebTokenAuthentication,jwt_decode_handler
import jwt
from rest_framework.exceptions import AuthenticationFailed
class MyAuthentication(BaseJSONWebTokenAuthentication):
    # 這裡重寫的邏輯和BaseJSONWebTokenAuthentication裡的authenticate一模一樣
    def authenticate(self, request):
        jwt_token = request.META.get('HTTP_AUTHORIZATION') # 獲取瀏覽器傳來的token
        if jwt_token:
            try:
                payload = jwt_decode_handler(jwt_token) # 傳入token,拿出第二段使用者資訊,有內建的校驗token功能
            except jwt.ExpiredSignature:
                raise AuthenticationFailed('簽名過期')
            except jwt.InvalidTokenError:
                raise AuthenticationFailed('使用者非法')
            except Exception as e:
                # 所有異常都會走到這
                raise AuthenticationFailed(str(e))
            # 通過內建的方法把payload轉換成使用者物件
            user = self.authenticate_credentials(payload)
            return user,None # ===》request.user,request.auth
        raise AuthenticationFailed('您沒有攜帶認證資訊')

sers

from rest_framework import serializers


# 多方序列化校驗登入
import re
from rest_framework.exceptions import ValidationError
from app01 import models
from rest_framework_jwt.utils import jwt_encode_handler,jwt_payload_handler
class LoginSer(serializers.ModelSerializer):
    # 我們要提交校驗資料的時候,如果直接用下面Meta繫結給模型類的話
    # 關鍵點2:這裡如果不寫username的話,序列化器直接用的是模型類的username
    # 這兩者的區別在於,如果覆蓋寫了username,他表示的可以是任何前端傳來的資料,如果是模型類繫結,那隻能是使用者名稱了
    # 我們這裡username用於多方登入的校驗資料,必須要重寫
    # 而password不用重寫,因為password用的就是模型類本身的
    username = serializers.CharField()
    class Meta:
        model = models.User
        fields =['username','password']
    def validate(self, attrs):
        username = attrs.get('username')
        password = attrs.get('password')
        if username:
            if re.match('^1[3-9][0-9]{9}$', username):
                user = models.User.objects.filter(mobile=username).first()
            elif re.match('^.+@.+$', username):  # 郵箱
                user = models.User.objects.filter(email=username).first()
            else:
                user = models.User.objects.filter(username=username).first()
            if user:
                if user.check_password(password):
                    # 關鍵點3:jwt_payload_handler把使用者資料物件轉化成使用者資訊的字典
                    # jwt_encode_handler把使用者資訊的字典轉化成token
                    payload = jwt_payload_handler(user)
                    # print('user:',user,type(user))
                    token = jwt_encode_handler(payload)
                    # print('payload:',payload,type(payload))
                    # print('token:',token)
                    # 關鍵點4:如果我們要給序列化器新增資料,讓檢視函式去使用
                    # 通常都是傳給物件的context屬性,當然直接賦值也可以,這只是他給我們提供的傳值介面
                    self.context['token'] = token
                    self.context['user'] = user
                    self.user = user
                    return attrs
                else:
                    raise ValidationError('密碼錯誤')
            raise ValidationError('不存在使用者')
        raise ValidationError('請輸入使用者名稱')


views

class LoginApi(ViewSet):
    authentication_classes = []
    def login(self, request):
        # 在呼叫序列化類給context傳資料,可以直接在序列化類中呼叫
        # 關鍵點1:注意區分序列化傳值與反序列化
        # 這裡只要拿字典取校驗資料,那就傳給data
        # 如果是要把資料物件轉化成字典就傳給instance
        user_ser = sers.LoginSer(data=request.data, context={'request': request})
        user_ser.is_valid(raise_exception=True)
        token = user_ser.context.get('token')
        user = user_ser.context.get('user')
        print(user_ser.user)
        return Response({'code': 100, 'msg': '登入成功', 'token': token, 'username': user.username})

相關文章