[D.R.F] 게시판에 Authentication과 Permission 적용하기
안녕하세요 (ง •_•)ง
오늘은 지난 시간에 실습했던 게시판 CRUD에 이어 authentication과 permission에 대해 알아보도록 하겠습니다.
게시판 CRUD 구현에 대한 부분은 아래의 링크를 참고해주세요!
지난 시간에 아래와 같은 에러가 발생함을 확인하였습니다.
저희가 구현한 게시판의 게시글에는 작성한 user 정보를 담고 있는데,
로그인하지 않은 사용자가 게시글을 작성(POST)하려고 하면 user 정보가 없기 때문에 아래와 같은 문제가 발생하였습니다.
실제 서비스를 생각해보아도 (익명 게시판이 아닌 경우) 비회원에게 글을 작성할 수 있게 해서는 안 되겠죠.
이럴 때 사용할 수 있는 개념이 authentication과 permission 입니다.
우선 authentication에 대해 먼저 알아봅시다!
authentication(인증)은 특정 서비스를 사용하는 데 있어 사용자의 신원(회원/비회원/관리자 등을 확인)을 확인하는 절차를 의미합니다.
즉 user의 id와 password를 확인하는 것이라고 생각할 수 있습니다.
authentication과 관련된 공식 문서는 아래의 링크를 통해 확인할 수 있습니다.
Django REST Framework authentication의 종류로는 크게
1. BasicAuthentication: HTTP 제어 header로 넘긴 id와 password를 base64로 encoding (보안 상의 위협이 있을 수 있음, 테스트에 적절)
2. TokenAuthentication: 해당 방식은 token으로 인증, 인증 요청을 보낼 시 key 값을 되돌려주는 방식 (client-server 관계에서 사용하기에 적절)
3. SessionAuthentication: 로그인될 때마다 저장되는 session 정보를 통해 인증
4. RemoteAuthentication: user 정보가 다른 서비스에서 관리될 때 쓰이는 방식
5. Custom Authentication: 개발자가 custom 하게 authentication을 만들어서 사용할 수도 있음
이 있습니다.
저희는 실제 서비스를 만드는 것이 아니기 때문에
흔하게 사용되는 BasicAuthentication과 SessionAuthentication 조합으로 설정해보도록 하겠습니다.
+) 이후 TokenAuthentication + postman 사용법에 대한 내용을 업로드할 예정입니다!
views.py
from .models import Blog
from .serializers import BlogSerializer
from rest_framework import viewsets
from rest_framework.authentication import SessionAuthentication, BasicAuthentication
# Blog의 목록, detail 보여주기, 수정하기, 삭제하기 모두 가능
class BlogViewSet(viewsets.ModelViewSet):
# authentication 추가
authentication_classes = [BasicAuthentication, SessionAuthentication]
queryset = Blog.objects.all()
serializer_class = BlogSerializer
def perform_create(self, serializer):
serializer.save(user = self.request.user)
우선 SessionAuthentication과 BasicAuthentication을 사용하기 위해 authentication을 import를 해주었습니다.
이후 제가 게시글 기능을 구현했던 BlogViewSet의 authentication_classes를 해당 authentication로 설정해주었습니다.
만약 모든 view에서 동일한 authentication을 사용하고 싶다면
아래와 같이 settings.py에 한 번만 선언해주어도 됩니다.
settings.py
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
]
}
다음은 permission입니다!
permission(권한)은 특정 서비스를 어느 정도로 이용할 수 있는지에 대한 권한을 의미합니다.
예를 들어 'A 권한을 가진 사용자는 전체 게시글을 다 볼 수 있다'라는 등의 예시가 있을 수 있겠죠.
permission과 관련된 자세한 내용은 아래의 공식 문서를 통해 확인할 수 있습니다.
Django REST Framework에서 제공하는 permission의 종류로는
1. AllowAny: 인증/비인증 모두 허용 (default)
2. IsAuthenticated: 인증된 요청에 대해서만 view 호출
3. IsAdminUser: Staff User에 대해서만 요청 허용 (User.is_staff가 True여야 함)
4. IsAuthenticatedOrReadOnly: 비인증 요청에 대해서는 읽기만 허용
5. DjangoModelPermissions: 사용자 인증과 관련 모델 권한이 할당된 경우 허용 (django.contrib.auth 모델 permission과 관련 있음)
6. DjangoModelPermissionOrAnonReadonly: DjangoModelPermission과 유사, 비인증 요청에 대해서는 읽기만 허용
7. DjangoObjectPermissions: 모델에 대한 객체 별로 권한이 할당된 경우 허용
8. Custom Permission: 개발자가 custom 하게 permission을 만들어서 사용할 수도 있음
이 있습니다.
저희가 현재 구현하고자 하는 권한은 로그인을 한 사용자만 글을 작성할 수 있고,
비회원은 글을 볼 수만 있어야 하기 때문에
IsAuthenticatedOrReadOnly 권한을 사용하도록 하겠습니다.
views.py
from .models import Blog
from .serializers import BlogSerializer
from rest_framework import viewsets
from rest_framework.authentication import SessionAuthentication, BasicAuthentication
from rest_framework.permissions import IsAuthenticatedOrReadOnly
# Blog의 목록, detail 보여주기, 수정하기, 삭제하기 모두 가능
class BlogViewSet(viewsets.ModelViewSet):
authentication_classes = [BasicAuthentication, SessionAuthentication]
# permission 추가
permission_classes = [IsAuthenticatedOrReadOnly]
queryset = Blog.objects.all()
serializer_class = BlogSerializer
def perform_create(self, serializer):
serializer.save(user = self.request.user)
우선 IsAuthenticatedOrReadOnly를 사용하기 위해 permissions를 import를 해주었습니다.
이후 BlogViewSet의 permission_classes를 IsAuthenticatedOrReadOnly로 설정해주었습니다.
authentication과 마찬가지로 만약 모든 view에서 동일한 permission을 사용하고 싶다면,
아래와 같이 settings.py에 한 번만 선언해주어도 됩니다.
settings.py
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
]
}
이렇게 설정해준 후 서버를 실행시키고 '127.0.01:8000/blog'에 접속을 해보면,
위의 사진과 같이 로그인하지 않은 사용자는 글을 작성할 수 있는 권한이 없는 것을 알 수 있습니다.
아직 인증이 되지 않았기 때문에 read-only로만 접근이 가능한 것입니다.
이후 오른쪽 상단의 Log in 버튼을 통해 로그인을 한 후 동일한 페이지에 접근하면
아래의 사진과 같이 글을 작성할 수 있게 된다는 것도 확인이 가능합니다.
이는 로그인을 통해 authentication에 성공하였기 때문에 가능한 것입니다.
하지만 여기까지만 구현을 하게 되면 아주 큰 결함(!)이 존재하게 됩니다.
일반적인 게시글의 경우 글을 작성한 사람만 해당 게시글을 수정/삭제할 수 있어야 하는데,
현재는 로그인을 한 사용자라면 누구나 게시글을 수정, 삭제를 할 수 있습니다.
이는 BlogViewSet의 permission이 IsAuthenticatedOrReadOnly로만 설정되어 있기 때문입니다.
실제 위의 사진을 보면 현재 test2로 접속되어 있는데,
test라는 user가 작성한 게시글이 수정 가능함을 알 수 있습니다.
따라서 우리는 현재 로그인한 사용자가 해당 게시글을 작성한 사용자와 동일할 때에만
게시글을 수정(PUT, PATCH), 삭제(DELETE)할 수 있도록 permissions를 추가해주어야 합니다.
앞서서 언급했던 custom permission을 사용해보도록 하겠습니다.
우선 프로젝트 내부(test_project)에 project 앱 내부에 permissions.py라는 파일을 생성해줍니다.
permissions.py
from rest_framework import permissions
class IsOwnerOrReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
# 읽기 권한 요청이 들어오면 허용
if request.method in permissions.SAFE_METHODS:
return True
# 요청자(request.user)가 객체(Blog)의 user와 동일한지 확인
return obj.user == request.user
IsOwnerOrReadOnly라는 이름의 permission을 하나 만들어줍니다.
해당 권한은 우선 SAFE_METHOD(GET, HEAD, OPTIONS)로 요청이 들어온 경우에는 method를 허용을 해주고,
그 외(PUT, PATCH, DELETE)에는 게시글의 user와 로그인된 user가 동일한 경우에만 권한을 허용해줍니다.
+) IsOwnerOrReadOnly에 대한 부분은 앞서 언급한 Django 공식 문서에서도 확인할 수 있습니다.
views.py
from .models import Blog
from .serializers import BlogSerializer
from rest_framework import viewsets
from rest_framework.authentication import SessionAuthentication, BasicAuthentication
from rest_framework.permissions import IsAuthenticatedOrReadOnly
from .permissions import IsOwnerOrReadOnly
class BlogViewSet(viewsets.ModelViewSet):
authentication_classes = [BasicAuthentication, SessionAuthentication]
permission_classes = [IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly]
queryset = Blog.objects.all()
serializer_class = BlogSerializer
def perform_create(self, serializer):
serializer.save(user = self.request.user)
이후 생성한 IsOwnerOrReadOnly 권한을 import 한 후 이를 permission_classes에 추가해줍니다.
코드를 저장한 후 서버를 다시 실행해보면,
아래와 같이 내가 작성한 게시글이 아닌 경우에는 수정할 수 없음을 알 수 있습니다.
문제 해결...!
그럼 다음 시간에는 게시글 댓글과 좋아요 기능을 구현해보도록 하겠습니다 !