天天看點

2021-09-10 jwt的内置簽發方式修改傳回格式 jwt簽發的源碼分析 drf_jwt認證的token源碼 自定義user表實作jwt的token簽發 RBAC介紹 simple-ui的使用上節回顧今日内容

上節回顧

1 過濾的源碼分析
	-視圖類中配置類屬性:filter_backends = ['過濾類']
    -必須繼承他倆ListModelMixin+GenericAPIView
    -ListModelMixin的list方法中執行了self.filter_queryset,視圖類
    -GenericAPIView找filter_queryset方法
    for backend in list(self.filter_backends):
       queryset = backend().filter_queryset(self.request, queryset, self)
        return queryset
    
    
2 分頁類的源碼分析
	-視圖類中配置類屬性: pagination_class = 分頁類
    -必須繼承他倆ListModelMixin+GenericAPIView
    -ListModelMixin的list方法中執行了
    page = self.paginate_queryset(queryset) # 分頁
    if page is not None:
       serializer = self.get_serializer(page, many=True)
       return self.get_paginated_response(serializer.data)
    
    -GenericAPIView找paginate_queryset方法
    	#self.paginator配置的分頁類的對象
    	self.paginator.paginate_queryset(queryset, self.request, view=self)
    -GenericAPIView找get_paginated_response方法
    	#self.paginator配置的分頁類的對象
		self.paginator.get_paginated_response(data)
  -具體的分頁類:PageNumberPagination
	-paginate_queryset
    -get_paginated_response
    
    
3 繼承APIView實作分頁
	-擷取了所有資料
    -分頁
    	-執行個體化得到分頁對象
        -調用分頁對象的paginate_queryset方法完成分頁
        -把要分頁的資料序列化
        -把序列化後的資料傳回(直接傳回和使用get_paginated_response)
        
4 自定義分頁(出去面試,面試官問:如何自定義分頁)
	-使用當此請求的request對象,取出擷取的頁碼數
    -通過頁碼數和每頁顯示多少條,具體的取出目前頁碼的資料

5 jwt:json web token,是一種前後端的登入認證方式,它分token的簽發和認證,簽發的意思是使用者登入成功,生成三段式的token串;認證指的是使用者通路某個接口,需要攜帶token串過來,我們完成認證
6 三段:頭.荷載.簽名,每一段都通過base64編碼
7 base64的編碼和解碼
8 djangorestframework-jwt:好久沒維護了,使用djangorestframework-simplejwt
9 快速使用
	-快速簽發
    	-在路由中(登入功能有了,基于auth的user表實作的登入)
        path('login/', obtain_jwt_token)
    -快速認證
    	-在視圖類中配置
        authentication_classes = [JSONWebTokenAuthentication, ]#drf_jwt
    	permission_classes = [IsAuthenticated] # drf
           

今日内容

1 jwt内置簽發方法修改傳回格式(

***

)

# 寫一個函數
def jwt_response_payload_handler(token, user=None, request=None):
    return {
        'token':token,
        'username':user.username
    }
# 在配置檔案中配置
#drf_jwt的配置檔案(drf_jwt有個預設配置檔案:from rest_framework_jwt import settings)
from rest_framework_jwt.utils import jwt_response_payload_handler
JWT_AUTH={
    'JWT_RESPONSE_PAYLOAD_HANDLER':'app01.utils.jwt_response_payload_handler'
}
           

1.2 jwt簽發的源碼分析

# 路由的---》obtain_jwt_token---》ObtainJSONWebToken.as_view()---》視圖類ObtainJSONWebToken----》
class ObtainJSONWebToken(JSONWebTokenAPIView):
    # 序列化類
    serializer_class = JSONWebTokenSerializer
    
# 登入請求,發了一個post請求,攜帶使用者名密碼---》ObtainJSONWebToken的post請求
# JSONWebTokenAPIView内有個post
    def post(self, request, *args, **kwargs):
        # 得到一個序列化類的對象,使用傳入的request.data
        serializer = self.get_serializer(data=request.data)
        # 序列化類對象的is_valid,會執行字段自己的校驗規則,局部鈎子,全局鈎子
        if serializer.is_valid():
            # 取出user,取出token
            user = serializer.object.get('user') or request.user
            token = serializer.object.get('token')
            # 執行你在配置檔案中配置的函數
            response_data = jwt_response_payload_handler(token, user, request)
            response = Response(response_data)
       return response
    
    
# JSONWebTokenSerializer源碼
	-validate
        def validate(self, attrs):
        credentials = {
            self.username_field: attrs.get(self.username_field),
            'password': attrs.get('password')
        }
        # {username:lq,password:lqz12345}
        if all(credentials.values()):
            user = authenticate(**credentials)  # 通過使用者名密碼認證auth的user表中的使用者
            if user:
                # 通過user得到荷載{user_id:1,username:lqz,..}
                payload = jwt_payload_handler(user)
                return {
                    'token': jwt_encode_handler(payload), # 通過荷載得到token串
                    'user': user
                }
          
           

1.3 drf_jwt認證token源碼

# 1 讀JSONWebTokenAuthentication的父類BaseJSONWebTokenAuthentication的authenticate
    def authenticate(self, request):
        # 取出前端傳入的token串
        jwt_value = self.get_jwt_value(request)
        if jwt_value is None: # 如果token串,前端沒有傳,也認證通過,後續必須權重限類
            return None

        try:
            payload = jwt_decode_handler(jwt_value) # 通過token串獲得payload,驗簽(是否被篡改),檢查過期時間
        except jwt.ExpiredSignature:
            msg = _('Signature has expired.')
            raise exceptions.AuthenticationFailed(msg)
        except jwt.DecodeError:
            msg = _('Error decoding signature.')
            raise exceptions.AuthenticationFailed(msg)
        except jwt.InvalidTokenError:
            raise exceptions.AuthenticationFailed()

        # 通過payload獲得目前使用者:通過user_id---->auth的user表中擷取目前使用者
        user = self.authenticate_credentials(payload)

        return (user, jwt_value)

# 2 JSONWebTokenAuthentication的get_jwt_value方法,傳回token串
    def get_jwt_value(self, request):
        # 取出前端傳入的token串,通過空格切分
        #(jwt,token串)
        auth = get_authorization_header(request).split()
        auth_header_prefix = api_settings.JWT_AUTH_HEADER_PREFIX.lower()

        if not auth: # 如果沒有傳認證的token,傳回None
            if api_settings.JWT_AUTH_COOKIE:
                return request.COOKIES.get(api_settings.JWT_AUTH_COOKIE)
            return None

        if smart_text(auth[0].lower()) != auth_header_prefix:
            return None

        if len(auth) == 1: # auth元組的長度如果等于1,抛異常,因為我們jwt token串
            msg = _('Invalid Authorization header. No credentials provided.')
            raise exceptions.AuthenticationFailed(msg)
        elif len(auth) > 2: # auth元組的長度如果大于1,抛異常,因為我們jwt token串
            msg = _('Invalid Authorization header. Credentials string '
                    'should not contain spaces.')
            raise exceptions.AuthenticationFailed(msg)

        return auth[1]

           

2 自定義User表實作jwt的token簽發(

****

)

from rest_framework.response import Response
from .models import UserInfo
from rest_framework_jwt.settings import api_settings

jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER


class LoginView(APIView):
    def post(self, request):
        username = request.data.get('username')
        password = request.data.get('password')
        user = UserInfo.objects.filter(username=username, password=password).first()
        if user:  # 使用者名和密碼正确,登入成功,簽發token
            payload = jwt_payload_handler(user)
            token = jwt_encode_handler(payload)
            return Response({'code': '100', 'msg': '登入成功', 'token': token, 'usrename': user.username})
        else:
            return Response({'code': '101', 'msg': '使用者名或密碼錯誤'})
           

3 自定義認證類,實作jwt的token認證(

****

)

from rest_framework.authentication import BaseAuthentication
from rest_framework import exceptions
import jwt
from .models import UserInfo
from rest_framework_jwt.settings import api_settings
jwt_decode_handler = api_settings.JWT_DECODE_HANDLER
class JwtAuthentication(BaseAuthentication):
    def authenticate(self, request):
        # 取出前端傳入的token串
        token=request.META.get('HTTP_TOKEN')
        # 通過token獲得payload
        try:
            payload = jwt_decode_handler(token) # 通過token串獲得payload,驗簽(是否被篡改),檢查過期時間
        # except Exception:
        #     raise exceptions.AuthenticationFailed('token認證失敗')
        except jwt.ExpiredSignature:
            msg = '簽名過期'
            raise exceptions.AuthenticationFailed(msg)
        except jwt.DecodeError:
            msg ='解碼錯誤'
            raise exceptions.AuthenticationFailed(msg)
        except jwt.InvalidTokenError:
            raise exceptions.AuthenticationFailed()
        # 通過payload獲得目前使用者(自己的表,自己拿)
        # user=UserInfo.objects.filter(pk=payload['user_id']).first()
        
        
        # 稍微優化一下,不是每次都取查詢目前登入使用者
        # user={'id':payload['user_id'],'username':payload['username']}
        user=UserInfo(id=payload['user_id'],username=payload['username'])
        # 傳回目前使用者
        return user,token
           

4 RBAC介紹(

****

)

# 基于角色的通路控制:RBAC  是基于角色的通路控制(Role-Based Access Control )在 RBAC  中,權限與角色相關聯,使用者通過成為适當角色的成員而得到這些角色的權限。這就極大地簡化了權限的管理。這樣管理都是層級互相依賴的,權限賦予給角色,而把角色又賦予使用者,這樣的權限設計很清楚,管理起來很友善。

# rbac權限控制一般都是用在公司内部的管理系統中(python的django寫公司内部項目較多,rbac很重要)
# Django的 Auth元件 采用的認證規則就是RBAC

# rbac表分析
	-使用者表:auth_user
    -角色表(部門,組表):auth_group
    -權限表:auth_permission
    
    -角色和權限是多對多---》中間表:auth_group_permissions
    -使用者和角色是多對多---》中間表:auth_user_groups
    
    -django的auth多寫了一個表,使用者和權限的多對多:auth_user_user_permissions
    
# djago有個admin背景管理,配合auth,可以快速搭建出一個基于rbac的背景管理系統   

# 幾年前,好多公司内部的管理系統,就是使用django的admin快速搭建的
# admin不好看,第三方美化
	-django 1.x上很火的 Xadmin:前端基于jq+bootstrap,2.x上支援不好了,3.x直接不能用了,而且作者也棄坑了,他又做了一個前後端分離的admin
    -simple-ui:djanog 3.x
           

5 simple-ui的使用(對admin美化)

1 pip3 install django-simpleui
2 自定義左側導航欄
	import time
SIMPLEUI_CONFIG = {
    'system_keep': False,
    'menu_display': ['Simpleui','權限認證', '多級菜單測試','動态菜單測試'],      # 開啟排序和過濾功能, 不填此字段為預設排序和全部顯示, 空清單[] 為全部不顯示.
    'dynamic': True,    # 設定是否開啟動态菜單, 預設為False. 如果開啟, 則會在每次使用者登陸時動态展示菜單内容
    'menus': [
        {
        'name': 'Simpleui',
        'icon': 'fas fa-code',
        'url': '/home/' # 自己的位址(前後端混合項目)
    },
        {
        'app': 'auth',
        'name': '權限認證',
        'icon': 'fas fa-user-shield',
        'models': [{
            'name': '使用者',
            'icon': 'fa fa-user',
            'url': 'auth/user/'
        }]
    },
        {
        'name': '多級菜單測試',
        'icon': 'fa fa-file',
      	# 二級菜單
        'models': [
            {
            'name': 'Baidu',
            'icon': 'far fa-surprise',
            # 第三級菜單 ,
            'models': [
                {
                  'name': '愛奇藝',
                  'url': 'https://www.iqiyi.com/dianshiju/'
                  # 第四級就不支援了,element隻支援了3級
                }, {
                    'name': '百度問答',
                    'icon': 'far fa-surprise',
                    'url': 'https://zhidao.baidu.com/'
                }
            ]
        },
            {
            'name': '内網穿透',
            'url': 'https://www.wezoz.com',
            'icon': 'fab fa-github'
        }]
    },
        {
        'name': '動态菜單測試' ,
        'icon': 'fa fa-desktop',
        'models': [{
            'name': time.time(),
            'url': 'http://baidu.com',
            'icon': 'far fa-surprise'
        }]
    }]
}




## 智慧大屏
https://gitee.com/kevin_chou/dataVIS