DRF(Django REST FrameWork) 를 통해서 어떤 리소스의 CRUD를 구현하게 된다면
API request를 보낸 User 를 식별하고 해당 리소스 즉, 자원에 대한 접근 권한을 설정하여
권한이 없는 User 는 해당 자원을 조회만(Only Read) 가능하게 설정하거나
조회 조차 불가능하게 설정해서 자원의 무결성(Integrity)을 보장해야 한다.
DRF 에서는 API 엔드포인트를 통해 리소스를 요청한 사람을 식별하는 인증(Authentication) 과
식별한 사용자 기반으로 리소스의 접근 권한(Permission) 을 부여하여 무결성을 보장할 수 있다.
이번 게시글에서는 DRF 에서 제공하는 Authentication 과 Permission 에 대해 알아보자
DRF 의 Authentication 는 리소스의 접근 여부를 판단하는 것이 아니라
단순히 request 를 보낸 사용자를 식별 하는 것임을 유의해야 한다.
django-rest-framework/rest_framework/views.py
django-rest-framework/rest_framework/authentication.py
DRF 에서 User를 식별 (Authentication) 하기 위한 기본적인 흐름을 알아보자.
먼저, API request (GET, POST, PATCH, PUT, DELETE) 가 오게되면
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()
에서 예외 처리가 가능하다.
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)
initial
내에서 perform_authentication(request)
호출perform_authentication
메소드는 추출한 인증 정보를 기반으로 user
속성을 추출한다.# django-rest-framework/rest_framework/views.py
class APIView(View):
# ...생략...
def perform_authentication(self, request):
request.user
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 정보를 반환을 구현한 부분이다.
즉, 정리하면
APIView 클래스의 dispatch()
메소드에서 inital()
, perform_authentication()
가 순차적으로 호출되고, settings.py 에서 설정한 DEFAULT_AUTHENTICATION_CLASSES
의 인증 방식(ex. Token, Session) 에 따라 authenticate()
메소드가 순차적으로 실행되고
handel_exception()
메소드를 통해 인증 실패에 대한 로직을 구현할 수 있다.
FLOW
- API request (GET, POST, PATCH, PUT, DELETE)
- call
dispatch(request)
- call
inital(reqest)
- call
perform_authentication(request)
- call
authenticate()
DRF 에서 기본으로 지원하는 Authentication 의 종류는 총 4가지 이다.
Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=
GET /api/endpoint/ HTTP/1.1 Host: example.com REMOTE_USER: kimjihong
위 인증 방식은 APIView
에서 지정해서 사용이 가능하다.
DRF 에서는 settings.py 의 DEFAULT_AUTHENTICATION_CLASSES
에서 전역 인증 방식을 선택하거나
개별 클래스 또는 함수에서 authentication_classes
를 통해 전역 인증 방식을
오버라이딩 하여 특정 인증 방식을 선택할 수 있다.
# settings.py
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES ' : [
'rest_framework.authentication.SessionAuthentication', # 세션 인증
'rest_framework.authentication.TokenAuthentication', # 토큰 인증
]
}
# 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를 식별한다는 의미이다.
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 Authentication 을 통해 단순히 request 를 보낸 사용자를 식별 하고 끝내게 된다면
모든 인증된 사용자가 모든 리소스에 접근하게 되는 결과가 생기게 된다.
따라서 User 정보와 같이 민감한 리소스에 접근할 때는
Permission 을 설정하여 요청한 User 에 따라 현재 요청을 Accept / Reject 하는 로직이 필요하다.
django-rest-framework/rest_framework/permissions.py
DRF에서 제공하는 모든 Permission 은 BasePermission
클래스를 상속하여 구현된다.
# 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 하거나 특정 모델 인스턴스에 대한 접근 권한을 설정 할 수 있다.
위 사진은 DRF 의 IsAdminUser
에서 BasePermission 을 상속하여 has_permission()
부분을
request.uer and request.user.is_staff
즉, 인증된 사용자 이며 해당 사용자가 관리자 일 경우에만 True
를 반환하도록 오버라이딩 하여
해당 API request 를 허가(Accept) 하도록 하는 것이다.
Permission 은 request API 를 보낸 사용자를 인증 과정을 통해 user 정보를 식별한 뒤
아래 과정이 시작된다.
has_permission()
을 호출has_object_permission()
메소드를 호출요청 허가 또는 거절 과정이 Bool
타입의 반환 값으로 결정 되므로
해당 클래스 내부의 함수의 반환 값은 Bool
타입으로 정의해야 한다.
DRF 에서 제공하는 기본 Permission 종류에는 여러가지가 있지만
나는 BasePermission
을 상속받아서 내가 필요로한 Permission 을 구축하는 편이다...
일단, 자주 쓰이는 DRF 에서 제공하는 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 메소드 로직 ...
Django REST Framework / views.py
Django REST Framework / authenticate.py
Django REST Framework / permissions.py
DRF - API Guide