simple-jwt的簡單使用

HuangQiaoqi發表於2024-05-04

【一】安裝

pip install djangorestframework-simplejwt

【二】配置

# settings.py
INSTALLED_APPS = [
	...
    'rest_framework',               # add
    'rest_framework_simplejwt',     # add
]

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated',
    ),
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_simplejwt.authentication.JWTAuthentication',
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.BasicAuthentication',
    ),
}
SIMPLE_JWT = {
    # token有效時長(返回的 access 有效時長)
    'ACCESS_TOKEN_LIFETIME': datetime.timedelta(seconds=30),
    # token重新整理的有效時間(返回的 refresh 有效時長)
    'REFRESH_TOKEN_LIFETIME': datetime.timedelta(seconds=20),
}

【三】配置路由

# urls.py
urlpatterns = [
    # 登入
    path('login/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
    path('refresh/', TokenRefreshView.as_view(), name='token_refresh'),
    path('verify/', TokenVerifyView.as_view(), name='token_verify'),
]

【四】配置認證類

# 匯入這兩個類
from rest_framework_simplejwt.authentication import JWTAuthentication
from rest_framework.permissions import IsAuthenticated


class CarView(ModelViewSet):
    serializer_class = CarSerializer
    queryset = CarModel.objects.all()
    # 認證類和許可權類需要搭配使用
    authentication_classes = [JWTAuthentication]
    permission_classes = [IsAuthenticated]

【五】內建user表

​ 如果用內建的user表的話,此時直接以 path('login/', TokenObtainPairView.as_view(), name='token_obtain_pair'),登入就會簽發token,

​ 需要在請求頭加上Authorization,格式:Bearer [token值] 有空格

【六】自定義返回內容

​ 如果需要自定義登入後返回的訊息的話,就需要進行如下配置

【1】序列化類

繼承TokenObtainPairSerializer

重寫validate方法

定製data返回,即為返回訊息

from rest_framework_simplejwt.serializers import TokenObtainPairSerializer

# 序列化類 繼承TokenObtainPairSerialize
class AutoLoginSerializer(TokenObtainPairSerializer):
    # 重寫父類的全域性鉤子
    def validate(self, attrs):
        dic = super().validate(attrs)
        data = {
            'code': 100,
            'message': '登陸成功',
            'username': self.user.username,
            'refresh': dic.get('refresh'),
            'access': dic.get('access')
        }
        return data

【2】檢視類

如果不是繼承內建user表的話,就需要進行如下配置

# 檢視類
class AutoUserView(GenericViewSet):
    serializer_class = AutoLoginSerializer

    @action(methods=['POST'], detail=False)
    def login(self, request):
        serializer = self.get_serializer(data=request.data)
        if serializer.is_valid():
            return Response(serializer.validated_data)

【3】JWT配置

由於自己定義了序列化類,所以需要在配置項裡面替換預設的序列化類為自己定義的序列化類

# JWT配置 裡面具體配置可以參考文件
SIMPLE_JWT = {
    # 用於生成access和刷refresh的序列化器。
    "TOKEN_OBTAIN_SERIALIZER": "app02.serializer.AutoLoginSerializer",
}

【七】定製payload內容

自定義序列化類

繼承TokenObtainPairSerializer

重寫get_token,繼承父類的get_token

修改get_tokend的返回值的內容

返回新的值

class AutoLoginSerializer(TokenObtainPairSerializer):
    @classmethod
    def get_token(cls, user):
        token = super().get_token(user)
        # 往第二段資料裡面加東西 這裡加一個名字
        token['name'] = user.username
        return token

    # 重寫全域性鉤子
    def validate(self, attrs):
        dic = super().validate(attrs)
        data = {
            'code': 100,
            'message': '登陸成功',
            'username': self.user.username,
            'refresh': dic.get('refresh'),
            'access': dic.get('access')
        }

        return data

【八】多方式登入

​ 可以透過多種方式登入,如使用者名稱/手機號/郵箱

【1】檢視類

class UserView(GenericViewSet, CreateModelMixin):
    serializer_class = None

    def get_serializer_class(self):
        if self.action == 'login':
            return UserLSerializer
        else:
            return UserRSerializer

    @action(methods=['POST'], detail=False)
    def register(self, request):
        res = super().create(request)
        return Response({'code': 200, 'message': '註冊成功', 'result': res.data})

    @action(methods=['POST'], detail=False)
    def login(self, request):
        serializer = self.get_serializer(data=request.data)
        if serializer.is_valid():
            refresh = serializer.context.get('refresh')
            access = serializer.context.get('access')
            return Response({'code': 200, 'message': '登入成功', 'refresh': refresh, 'access': access})
        return Response(serializer.errors)

【2】序列化類

class UserLSerializer(serializers.Serializer):
    # 因為登入的時候只輸入使用者名稱密碼
    # 所以只需要序列化兩個欄位
    username = serializers.CharField()
    password = serializers.CharField()

    # 這個方法用來拿到登入物件
    def _get_user(self, attrs):
        # 手機號正規表示式
        phone_regex = r'^1[3-9]\d{9}$'
        # 郵箱正規表示式
        email_regex = r'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$'
        # 拿到 手機號/郵箱/使用者名稱
        username = attrs.get('username')
        # 拿到密碼
        password = attrs.get('password')
        # 下面分別做判斷 看使用者名稱是以什麼方式登入
        if re.match(phone_regex, username):
            user = User.objects.filter(mobile=username).first()
        elif re.match(email_regex, username):
            user = User.objects.filter(email=username).first()
        else:
            user = User.objects.filter(username=username).first()
        if user and user.check_password(password):
            return user


    def validate(self, attrs):
        user = self._get_user(attrs)
        # 如果上面驗證失敗user就為None,就拋異常
        if not user:
            raise ValidationError('使用者名稱或密碼錯誤')
        refresh = RefreshToken.for_user(user)
        self.context['refresh'] = str(refresh)
        self.context['access'] = str(refresh.access_token)
        return attrs

【九】自定義使用者表 簽發

​ 如果不使用內建的user表作為使用者表,那麼就需要手動簽發token以及手動認證

【1】檢視類

手動寫登入註冊介面

class NormaUserView(GenericViewSet, CreateModelMixin):
    serializer_class = None

    def get_serializer_class(self):
        if self.action == 'login':
            return NormalUserLSerializer
        else:
            return NormalUserRSerializer

    @action(methods=['POST'], detail=False)
    def register(self, request):
        res = super().create(request)
        return Response({'code': 200, 'message': '註冊成功', 'result': res.data})

    @action(methods=['POST'], detail=False)
    def login(self, request):
        serializer = self.get_serializer(data=request.data)
        if serializer.is_valid():
            refresh = serializer.context.get('refresh')
            access = serializer.context.get('access')
            return Response({'code': 200, 'message': '登入成功', 'refresh': refresh, 'access': access})
        return Response(serializer.errors)

【2】序列化類

手動簽發token

匯入RefreshToken類

呼叫RefreshToken類的for_user方法傳入登入user物件

獲取token,傳入上下文管理器context

class NormalUserLSerializer(serializers.Serializer):
    username = serializers.CharField()
    password = serializers.CharField()

    def _get_user(self, attrs):
        # 拿到 手機號/郵箱/使用者名稱
        username = attrs.get('username')
        # 拿到密碼
        password = attrs.get('password')
        user = NormalUser.objects.filter(username=username, password=password).first()
        print(user)
        return user

    def validate(self, attrs):
        user = self._get_user(attrs)
        if not user:
            raise ValidationError('使用者名稱或密碼錯誤')
        token = RefreshToken.for_user(user)
        access = str(token.access_token)
        refresh = str(token)
        self.context['access'] = access
        self.context['refresh'] = refresh
        return attrs

【十】自定義使用者表 認證

匯入JWTAuthentication類from rest_framework.exceptions import AuthenticationFailed

寫認證類繼承JWTAuthentication類

重寫authenticate方法

獲取token

呼叫父類get_validated_token以此校驗token

from rest_framework.exceptions import AuthenticationFailed

# 繼承JWTAuthentication
class CommonAuthentication(JWTAuthentication):
    # 重寫authenticate
    def authenticate(self, request):
        # 從請求頭中取出token
        token = request.META.get('HTTP_AUTHORIZATION')
        # 如果沒有token就說明沒有登入,所以丟擲異常
        if not token:
            raise AuthenticationFailed('請先登入再操作')
        # 呼叫父類get_validated_token以此校驗token,返回值是payload段,包含使用者id
        validated_token = self.get_validated_token(token)
        # 取到id,從而得到使用者物件
        user_id = validated_token.get('user_id')
        user = NormalUser.objects.filter(pk=user_id).first()
        return user, token