[DRF] <Level Two> Django REST Framework - 4. Permission on Ebook List, Detail

Alex of the year 2020 & 2021·2020년 9월 2일
0

Django Rest Framework

목록 보기
8/15
post-thumbnail

Permission

  • 인증된 유저에게만 접근 권한을 주기 (Grant access to our API only to authenticated users)
  • 인가된 유저에게만 쓰기 권한을 주기 (Grant write permissions only to authorized users)

Permission의 기본

DRF 공식 문서 --> API guide --> Permissions --> Setting the permission policy 문서 참조

가장 쉬운 방법은, 위의 코드를 settings.py의 맨 하단에 붙여넣는 것이다.
하지만 이 방법으로 permission을 설정해버릴 경우, 우리가 만드는 모든 엔드포인트에 인증된 유저만이 접근할 수 있게 된다. 지금까지는 위의 코드를 적지 않고 사용했기에
위처럼 디폴트 값에 해당하는 AllowAny가 적용되어, GET, POST, PUT, PATCH등이 특별한 인증 없이도 가능했던 것이다.

Permission class의 종류

1) AllowAny : 아무것도 설정하지 않을 경우 default이지만 의도할 경우 명시해주는 것이 좋다.
2) IsAuthenticated
3) IsAdminUser
4) IsAuthenticatedOrReadOnly : 인증된 유저의 경우 모든 권한이 있지만, 그렇지 않은 경우에는 상대적으로 SAFE한 권한만을 가지게 된다. GET, HEAD or OPTIONS가 그에 해당한다.

코드

1) 먼저 가장 기본적으로 제시한 방법, settings.py 하단에 설정을 걸어보자.
ebooksapi/ebooksapi/settings.py

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ]
}

2) 이후 바로 python manage.py runserver 실행 후
127.0.0.1:8000/api/ebooks/ 접속 시, 리스트가 잘 나오던 이전과는 달리 이 창을 보게될 것이다

127.0.0.1:8000/api/ebooks/1/ 등 다른 엔드포인트 역시 같은 결과를 낸다.
아까 말했듯, settings.py의 하단에 IsAuthenticated permission을 설정하면 지금처럼 globally 적용이 된다.

3) 그럼 이번에는 다른 permission으로 바꾸어본다.
ebooksapi/ebooksapi/settings.py

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticatedOrReadOnly',
    ]
}

4) 다시 python manage.py runserver 실행 후
127.0.0.1:8000/api/ebooks/ 접속 시, 리스트가 잘 나오는 것을 확인할 수 있다. Runauthenticated 유저에게도 ReadOnly가 가능하기 때문이다.
하지만 이번 강의에서 원하는 것은 Global한 permission을 적용이 아닌
각 API마다, 각 엔드포인트마다 다른 수준의 permission 적용이므로 settings.py 부분에 적었던 permission은 다시 주석처리 후, 다음으로 넘어간다.

5) ebooksapi/ebooks/api/views.py 수정

from rest_framework import generics
from rest_framework.generics import get_object_or_404 
from rest_framework import permissions # permission 클래스 임포트

from ebooks.models import Ebook, Review 
from ebooks.api.serializers import EbookSerializer, ReviewSerializer 


class EbookListCreateAPIView(generics.ListCreateAPIView):
    queryset = Ebook.objects.all()
    serializer_class = EbookSerializer
    permission_classes = [permissions.IsAuthenticatedOrReadOnly]
    # settings.py에 설정했던것과 달리 
    # 특정 엔드포인트에만 local하게 적용되도록 설정한다 
    
class EbookDetailAPIView(generics.RetrieveUpdateDestroyAPIView):
    queryset = Ebook.objects.all()
    serializer_class = EbookSerializer
    permission_classes = [permissions.IsAuthenticatedOrReadOnly]
    # settings.py에 설정했던것과 달리 
    # 특정 엔드포인트에만 local하게 적용되도록 설정한다 

class ReviewCreateAPIView(generics.CreateAPIView):
    queryset = Review.objects.all()
    serialzer_class = ReviewSerializer

	def perform_create(self, serializer):        
        ebook_pk = self.kwargs.get("ebook_pk") 
        ebook = get_object_or_404(Ebook, pk=ebook_pk)  
        serializer.save(ebook=ebook)
        
class ReviewDetailAPIView(generics.RetrieveUpdateDestroyAPIView):
    queryset = Review.objects.all()
    serialzer_class = ReviewSerializer

결과는 127.0.0.1:8000/api/ebooks/과 127.0.0.1:8000/api/ebooks/1 접속 시, 모두 ReadOnly로만 접속되는 것을 볼 수 있다. 혹은 Admin 페이지로 가서 Admin 계정으로 로그인 후 접근 시 여타 수정과 삭제도 가능하다는 것도 확인 가능하다. (admin은 authenticated이므로)

DRF에서 제공하는 기본적인 Permission 클래스에 AllowAny, IsAuthenticated, IsAdminUser, IsAuthenticatedOrReadOnly가 있다는 것을 아까 확인했다. 하지만 IsAdminUserOrReadOnly 같은 조금 더 복잡한 permission을 구현하려면 어떻게 해야할까?

6) 우선 admin 페이지로 가서, standard라는 임의의 보통 유저를 만든다. (Staff, Superuser X)
7) 이후가 중요하다. 잘 수정하지 않는 곳인데,
ebooksapi/ebooksapi/urls.py로 가서 전체 urls를 수정한다

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include('ebooks.api.urls')),
    path('api-auth/', include('rest_framework.urls'))
    # 이 코드를 추가해줘야 한다. 이 코드를 추가하면 크롬 상단 우측 쪽에 logout할 수 있도록 UI가 변경된다. 
]

UI만 변경된 것이 아니라, 우리가 추가한 저 코드가 어떻게 routing하는 지 살펴보려면 127.0.0.1:8000/api-auth/로 접속한다. 그러면 이런 창을 보게 된다.

즉, login과 logout을 할 수 있는 엔드포인트로의 연결이 가능해진 것이다.

만일, 127.0.0.1:8000/api/ebooks/ 엔드포인트에서(ebooks list page) 로그아웃을 한 후, 로그인 버튼을 누르면 흰 바탕에 로그인 화면이 뜰 것이다. 그 때의 엔드포인트는
127.0.0.1:8000/api-auth/login/?next=/api/ebooks/ 라는 것을 확인할 수 있다. 이 url로 어떤 식으로 routing 되고 있는지 이해할 수 있을 것이다.

여튼
127.0.0.1:8000/api-auth/login/?next=/api/ebooks/ 엔드포인트에서 좀전에 가입시킨 일반 유저 standard를 로그인 시키면, authenticated 유저인 standard는
127.0.0.1:8000/api/ebooks/ 화면으로 routing됨과 동시에, authenticated이므로 ReadOnly가 아닌 ebook 리스트를 수정할 수 있는 권한을 확인하게 된다.

8) ebooksapi/ebooks/api/permission.py 파일을 만든다. 근데 이걸 왜 만들까?
위에서 말했듯, 특별한 permission을 구현하기 위해서(customized permission) 내가 직접 작성하기 위해 파일을 만든 것이다.

from rest_framework import permissions

class IsAdminUserOrReadOnly(permissions.IsAdminUser):
# IsAdminUser를 상속받아, 내가 원하는대로 override한다.
    def has_permission(self, request, view):
    # has_permission은 인자로 self, request, view를 꼭 가진다
        is_admin = super().has_permission(request, view)
        # is_admin은 이미 우리가 상속받은 IsAdminUser 클래스의 has_permission 메소드를 실행하면 된다. 참고로 bool 값을 리턴한다. 
        return request.method in permissions.SAFE_METHODS or is_admin
        # request에서 보내온 method가 permission 클래스가 정의하는 SAFE_METHODS인 
        # 'get, options, head'에 해당한다면 (우리의 DB에 영향을 줄 일이 없는) 
        # 혹은 지금 이 유저가 어드민이라면 (is_admin) 
        # True를 반환하겠다

참고로 위의 과정은 공식문서에 따르면 이런식으로 적혀있다.

9) 위에서 만든 permissions.py를 저장한 후,
ebooksapi/ebooks/api/views.py로 이동하여 임포트 한다.

from rest_framework import generics
from rest_framework.generics import get_object_or_404 
from rest_framework import permissions # 이건 DRF 제공하는 기존의 permission 클래스 임포트한거고, 지금은

from ebooks.models import Ebook, Review 
from ebooks.api.serializers import EbookSerializer, ReviewSerializer 
from ebooks.api.permissions import IsAdminOrReadOnly # 방금 내가 직접 제작한 permission 클래스를 임포트해준다.

class EbookListCreateAPIView(generics.ListCreateAPIView):
    queryset = Ebook.objects.all()
    serializer_class = EbookSerializer
    permission_classes = [IsAdminOrReadOnly]
    # 그리고 적용시킨다.
    
class EbookDetailAPIView(generics.RetrieveUpdateDestroyAPIView):
    queryset = Ebook.objects.all()
    serializer_class = EbookSerializer
    permission_classes = [IsAdminOrReadOnly]
    # 그리고 적용시킨다.

class ReviewCreateAPIView(generics.CreateAPIView):
    queryset = Review.objects.all()
    serialzer_class = ReviewSerializer

	def perform_create(self, serializer):        
        ebook_pk = self.kwargs.get("ebook_pk") 
        ebook = get_object_or_404(Ebook, pk=ebook_pk)  
        serializer.save(ebook=ebook)
        
class ReviewDetailAPIView(generics.RetrieveUpdateDestroyAPIView):
    queryset = Review.objects.all()
    serialzer_class = ReviewSerializer

이대로 standard 유저일 때 127.0.0.1:8000/api/ebooks/에 접속하면, ReadOnly 형식의 페이지를 보여주지만,
로그아웃 후 admin 유저로 다시 접속하면 수정과 삭제가 가능한 페이지를 볼 수 있다.

references:
https://www.django-rest-framework.org/api-guide/permissions/

profile
Backend 개발 학습 아카이빙 블로그입니다. (현재는 작성하지 않습니다.)

0개의 댓글