DRF Authentication과 Permission
2022. 7. 14. 17:52ㆍ강의 정리/Django REST Framework
반응형
인증
유입되는 요청을 허용 / 거부하는 것을 결정하는 것이 아니라, 단순히 인증 정보로 유저를 식별하는 것입니다.
- Authentication : 유저 식별(ex: 업주인지 고객 유저인지)
- Permissions : 유저의 리소스에 대한 각 요청에 대한 허용/거부(ex: 특정 유저만 글 쓰기)
- Throttling : 특정 유저가 일정 시간 동안에 허용할 최대 요청 횟수(ex: 글 쓰기 제한)
인증 프로세스
- 유저 식별
- 해당 유저가 리소스에 대한 어떤 액션을 취할 때 허용할 것인지, 거부할 것인지
- 허용된다면, 특정 리소스에 대한 요청 횟수를 넘어서지 않는지
인증 처리 순서
- 매 요청 시마다 APIView의 dispatch(request) 호출
- APIView의 initial(request) 호출
- APIView의 perform_authentication(request) 호출
- 세션 인증은 로그인을 할 때만 인증을 하고, 인증을 한 내역을 세션에 담아두고 활용하는데, API에서는 매번 클라이언트가 서버에 요청을 보낼 때 마다 매번 인증을 한다. 즉 매번 인증을 해서 이 요청이 유효한지 검사한다.
- 인증 방법은, 매 인증 시 json 토큰을 보내거나, 매번 username/password를 올리는 등 여러가지 방법이 있다.
- request의 user속성 호출 (rest_framework.request.Request 타입)
- 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
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 |