Django REST framework API 指南(14):許可權

wcode發表於2018-03-16

官方原文連結
本系列文章 github 地址
轉載請註明出處

許可權

與 authentication 和 throttling 一起,permission 決定是應該接受還是拒絕訪問請求。

許可權檢查總是在檢視的最開始處執行,在任何其他程式碼被允許進行之前。許可權檢查通常會使用 request.userrequest.auth 屬性中的認證資訊來確定是否允許傳入請求。

許可權用於授予或拒絕不同類別的使用者訪問 API 的不同部分。

最簡單的許可權是允許通過身份驗證的使用者訪問,並拒絕未經身份驗證的使用者訪問。這對應於 REST framework 中的 IsAuthenticated 類。

稍微寬鬆的許可權會允許通過身份驗證的使用者完全訪問,而未通過身份驗證的使用者只能進行只讀訪問。這對應於 REST framework 中的 IsAuthenticatedOrReadOnly 類。

如何確定許可權

REST framework 中的許可權總是被定義為許可權類的列表。

在執行檢視的主體之前,檢查列表中的每個許可權。如果任何許可權檢查失敗,則會引發 exceptions.PermissionDeniedexceptions.NotAuthenticated 異常,並且檢視的主體不會再執行。

當許可權檢查失敗時,根據以下規則,將返回 “403 Forbidden” 或 “401 Unauthorized” 響應:

  • 該請求已成功通過身份驗證,但許可權被拒絕。 — 將返回 403 Forbidden 響應。
  • 該請求未成功通過身份驗證,並且最高優先順序身份驗證類未新增 WWW-Authenticate header。— 將返回 403 Forbidden 響應。
  • 該請求未成功通過身份驗證,不過最高優先順序身份驗證類新增了 WWW-Authenticate header。— 返回一個 HTTP 401 Unauthorized 響應,並會帶上一個適當的 WWW-Authenticate header。

物件級許可權

REST framework 許可權還支援物件級許可權。物件級許可權用於確定是否允許使用者對特定物件進行操作,該特定物件通常是指模型例項。

.get_object() 被呼叫時,物件級許可權由 REST framework 的通用檢視執行。與檢視級許可權一樣,如果使用者不被允許對給定物件進行操作,則會引發 exceptions.PermissionDenied 異常。

如果您正在編寫自己的檢視並希望強制執行物件級許可權,或者如果您在通用檢視上重寫了 get_object 方法,那麼將需要顯式地在你檢索該物件時呼叫 .check_object_permissions(request, obj) 方法。

這將引發 PermissionDeniedNotAuthenticated 異常,或者只是在檢視具有適當的許可權時才返回。

例如:

def get_object(self):
    obj = get_object_or_404(self.get_queryset(), pk=self.kwargs["pk"])
    self.check_object_permissions(self.request, obj)
    return obj
複製程式碼

物件級許可權的限制

出於效能原因,通用檢視在返回物件列表時不會自動將物件級許可權應用於查詢集中的每個例項。

通常,當您使用物件級許可權時,您還需要適當地過濾查詢集,以確保使用者只能看到他們被允許檢視的例項。

設定許可權策略

預設許可權策略可以使用 DEFAULT_PERMISSION_CLASSES setting 全域性設定。例如。

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated',
    )
}
複製程式碼

如果未指定,則此設定預設為允許無限制訪問:

'DEFAULT_PERMISSION_CLASSES': (
   'rest_framework.permissions.AllowAny',
)
複製程式碼

您還可以在基於 APIView 類的檢視上設定身份驗證策略。

from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView

class ExampleView(APIView):
    permission_classes = (IsAuthenticated,)

    def get(self, request, format=None):
        content = {
            'status': 'request was permitted'
        }
        return Response(content)
複製程式碼

或者在基於 @api_view 裝飾器的函式檢視上設定。

from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response

@api_view(['GET'])
@permission_classes((IsAuthenticated, ))
def example_view(request, format=None):
    content = {
        'status': 'request was permitted'
    }
    return Response(content)
複製程式碼

注意: 當你通過類屬性或裝飾器設定新的許可權類時,settings.py 檔案中的預設設定會被忽略。


API 參考

AllowAny

AllowAny 許可權類將允許不受限制的訪問,而不管該請求是否已通過身份驗證或未經身份驗證。

也不一定非要用此許可權,可以通過為許可權設定空列表或元組來實現相同的結果,但是你會發現,使用此許可權使意圖更加清晰。

IsAuthenticated

IsAuthenticated 許可權類將拒絕任何未通過身份驗證的使用者的訪問。

如果你希望 API 只能由註冊使用者訪問,則可以使用此許可權。

IsAdminUser

IsAdminUser 許可權僅允許 user.is_staffTrue 使用者訪問,其他任何使用者都將被拒絕。

如果你希望 API 只能被部分受信任的管理員訪問,則可以使用此許可權。

IsAuthenticatedOrReadOnly

IsAuthenticatedOrReadOnly 允許通過身份驗證的使用者執行任何請求。未通過身份驗證的使用者只能請求 “安全” 的方法: GETHEADOPTIONS

如果你希望 API 允許匿名使用者擁有讀取許可權,並且只允許對已通過身份驗證的使用者執行寫入許可權,則可以使用此許可權。

DjangoModelPermissions

此許可權類與 Django 的標準 django.contrib.auth 模型許可權繫結。此許可權只能應用於具有 .queryset 屬性集的檢視。只有在使用者通過身份驗證並分配了相關模型許可權的情況下,才有許可權訪問。

  • POST 請求要求使用者在模型上具有 add 許可權。
  • PUTPATCH 請求要求使用者在模型上具有 change 許可權。
  • DELETE 請求要求使用者在模型上具有 delete 許可權。

預設行為也可以被重寫以支援自定義模型許可權。例如,你可能想要包含 GET 請求的 view 模型許可權。

要自定義模型許可權,請繼承 DjangoModelPermissions 並設定 .perms_map 屬性。有關詳細資訊,請參閱原始碼。

使用不包含 queryset 屬性的檢視。

如果你將此許可權與重寫 get_queryset() 方法的檢視一起使用,則檢視上可能沒有 queryset 屬性。在這種情況下,我們建議使用 sentinel 查詢集標記檢視,以便此類可以確定所需的許可權。例如:

queryset = User.objects.none()  # Required for DjangoModelPermissions
複製程式碼

DjangoModelPermissionsOrAnonReadOnly

DjangoModelPermissions 類似,但也允許未經身份驗證的使用者對 API 進行只讀訪問。

DjangoObjectPermissions

該許可權類與 Django 的標準物件許可權框架繫結,該框架允許對每個模型物件進行許可權驗證。為了使用此許可權類,你還需要新增支援物件級許可權的許可權後端,例如 django-guardian

DjangoModelPermissions 一樣,此許可權只能應用於具有 .queryset 屬性或 .get_queryset() 方法的檢視。只有在使用者通過身份驗證並且具有相關的每個物件許可權和相關的模型許可權後,才有許可權訪問。

  • POST 請求要求使用者對模型例項具有 add 許可權。
  • PUTPATCH 請求要求使用者對模型例項具有 change 許可權。
  • DELETE 請求要求使用者對模型例項具有 delete 許可權。

請注意,DjangoObjectPermissions 不需要 django-guardian 軟體包,並且同樣支援其他物件級別的後端。

DjangoModelPermissions 一樣,你可以通過繼承 DjangoObjectPermissions 並設定 .perms_map 屬性來自定義模型許可權。有關詳細資訊,請參閱原始碼。


注意: 如果你需要獲取 GETHEADOPTIONS 請求的物件級 view 許可權,則還需要考慮新增 DjangoObjectPermissionsFilter 類,以確保列表端點只返回包含使用者具有檢視許可權的物件的結果。



自定義許可權

要實現自定義許可權,請繼承 BasePermission 並實現以下方法中的一個或兩個:

  • .has_permission(self, request, view)
  • .has_object_permission(self, request, view, obj)

如果請求被授予訪問許可權,則方法應該返回 True,否則返回 False

如果你需要測試一個請求是一個讀操作還是一個寫操作,你應該根據常量 SAFE_METHODS 檢查請求方法, SAFE_METHODS 是一個包含 'GET''OPTIONS''HEAD' 的元組。例如:

if request.method in permissions.SAFE_METHODS:
    # Check permissions for read-only request
else:
    # Check permissions for write request
複製程式碼

Note: 只有在檢視級別 has_permission 檢查已通過時才會呼叫例項級別的 has_object_permission 方法。還要注意,為了執行例項級檢查,檢視程式碼應該顯式呼叫 .check_object_permissions(request, obj)。如果你使用的是通用檢視,那麼預設情況下會為您處理。(基於函式的檢視將需要明確檢查物件許可權,在失敗時引發 PermissionDenied。)


如果測試失敗,自定義許可權將引發 PermissionDenied 異常。要更改與異常相關的錯誤訊息,請直接在你的自定義許可權上實現 message 屬性。否則將使用 PermissionDenieddefault_detail 屬性。

from rest_framework import permissions

class CustomerAccessPermission(permissions.BasePermission):
    message = 'Adding customers not allowed.'

    def has_permission(self, request, view):
         ...
複製程式碼

舉個栗子

以下是一個許可權類的示例,該許可權類將傳入請求的 IP 地址與黑名單進行比對,並在 IP 被列入黑名單時拒絕該請求。

from rest_framework import permissions

class BlacklistPermission(permissions.BasePermission):
    """
    Global permission check for blacklisted IPs.
    """

    def has_permission(self, request, view):
        ip_addr = request.META['REMOTE_ADDR']
        blacklisted = Blacklist.objects.filter(ip_addr=ip_addr).exists()
        return not blacklisted
複製程式碼

除了針對所有傳入請求執行的全域性許可權,還可以建立物件級許可權,這些許可權僅針對影響特定物件例項的操作執行。例如:

class IsOwnerOrReadOnly(permissions.BasePermission):
    """
    Object-level permission to only allow owners of an object to edit it.
    Assumes the model instance has an `owner` attribute.
    """

    def has_object_permission(self, request, view, obj):
        # Read permissions are allowed to any request,
        # so we'll always allow GET, HEAD or OPTIONS requests.
        if request.method in permissions.SAFE_METHODS:
            return True

        # Instance must have an attribute named `owner`.
        return obj.owner == request.user
複製程式碼

請注意,通用檢視將檢查適當的物件級許可權,但如果你正在編寫自己的自定義檢視,則需要確保檢查自己的物件級許可權。您可以通過在擁有物件例項後從檢視中呼叫 self.check_object_permissions(request, obj) 來完成此操作。如果任何物件級許可權檢查失敗,此呼叫將引發適當的 APIException,否則將簡單地返回。

另請注意,通用檢視將僅檢查單個模型例項的檢視的物件級許可權。如果你需要列表檢視的物件級過濾,則需要單獨過濾查詢集。

相關文章