파이썬/장고 웹서비스 개발 완벽 가이드 with 리액트 강의를 듣고 정리한 글입니다.
DRF에서는 인증 및 권한 관리를 지원한다.
세션인증, 토큰인증 등을 지원하며 원하는 인증만 사용할 수 있다.
또한 APIView에 permission_classes를 지정해주어서 권한을 관리할 수 있다. (전역 설정도 가능하다.)
뿐만 아니라 permission_classes에 지정할 클래스를 직접 정의할 수도 있다.
이번 포스팅에서는 DRF에서 인증 및 권한을 어떻게 관리하고 커스텀 하는지 알아본다.
유입되는 요청을 허용 / 거부하는 것을 결정하는 것이 아니라, 단순히 인증정보로 유저를 식별하는 것입니다.
유저 식별 → 해당 유저가 어떤 리소스에 대한 어떤 액션을 취할 때 허용/ 거부할 것인지 결정 → 허용이 된다면, 특정 리소스에 대한 요청 횟수를 넘어서는가 확인해서 허용
매 요청 시마다 APIView의 dispatch(request)
호출
APIView의 initial(request)
호출
APIView의 perform_authentication(request)
호출
(참고) 세션 인증은 한번의 로그인으로 끝나지만, API에서는 매번 클라이언트가 서버에 요청시마다 인증을 한다. 즉 매번 인증을 해서 이 요청이 유효한지 검사한다. 인증 방법은, 매 인증 시 json토큰을 보내거나, username/password를 올리는 등 여러가지 방법이 있다.
request의 user
속성 호출 (rest_framework.request.Request 타입)
request의 _authenticate()
호출 - 실제 인증 로직
쉘> http --auth 유저명:암호 --form POST :8000 필드명1:값1 필드명2:값2
장고 기본 앱 django.contrib.auth를 통한 지원 → 세션 인증
from django.conf.urls import url
from django.contrib.auth import views
app_name = 'rest_framework'
urlpatterns = [
url(r'^login/$', views.LoginView.as_view(template_name='rest_framework/login.html'),
name='login'),
url(r'^logout/$', views.LogoutView.as_view(), name='logout'),
]
urlpatterns += [
path('api-auth/', include('rest_framework.urls', namespace='rest_framework')),
]
개체(정보/코드 등)에 대한 접근을 허용하기 위해서, 인증/식별만으로는 충분하지 않습니다. 추가로 각 개체에 대한 허가가 필요합니다.
현재 요청에 대한 허용/거부를 결정합니다. APIView 단위로 지정할 수 있습니다.
- AllowAny (디폴트 전역 설정) : 인증 여부에 상관없이, 뷰 호출 허용
- IsAuthenticated : 인증된 요청에 한해서, 뷰 호출 허용
- IsAdminUser : Staff 인증 요청에 한해서, 뷰 호출 허용
- IsAuthenticatedOrReadOnly : 비인증 요청에게는 읽기 권한만 허용 → 최소 전역 디폴트 설정 권고
- DjangoModelPermissions : 인증된 요청에 한해 뷰 호출을 허용하고, 추가로 장고의 모델단위 Permissions 체크
- DjangoModelPermissionsOrAnonReadOnly : DjangoModelPermissions과 유사하나, 비인증 요청에게는 읽기만 허용
- DjangoObjectPermissions : 비인증 요청은 거부하고, 인증된 요청은 Object에 대한 권한 체크를 수행
아래와 같이, IsAuthenticated
지정 시, 인증된 상황임을 보장받을 수 있습니다.
APIView에는 permission_classes 설정
from rest_framework.permissions import IsAuthenticated
class ExampleView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request, format=None):
content = { 'status': 'request was permitted' }
return Response(content)
@api_view에는 @permission_classes 장식자 설정
from rest_framework.decorators import permission_classes
@api_view(['GET'])
@permission_classes([IsAuthenticated])
def example_view(request, format=None):
content = { 'status': 'request was permitted' }
return Response(content)
# settings.py
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.AllowAny',
]
}
모든 Permission 클래스는 다음 2가지 함수를 선택적으로 구현
has_permission(request, view)
has_object_permission(request, view, obj)
# https://github.com/encode/django-rest-framework/blob/3.10.1/rest_framework/permissions.py
SAFE_METHODS = ('GET', 'HEAD', 'OPTIONS')
class AllowAny(BasePermission):
def has_permission(self, request, view):
return True # 무조건 허용
class IsAuthenticated(BasePermission):
def has_permission(self, request, view):
return request.user and request.user.is_authenticated # 유저가 있고, 로그인 된 유저만 허용
class IsAdminUser(BasePermission):
def has_permission(self, request, view):
return request.user and request.user.is_staff # 스태프 여부 판단
# https://github.com/encode/django-rest-framework/blob/3.10.1/rest_framework/permissions.py
class DjangoObjectPermissions(DjangoModelPermissions): # 장고 기본의 퍼미션을 체크한다.
# ...
def has_object_permission(self, request, view, obj):
# authentication checks have already executed via has_permission
queryset = self._queryset(view)
model_cls = queryset.model
user = request.user
perms = self.get_required_object_permissions(request.method, model_cls)
if not user.has_perms(perms, obj):
if request.method in SAFE_METHODS:
raise Http404
read_perms = self.get_required_object_permissions('GET', model_cls)
if not user.has_perms(read_perms, obj):
raise Http404
return False
return True
모델에 author 필드가 있다고 가정
from rest_framework import permissions
class IsAuthorOrReadonly(permissions.BasePermission):
# 인증된 유저에 한해, 목록조회/포스팅등록을 허용
def has_permission(self, request, view):
return request.user.is_authenticated # 인증이 되야만 허용
# 작성자에 한해, Record에 대한 수정/삭제 허용
def has_object_permission(self, request, view, obj):
# 조회 요청(GET, HEAD, OPTIONS) 에 대해서는 인증여부에 상관없이 허용
if request.method in permissions.SAFE_METHODS: # SAFE_METHODS = ('GET', HEAD', 'OPTIONS')
return True
# PUT, DELETE 요청에 대해, 작성자일 경우에만 요청 허용
return obj.author == request.user
Django Shell에서 간단한 비밀번호를 가진 유저 생성하기
# python shell
>>> from django.contrib.auth import get_user_model
>>> User = get_user_model()
>>> User.objects.create_user?
Signature:
User.objects.create_user(
username,
email=None,
password=None,
**extra_fields,
)
Docstring: <no docstring>
...
>>> User.objects.create_user('user2', password='1234')
<User: user2>
인증된 유저로 요청하기
http --auth user2:1234 http://localhost:8000/post/1/ #
http --auth user2:1234 POST http://localhost:8000/post/ message="user2가 지정하는 첫 메시지"
http --auth user2:1234 PATCH http://localhost:8000/post/6 message="user2가 수정하는 첫 메시지"
http --auth user2:1234 PATCH http://localhost:8000/post/1 message="수정?" # author가 다르면, 거부됨
http --auth user2:1234 gethttp://localhost:8000/post/1 # author가 달라도 조회는 가능!
from rest_framework import permissions
class IsAuthorUpdateOrReadonly(permissions.BasePermission):
def has_permission(self, request, view):
return request.user.is_authenticated
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return True
if (request.method == 'DELETE'):
return request.user.is_superuser # 또는 request.user.is_staff
return obj.author == request.user