1. 程式人生 > 實用技巧 >Django Rest framework 之 許可權

Django Rest framework 之 許可權

一、許可權例項

在閱讀本文之前請先參考django rest framework 之 認證中關於django rest framework的相關內容及例項

1、目錄結構

為了更好的管理各個功能元件,在django rest framework 之 認證中我們說到可以將認證類單獨的拿出來,放到其他目錄下,然後匯入到views.py 檔案中,在許可權環節我們亦可以這麼做,目錄結構就變成這樣

在api這個app下建立一個utils包專門用來存放相關的元件。

2、為模型類新增認證欄位

我們在models.py中定義了兩個模型類,分別是

from django.db import models


class UserInfo(models.Model):
    USER_TYPE = (
        (1,'普通使用者'),
        (2,'VIP'),
        (3,'SVIP')
    )

    user_type = models.IntegerField(choices=USER_TYPE, default=1)
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=64)

class UserToken(models.Model):
    user = models.OneToOneField(UserInfo,on_delete=models.CASCADE)
    token = models.CharField(max_length=64)

UserInfo中通過為使用者新增一個user_type欄位來保證使用者的身份,是普通使用者,VIP還是SVIP,這樣就可以通過使用者的身份驗證不同的許可權。如果想要定義一個檢視類,這個類中的邏輯只有超級使用者才能訪問。

3、具體許可權認證

可以再utils中的permissions.py中這麼寫

# utils/permission.py


class SVIPPremission(object):
    message = "必須是SVIP才能訪問"  # 這裡的message表示如果不通過許可權的時候,錯誤提示資訊

    def has_permission(self,request,view):
        if request.user.user_type != 3:
            return False
        return True


class MyPremission(object):
    # 這個許可權類表示當用戶為SVIP時不可通過
    def has_permission(self,request,view):
        if request.user.user_type == 3:
            return False
        return True

這裡只是判斷使用者的USER_TYPE的欄位,判斷使用者是否有許可權,也可以新增其他的邏輯進行判斷。

4、全域性配置

在上一節的django rest framework 之 認證的認證中,將認證類放到了settings.py檔案中,這樣會作用到檢視中的每一個檢視類,如果檢視類想要自己進行認證,只需要重寫authentication_classes即可,那麼對於許可權來說我們也可以這麼做,

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': ['api.utils.authenticate.FirstAuthenticate', 'api.utils.authenticate.MyAuthenticate'], 
    "UNAUTHENTICATED_USER": None, # 匿名,request.user = None
    "UNAUTHENTICATED_TOKEN": None,# 匿名,request.auth = None
    "DEFAULT_PERMISSION_CLASSES": ['api.utils.permission.MyPermission'],  # 表示每一個檢視類(只要不重寫permission_classes屬性),都需要SVIP的使用者才能訪問。
}

5、檢視

在檢視view.py中定義一個使用者詳情類UserInfoView作為測試,這裡的檢視和上一節的django rest framework 之 認證是相接的。

from django.shortcuts import render, HttpResponse
from django.http import JsonResponse
from django.views import View
from rest_framework.views import APIView
from rest_framework.request import Request

from .utils import authenticate, permission
from api import models

import json


def md5(user):
    import hashlib
    import time

    # 當前時間,相當於生成一個隨機的字串
    ctime = str(time.time())

    # token加密
    m = hashlib.md5(bytes(user, encoding='utf-8'))
    m.update(bytes(ctime, encoding='utf-8'))
    return m.hexdigest()


class AuthView(APIView):
    '''用於使用者登入驗證'''
    authentication_classes = []      #裡面為空,代表不需要認證
    permission_classes = []          #不裡面為空,代表不需要許可權

    def get(self, request, *args, **kwargs):
        ret = {'code': 1000, 'msg': 'success', 'name': '偷偷'}
        ret = json.dumps(ret, ensure_ascii=False)

        return HttpResponse(ret)

    def post(self,request,*args,**kwargs):
        ret = {'code':1000,'msg':None}
        try:
            user = request.POST.get('username')
            pwd = request.POST.get('password')
            print(user, pwd)
            obj = models.UserInfo.objects.filter(username=user,password=pwd).first()
            print(obj.username, obj.password)
            if not obj:
                ret['code'] = 1001
                ret['msg'] = '使用者名稱或密碼錯誤'
            #為使用者建立token
            token = md5(user)
            #存在就更新,不存在就建立
            models.UserToken.objects.update_or_create(user=obj,defaults={'token':token})
            ret['token'] = token
        except Exception as e:
            ret['code'] = 1002
            ret['msg'] = '請求異常'
        return JsonResponse(ret)


ORDER_DICT = {
    1:{
        'name':'apple',
        'price':15
    },
    2:{
        'name':'狗子',
        'price':100
    }
}


class OrderView(APIView):
    # 使用者想要獲取訂單,就要先通過身份認證、在全域性settings.py 中已經配置
    permission_classes = []

    def get(self, request, *args, **kwargs):
        ret = {
            'code': 1024,
            'msg': '訂單獲取成功',
        }
        try:
            ret['data'] = ORDER_DICT
        except Exception as e:
            pass
        return JsonResponse(ret)
    

class UserInfoView(APIView):
    permission_classes = [permission.SVIPPermission]

    def get(self, request, *args, **kwargs):
        print(request.user)
        return HttpResponse('SVIP使用者資訊')

這裡的UserInfoView重寫了permission_classes屬性,則不會再使用settings.py中關於認證類的配置。表示只有SVIP才能訪問這個類的內部。

6、路由分發

在url.py中設定路由分發

from django.conf.urls import url

from api.views import AuthView, OrderView, UserInfoView


urlpatterns = [
    url(r'^api/v1/auth/$', AuthView.as_view()),
    url(r'^api/v1/order/$', OrderView.as_view()),
    url(r'^api/v1/info/$', UserInfoView.as_view()),
]

7、請求測試

Postman或者瀏覽器傳送請求,由於我們在setting.py中配置了 'DEFAULT_AUTHENTICATION_CLASSES': ['api.utils.authenticate.FirstAuthenticate', 'api.utils.authenticate.MyAuthenticate'],
會對請求進行認證,所以要帶這使用者的token才能通過,進入到許可權元件。
進入sqlite資料庫,找到對應的註冊使用者

在資料庫中,拿到重新整理的使用者的最近一次的token

拿到這個token傳送請求

請求成功,則說明許可權生效,也可以在將UserInfoView改成如下

class UserInfoView(APIView):
    permission_classes = [permission.SVIPPermission]

    def get(self, request, *args, **kwargs):
        print(request.user)
        return HttpResponse('SVIP使用者資訊')

在傳送請求,就會發現使用者不會有許可權的提示資訊。如下

二、原始碼分析

django rest framework 之 認證一樣進入,request的請求流程,進入原始碼檢視具體許可權的操作

1、進入dispath()方法

2、進入initial()方法

3、進入check_permissions()方法

4、許可權類的具體操作

在這裡可以看到和認證中有類似的操作,獲取所有的許可權類,並且執行每一個許可權類的has_permission()方法,而這個方法具體封裝了我們的判斷許可權操作,但是has_permission()方法的返回值需要時False或者Trueself.permission_denied(request, message=getattr(permission, 'message', None))說明可以在許可權類中重寫message屬性,來定義許可權不通過時候的提示資訊。

進入self.get_permissions()來看一下

4、獲取所有的許可權類

在APIView中有定義預設的許可權類,因此也可以通過全域性配置的方法配置許可權類。

5、原生的許可權類

像認證那樣,django rest framework中也有許可權類

可以根據自己的需要呼叫。

三、總結

許可權其實的流程跟之前的認證流程是一樣的,認證類封裝到request中,然後再呼叫認證類的方法,不過這裡的方法返回值不再是像認證元件那樣的直接返回一個認證的物件,而是返回一個True或者False值表示認證過的物件是否有某些許可權再進行具體操作。
這裡注意的是,在自己重寫許可權類的相關方法,新增自己的邏輯的時候,返回值需要是一個布林值,Flase或者True,表示是否有許可權。也可以通過全域性配置和區域性配置許可權類。