[DRF] - Authentication & Permission

KimJiHong·2023년 11월 30일
1

REST API

목록 보기
5/6
post-thumbnail

Why use Authentication & Permission ?

DRF(Django REST FrameWork) 를 통해서 어떤 리소스의 CRUD를 구현하게 된다면

API request를 보낸 User 를 식별하고 해당 리소스 즉, 자원에 대한 접근 권한을 설정하여

권한이 없는 User 는 해당 자원을 조회만(Only Read) 가능하게 설정하거나

조회 조차 불가능하게 설정해서 자원의 무결성(Integrity)을 보장해야 한다.

DRF 에서는 API 엔드포인트를 통해 리소스를 요청한 사람을 식별하는 인증(Authentication)

식별한 사용자 기반으로 리소스의 접근 권한(Permission) 을 부여하여 무결성을 보장할 수 있다.

  • Authentication
    • request 를 보낸 User 식별
    • 누구세요?
  • Permission
    • CRUD 요청의 Accept or Reject
    • 입장권 있으세요?

이번 게시글에서는 DRF 에서 제공하는 AuthenticationPermission 에 대해 알아보자


DRF Authentication

DRF 의 Authentication 는 리소스의 접근 여부를 판단하는 것이 아니라

단순히 request 를 보낸 사용자를 식별 하는 것임을 유의해야 한다.

Authentication Flow

django-rest-framework/rest_framework/views.py
django-rest-framework/rest_framework/authentication.py

DRF 에서 User를 식별 (Authentication) 하기 위한 기본적인 흐름을 알아보자.

먼저, API request (GET, POST, PATCH, PUT, DELETE) 가 오게되면

  1. DRF 의 APIView 클래스에 정의된 dispatch(request) 메소드 호출
    ›› dispatch 메소드는 HTTP 메소드에 따라 적절한 View 메소드를 호출한다.
# django-rest-framework/rest_framework/views.py

class APIView(View):
    # ...생략...
    
    def dispatch(self, request, *args, **kwargs):
        # ...생략...
        
        try:
            self.initial(request, *args, **kwargs)
            # ...생략...

        except Exception as exc:
            response = self.handle_exception(exc)

        self.response = self.finalize_response(request, response, *args, **kwargs)
        return self.response

이때, 인증 시 발생한 예외 응답은 handel_exception() 에서 예외 처리가 가능하다.

  1. dispatch 내에서 initial(request) 메소드 호출
    ›› initial 메소드는 View가 실행되기 전 초기화 작업을 수행한다.
# django-rest-framework/rest_framework/views.py

class APIView(View):
	# ...생략...
    
    def initial(self, request, *args, **kwargs):
        # ...생략...

        # Ensure that the incoming request is permitted
        self.perform_authentication(request)
        self.check_permissions(request)
        self.check_throttles(request)
  1. initial 내에서 perform_authentication(request) 호출
    ›› perform_authentication 메소드는 추출한 인증 정보를 기반으로 user 속성을 추출한다.
# django-rest-framework/rest_framework/views.py

class APIView(View):
	# ...생략...
    
    def perform_authentication(self, request):
        request.user
  1. BaseAuthentication 의 authenticate() 호출
    ›› authenticate() 메소드는 HTTP 요청에서 인증 정보를 추출하고, 추출된 정보를 기반으로 인증 성공 또는 실패를 반환한다.
# django-rest-framework/rest_framework/authentication.py

class BaseAuthentication:
    # 모든 인증 클래스는 반드시 BaseAuthentication 를 상속해야한다.

    def authenticate(self, request):
        # request의 인증 결과는 (user, token) 형식의 튜플로 반환한다!
        raise NotImplementedError(".authenticate() must be overridden.")

    def authenticate_header(self, request):
        # '401 Unauthenticated' 응답에는 'WWW-Authenticate' 헤더를 통해 인증을 요구하고
        # 클라이언트가 권한이 없을 때는 '403 Permission Denied' 을 응답해야한다.
        pass

예를 들어, 내가 Session 기반 인증 방식인 SessionAuthentication 을 선택했다면
SessionAuthentication 클래스는 BaseAuthentication 클래스를 상속하여
authenticate() 메소드를 오버라이딩 해 Session 기반으로 사용자를 추출한다.

위 사진은 DRF 의 SessionAuthentication 방식에서 authenticate() 을 오버라이딩 해 Session 기반으로 User 정보를 반환을 구현한 부분이다.

Authentication Flow 정리

즉, 정리하면

APIView 클래스의 dispatch() 메소드에서 inital(), perform_authentication() 가 순차적으로 호출되고, settings.py 에서 설정한 DEFAULT_AUTHENTICATION_CLASSES 의 인증 방식(ex. Token, Session) 에 따라 authenticate() 메소드가 순차적으로 실행되고

handel_exception() 메소드를 통해 인증 실패에 대한 로직을 구현할 수 있다.

FLOW

  1. API request (GET, POST, PATCH, PUT, DELETE)
  2. call dispatch(request)
  3. call inital(reqest)
  4. call perform_authentication(request)
  5. call authenticate()

Authentication list (인증 종류)

DRF 에서 기본으로 지원하는 Authentication 의 종류는 총 4가지 이다.

  • SessionAuthentication
    • Session 을 통한 인증방식
  • TokenAuthentication
    • Token 를 통한 인증 방식
    • 실제로 DRF의 Token 인증방식보다 JWT(Json Web Token) 인증방식을 많이 사용한다.
    • JWT 는 SimpleJWT 라이브러리를 통해 사용 가능하다.
    • JWT 인증 방식과 왜 Token 보다 많이 쓰이는 지는 다음 게시글에서 자세히 설명하도록 하겠다.
  • BasicAuthentication
    • "username:password" 를 Base64로 인코딩하여 인증하는 방식
    • Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=
  • RemoteUserAuthentication
    • User 정보가 다른 곳에서 관리될 때 REMOTE_USER 헤더를 통해 인증
    • GET /api/endpoint/ HTTP/1.1 Host: example.com REMOTE_USER: kimjihong

위 인증 방식은 APIView 에서 지정해서 사용이 가능하다.

Authentication 전역 설정 및 개별 설정

DRF 에서는 settings.py 의 DEFAULT_AUTHENTICATION_CLASSES 에서 전역 인증 방식을 선택하거나

개별 클래스 또는 함수에서 authentication_classes 를 통해 전역 인증 방식을

오버라이딩 하여 특정 인증 방식을 선택할 수 있다.

  • DEFAULT_AUTHENTICATION_CLASSES 전역 설정
# settings.py
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES ' : [
         'rest_framework.authentication.SessionAuthentication', # 세션 인증
        'rest_framework.authentication.TokenAuthentication', # 토큰 인증
    ]
}

  • CBV (Class Base View)
# account/api/views.py

from rest_framework.views import APIView
from rest_framework.authentication import SessionAuthentication, TokenAuthentication

class UserAPIView(APIView):
    authentication_classes = [SessionAuthentication, TokenAuthentication]
    
    # ...HTTP method 로직...

위 코드에서 authentication_classes 에서 세션 또는 토큰 인증방식으로 User를 식별한다는 의미이다.

  • FBV (Function Base View)
from rest_framework.decorators import authentication_classes, api_view
from rest_framework.authentication import SessionAuthentication, TokenAuthentication

@api_view(['GET'])
@authentication_classes([SessionAuthentication, TokenAuthentication])
def UserAPI(request):

	# ...HTTP method 로직...

함수 기반 뷰 (Function Base View) 에서도 마찬가지로 세션 또는 토큰 인증방식으로 User를 식별할 수 있다.


DRF Permission

DRF Authentication 을 통해 단순히 request 를 보낸 사용자를 식별 하고 끝내게 된다면

모든 인증된 사용자가 모든 리소스에 접근하게 되는 결과가 생기게 된다.

따라서 User 정보와 같이 민감한 리소스에 접근할 때는

Permission 을 설정하여 요청한 User 에 따라 현재 요청을 Accept / Reject 하는 로직이 필요하다.

BasePermission class

django-rest-framework/rest_framework/permissions.py

DRF에서 제공하는 모든 PermissionBasePermission 클래스를 상속하여 구현된다.

# django-rest-framework/rest_framework/permissions.py

class BasePermission(metaclass=BasePermissionMetaclass):
    # 모든 permission 클래스가 상속해야 하는 기본 클래스

	# 클래스 전역 권한 (모든 요청에 대한 권한)
    def has_permission(self, request, view):
        # 권한이 부여되면 return True, 그렇지 않으면 return False
        return True

	# 모델 인스턴스 접근 권한 (특정 객체에 대한 권한)
    def has_object_permission(self, request, view, obj):
        # 권한이 부여되면 return True, 그렇지 않으면 return False
        return True

BasePermission 클래스는 has_permission() 메소드와 has_object_permission() 메소드를 가지고 있어

정의된 Permission 클래스들이 두 메소드를 오버라이딩하여

현재 요청을 Accept / Reject 하거나 특정 모델 인스턴스에 대한 접근 권한을 설정 할 수 있다.

  • has_permission
    • 특정 객체에 의존하지 않는 전역 권한
  • has_object_permission
    • 특정 객체에 의존한 객체 수준의 권한

위 사진은 DRF 의 IsAdminUser 에서 BasePermission 을 상속하여 has_permission() 부분을

request.uer and request.user.is_staff

즉, 인증된 사용자 이며 해당 사용자가 관리자 일 경우에만 True 를 반환하도록 오버라이딩 하여

해당 API request 를 허가(Accept) 하도록 하는 것이다.

Permission Flow

Permission 은 request API 를 보낸 사용자를 인증 과정을 통해 user 정보를 식별한 뒤

아래 과정이 시작된다.

  1. 해당 Permission 클래스의 has_permission() 을 호출
  2. 모델 객체의 특정 권한이 필요한 경우에 has_object_permission() 메소드를 호출
  3. 반환값 True / False 값에 의해 API requestAccept 되거나 Reject

요청 허가 또는 거절 과정이 Bool 타입의 반환 값으로 결정 되므로

해당 클래스 내부의 함수의 반환 값은 Bool 타입으로 정의해야 한다.

Permission List

DRF 에서 제공하는 기본 Permission 종류에는 여러가지가 있지만

나는 BasePermission 을 상속받아서 내가 필요로한 Permission 을 구축하는 편이다...

일단, 자주 쓰이는 DRF 에서 제공하는 Permission 종류는 아래와 같다!

  • AllowAny
    • Permission의 Default 값
    • 모든 request Accept
  • IsAuthenticated
    • Auth 된 사용자만 Accept
  • IsAdminUser
    • 관리자 권한을 가진 사용자만 Accept
  • IsAuthenticatedOrReadOnly
    • Auth 된 사용자는 모든 작업 Accept
    • Auth 되지 않은 사용자는 Read (GET) 만 가능

Permission 전역 설정 및 개별 설정

Authenication 과 마찬가지로

settings.py 의 DEFAULT_PERMISSION_CLASSES 옵션을 통해 전역 설정이 가능하고

CBV 에서는 permission_classes 옵션으로, FBV 에서는 데코레이터를 통해 가능하다.

하지만, Authentication 은 전역 설정으로 사용해도 상관없지만

Permission 은 리소스에 따라 다르게 적용 해야해서 전역 설정보다는 permission_classes 을 통해

개별 설정하는 것이 좋을 것 같다.

# settings.py

# 전역 설정
REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated',
        'rest_framework.permissions.IsAdminUser',
    ),
}


# api/views.py

from rest_framework.views import APIView
from rest_framework.decorators import api_view, permission_classes, authentication_classes
from rest_framework.permissions import IsAuthenticated, AllowAny
from rest_framework.authentication import SessionAuthentication, TokenAuthentication

# FBV
@api_view(['GET'])
@authentication_classes([SessionAuthentication, TokenAuthentication])
@permission_classes([IsAuthenticated]) # 인증된 사용자만 request Accept!
def UserAPI(request):

	# ... HTTP 메소드 로직 ...
    

# CBV
class UserAPIView(APIView):
    authentication_classes = [SessionAuthentication, TokenAuthentication]
    permission_classes = [IsAuthenticated] # 인증된 사용자만 request Accept!
    
    # ... HTTP 메소드 로직 ...


Ref.

Django REST Framework / views.py
Django REST Framework / authenticate.py
Django REST Framework / permissions.py
DRF - API Guide

profile
https://h0ng.dev

0개의 댓글