TIL121. DRF : Authentication과 Custom Permission 적용

ID짱재·2022년 2월 14일
0

Django REST Framework

목록 보기
5/10
post-thumbnail

📌 이 포스팅에서는 DRF에서 인증과 인가를 적용시키는 방법에 대해 정리하였습니다.



🌈 Authentication과 Custom Permission 적용

🔥 Authentication과 Permission 이란?

🔥 DRF의 Permission 시스템

🔥 Custom Permission 적용하기



1. Authentication과 Permission 이란?

🤔 Authentication과 Permission 이란?

✔️ 유입되는 요청을 허용 또는 거부하는 것이 아닌, 단순히 사용자를 식별하는 것을 인증(Authentication) 이라 한다.

✔️ 또한 인증된 사용자가 어떤한 요청을 허용 또는 거부하는 것은 허가(Permission)이다.

✔️ 예를 들어, 가입된 회원만 확인할 수 있는 페이지(ex. 프로필 페이지)는 로그인이 필요하고, 이를 인증이 필요하다 한다.

✔️ 하지만, 인증이 되었다고해서 다른 사람의 프로필을 삭제/수정할 수 잇는 것은 아닌데, 이 때 필요한 것이 허가이다.

✔️ 즉, 서비스의 사용자인지 식별하는 것 자체는 인증이고, 리소스에 대한 접근, 수정, 삭제 권한 등에 있어서는 허가가 필요하다.

🤔 Throttling 란?

✔️ Throttling은 일정 기간 동안 허용할 최대 요청 횟수를 제한하는 것을 의미한다.

✔️ 즉, 매분, 매일, 매월 등 어떠한 주기로 사용자가 요청할 수 있는지 제한시키는 것을 Throttling라 한다.

🤔 인증 처리 순서

  1. 매 요청 마다, APIView의 dispatch(request) 호출
  2. APIView의 initial(request) 호출
  3. APIView의 perform_authentication(request) 호출
  4. request의 user 속성 호출(rest_framework.request.Request 타입)
  5. request의 _authenticate() 호출

✔️ 단, Django Session을 사용할 때는 로그인할 때만 인증을 거치고, 세션 정보를 저장 후 이를 지속적으로 활용하지만, API로써 사용할 때는 클라이언트에서 서버로 요청을 할 때마다 매번 인증이 이뤄진다.



2. DRF의 Permission 시스템

✔️ DRF의 Permission 시스템은 현재 요청에 대한 허용/거부를 결정하고, 이를 전역 또는 APIView 단위별로 지정할 수 있다.

✔️ DRF permission 종류

  • AllowAny : 디폴트로 지정된 전역 설정으로 인증 여부에 상관없이, View를 호출할 수 있다.
  • IsAuthenticated : 회원으로 인증된 요청에 한해서, View 호출
  • IsAdmin : staff 인증 요청에 한해서, View 호출
  • IsAuthenticatedOrReadOnly : 비인증된 요청에 대해서는 읽기 권한만 허용

🤔 Permission Class 지정 예시

✔️ CBV의 경우애는 permission class에 list형식으로 permission 종류를 추가한다.

from rest_framework.permissions import IsAuthenticated # 👈 permissions에서 import
class ExampleView(APIView):
    permission_classes = [IsAuthenticated]
    def get(self, request, format=None):
        content = {'status': 'request was permitted.'}
        return Response(content)

✔️ FBV이 경우에는 permission 데코레이션을 사용합니다.

from rest_framework.decorators import permission_classes # 👈 decorators 에서 import
@api_view(['GET'])
@permission_classes([IsAuthenticated])  # 👈 list 형식으로 전달
def example_view(request, format=None):
    content = {'status': 'request was permitted.'}
    return Response(content)


3. Custom Permission 적용하기

🤔 전역으로 Permission 적용시키는 방법

✔️ DRF를 이용하여, 전역으로 Permission을 적용시키려면 settings.py에서 이를 선언할 수 있다.

✔️ 아래와 같이 직접 지정해주지 않는다면, Default로 "AllowAny"가 지정되어있다.

# settings.py
REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': [
        # 'rest_framework.permissions.AllowAny', # 👈 지정해주지않으면, Default로 AllowAny가 적용되있다.
        'rest_framework.permissions.IsAuthenticated',
    ],
}

✔️ 따라서 API별로 View의 역할에 맞게 지정하더라도, 전역 설정으로 Default는 IsAuthenticated로 해주는 편이 안전한다.

✔️ "IsAuthenticated"로 전역 지정했기 댸문에 View에 별다른 조치를 안해줘도, 인증을 수행한 요청만 응답이 반환된다.

🤔 Custom Permission 적용

✔️ 앱 내에 permissions.py를 생성하여 custom permission을 직접 적용시킬 수 있다.

✔️ 모든 Permission classes는 has_permission와 has_object_permission를 선택적으로 지정하여 custom할 수 있다.

✔️ has_permission는 APIView에 접근 시 인증을 체크하고, has_object_permission는 APIView의 get_object 함수를 통해 object에 접근 시 인증을 체크해준다.

✔️ 객체에 접근할 때, SAFE_METHODS의 종류 중 하나로 요청되었다면, True를 반환해서 응답을 주지만, PATCH나 DELETE로 요청될 때는 현재 사용자가 참조 중인 해당 겍체의 사용자 일 때만 True를 반환시킨다.

✔️ 여기서 SAFE_METHODS란 OPTION, HEAD, GET 요청을 의미하고, permissions에 이미 구현되있다.

# permissions.py
from rest_framework import permissions # 👈 psermissions import
class IsAuthorOrReadonly(permissions.BasePermission): # 👈 BasePermission 상속
    # has_permission
    def has_permission(self, request, view): # 👈 인증된 사용자에 한하여 목록조회/포스트 등록 가능
        return request.user and request.user.is_authenticated
    # has_object_permission
    def has_object_permission(self, request, view, obj):
        if request.method in permissions.SAFE_METHODS: # 👈 GET, OPTION, HEAD 요청일 때는 그냥 허용
            return True
        return obj.author == request.user # 👈 DELETE, PATCH 일 때는 현재 사용자와 객체가 참조 중인 사용자가 일치할 때마다 허용

✔️ 기본 인증 CBV에서 permission_classes를 통해 각각의 view에 대한 permission을 아래와 같이 적용시킨다.

from rest_framework.permissions import IsAuthenticated # 👈 기본 인증 import
from rest_framework.viewsets    import ModelViewSet
from rest_framework.decorators  import api_view, action
from rest_framework.response    import Response
from .permissions import IsAuthorOrReadonly # 👈 custom permission import
from .serializers import PostSerializer
from .models      import Post
class PostViewSet(ModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer
    permission_classes = [IsAuthenticated, IsAuthorOrReadonly] # 👈 permission 적용
    def perform_create(self, serializer):
        author = self.request.user
        ip = self.request.META['REMOTE_ADDR']
        serializer.save(author=author, ip=ip)
    @action(detail=False, methods=['GET'])
    def public(self, request):
        qs = self.get_queryset().filter(is_public=True)
        serializer = self.get_serializer(qs, many=True)
        return Response(serializer.data)
    @action(detail=True, methods=['PATCH'])
    def set_public(self, request, pk):
        instance = self.get_object()
        instance.is_public = True
        instance.save(update_fields=['is_public'])
        serializer = self.get_serializer(instance)
        return Response(serializer.data)
profile
Keep Going, Keep Coding!

0개의 댓글