DRF Authentication과 Permission

2022. 7. 14. 17:52강의 정리/Django REST Framework

반응형

인증


유입되는 요청을 허용 / 거부하는 것을 결정하는 것이 아니라, 단순히 인증 정보로 유저를 식별하는 것입니다.

  • Authentication : 유저 식별(ex: 업주인지 고객 유저인지)
  • Permissions : 유저의 리소스에 대한 각 요청에 대한 허용/거부(ex: 특정 유저만 글 쓰기)
  • Throttling : 특정 유저가 일정 시간 동안에 허용할 최대 요청 횟수(ex: 글 쓰기 제한)

인증 프로세스

  1. 유저 식별 
  2. 해당 유저가 리소스에 대한 어떤 액션을 취할 때 허용할 것인지, 거부할 것인지
  3. 허용된다면, 특정 리소스에 대한 요청 횟수를 넘어서지 않는지

 

인증 처리 순서


  1. 매 요청 시마다 APIView의 dispatch(request) 호출
  2. APIView의 initial(request) 호출
  3. APIView의 perform_authentication(request) 호출
    • 세션 인증은 로그인을 할 때만 인증을 하고, 인증을 한 내역을 세션에 담아두고 활용하는데, API에서는 매번 클라이언트가 서버에 요청을 보낼 때 마다 매번 인증을 한다. 즉 매번 인증을 해서 이 요청이 유효한지 검사한다.
    • 인증 방법은, 매 인증 시 json 토큰을 보내거나, 매번 username/password를 올리는 등 여러가지 방법이 있다.
  4. request의 user속성 호출 (rest_framework.request.Request 타입)
  5. request의 _authenticate() 호출 - 실제 인증 로직

 

 

 

 

 


 

지원하는 인증 (Authentication)의 종류


 

SessionAuthentication

  • 세션을 통한 인증, APIView에서 디폴트 지정
  • 한번만 로그인 하면 그 정보를 세션에 담아두고, 저장된 세션을 통해 인증하는 것

 

BasicAuthentication

  • 클라이언트가 서버에게 매 요청시마다 요청 헤더에 인증 정보를 보내는 것
  • Base 인증 헤더를 통한 인증 (예 → Authorization: Basic YWxsaWV1czE6MTAyOXNoYWtl )
    • HTTPie를 통한 요청 : 쉘> http --auth 유저명:암호 --form POST :8000 필드명1:값1 필드명2:값2
  • 암호는 base64 인코딩되어 지정
  • http요청일 경우 누구나 스니핑 해서 볼 수 있음. -> 그래서 반드시 https를 사용해야 한다.

 

TokenAuthentication

  • Basic과 똑같이 요청헤더에 인증 정보를 보내는데 대신, 비밀번호가 아닌 유저마다 Unique한 랜덤한 문자열을 통해 인증한다.
  • Token 헤더를 통한 인증 (예 → Authorization: Token 401f7ac837da42b97f613d789819ff93537bee6a)
  • 장점은 구현이 쉽다는 점, 단점은 Token의 만료 기한이 정해지지않은 것과, 유저별 하나의 Token만 제공되는 것(유출될 시 큰일)
  • 마찬가지로 http요청일 경우 누구나 스니핑 해서 볼 수 있음.
  • DRF에서 지원하는 Token은 예시로만 보면 되고, 실 구현시에는 JWT과 세션 인증으로 구현

 

RemoteUserAuthentication

  • User가 다른 서비스에서 관리될 때, Remote 인증
  • Remote-User 헤더를 통한 인증 수행

 

 

 

ModelView 및 APIVIew에서 인증과 허가를 구현하기 위해서는 아래 예시 코드와 같이 작성하면 됩니다.

# Blog의 목록, detail 보여주기, 수정하기, 삭제하기 모두 가능
class BlogViewSet(viewsets.ModelViewSet):
    # authentication 추가
    authentication_classes = [BasicAuthentication, SessionAuthentication]
    
    # permission 추가
    permission_classes = [IsAuthenticatedOrReadOnly]
    queryset = Blog.objects.all()
    serializer_class = BlogSerializer

 

 

 

 


웹브라우저를 통한 API 접근에서 로그인/로그아웃 지원

 


장고 기본 앱 django.contrib.auth를 통한 지원 → 세션 인증

 

rest_framework/urls.py

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'),
]

 

프로젝트 내 urls.py

urlpatterns += [
    path('api-auth/', include('rest_framework.urls', namespace='rest_framework')),
]

 

 

 


 

허가와 권한

 

인증과 허가


개체(정보/코드 등)에 대한 접근을 허용하기 위해서, 인증/식별만으로는 충분하지 않습니다. 추가로 각 개체에 대한 허가가 필요합니다.

 

 

 


 

DRF의 Permission 시스템

 


현재 요청에 대한 허용/거부를 결정합니다. APIView 단위로 지정할 수 있습니다.

  • AllowAny (디폴트 전역 설정) : 인증 여부에 상관없이, 뷰 호출 허용 -> 적합하지 않다.
  • IsAuthenticated : 인증된 요청에 한해서, 뷰 호출 허용 -> 비로그인자는 조회도 안되게 할 때 전역 디폴트 권고
    • 해당 ViewSet에 접근을 위해서는 무조건 로그인 되어있어야 합니다.
  • IsAdminUser : Staff 인증 요청에 한해서, 뷰 호출 허용
    • is_staff은 admin 페이지에 접근할 수 있는 권한이다. 단 장고 기본 모델에 있는 적절한 Permission이 주어져야 함
  • IsAuthenticatedOrReadOnly : 비인증 요청에게는 읽기 권한만 허용 → 최소 전역 디폴트 설정 권고
  • DjangoModelPermissions : 인증된 요청에 한해 뷰 호출을 허용하고, 추가로 장고의 모델단위 Permissions 체크
    • 장고 Auth의 Permission과 DRF의 Permission은 다르다.
    • 해당 Permission은 Django Auth Permission이다.
  • DjangoModelPermissionsOrAnonReadOnly : DjangoModelPermissions과 유사하나, 비인증 요청에게는 읽기만 허용
  • DjangoObjectPermissions : 비인증 요청은 거부하고, 인증된 요청은 Object에 대한 권한 체크를 수행

 

 

permission_classes 지정


 

 

CBV 즉, 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)

 

FBV 즉, @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


모든 Permission 클래스는 다음 2가지 함수를 선택적으로 구현되어있고, 커스텀 Permission 시 구현해야한다.

 

  • has_permission(request, view)
    • APIView 접근 시, 체크
    • 거의 모든 Permission 클래스에서 구현하며, 로직에 따라 True/False 반환
    • 항상 APIView 클래스를 상속받아서 DRF를 사용해야 함

 

  • has_object_permission(request, view, obj)
    • APIView의 get_object 함수를 통해 object 획득 시에 체크
    • 브라우저를 통한 API 접근에서 CREATE/UPDATE Form 노출 시에 체크
    • DjangoObjectPermissions에서 구현하며, 로직에 따라 True/False 반환
    • 특정 Object를 획득할 때는 반드시 해당 APIView에서 self.get_object를 이용하여 object를 얻어야 한다.

 

룰 기반 퍼미션 지원 : https://github.com/dbkaplan/dry-rest-permissions

 

GitHub - dbkaplan/dry-rest-permissions: Rules based permissions for the Django Rest Framework

Rules based permissions for the Django Rest Framework - GitHub - dbkaplan/dry-rest-permissions: Rules based permissions for the Django Rest Framework

github.com

 

 


 

DRF의 Permissions 코드 살펴보기

# 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
  • 장고 기본 Permission은 Django Admin Page에서 유저의 Permission에서 확인할 수 있다.

 


 

 

커스텀 퍼미션 예시 : 포스팅 작성자가 아니라면, 읽기 권한만 부여하기.


모델에 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
  • Head method -> 응답 헤더만 받는 것
  • Options method -> 엔드포인트에서 어떠한 메소드를 지원하는지 알려주는 것

 

 

포스팅 작성자에게 수정권한만 부여하고, 삭제는 superuser에게만 가능케 하는 Permission 클래스 정의


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

 

 

권한 확인하기


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가 달라도 조회는 가능!

 

 

 

 

ex) 고객 유저 Permission


from rest_framework import permissions

class IsCustomer(permissions.BasePermission):
    # 고객 유저에 한해, 목록조회/포스팅등록을 허용
    def has_permission(self, request, view):
        return request.user.IsCustomer  # Customer 필드가 True 이어야만 허용

    # 고객 유저에 한해, 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 REST Framework' 카테고리의 다른 글

DRF Token인증 적용하기  (0) 2022.07.15
DRF Pagination  (0) 2022.07.14
DRF Serializer를 통한 유효성 검사 및 저장  (0) 2022.07.14
From과 Serializer 관점에서 DRF 비교  (0) 2022.07.13
DRF ViewSet과 Router  (0) 2022.07.13