지난번 API의 접근 권한을 permissions.IsAuthenticated로 설정하면서 인증된 사용자에 한해서 API에 접근할 수 있도록 설정해주었는데 접근 권한과 사용자 인증을 하는 방법이 어떤 것들이 있을지 궁금해졌다.
👉 유입되는 요청을 허용 또는 거부하는 것이 아닌, 단순히 사용자를 식별하는 것을 인증(Authentication) 이라 한다.
👉 인증된 사용자가 어떤한 요청을 허용 또는 거부하는 것은 허가(Permission)이다.
예를 들어, 서비스에 가입된 회원만 확인할 수 있는 페이지는 로그인이 필요하고, 다른 사람의 글을 삭제 및 수정을 하기 위한 권한 등에 있어서는 허가가 필요하다.
authentication(인증)은 특정 서비스를 사용하는 데 있어 사용자의 신원(회원/비회원/관리자 등을 확인)을 확인하는 절차를 의미한다.
지원하는 인증의 종류는 총 5가지가 있다.
SessionAuthentication(세션을 통한 인증 여부 체크)
로그인될 때마다 저장되는 session 정보를 통해 인증
BasicAuthentication(Basic 인증헤더를 통한 인증 수행)
HTTP 제어 header로 넘긴 id와 password를 base64로 encoding (보안 상의 위협이 있을 수 있음, 테스트에 적절)
ex) Authorization: Basic YWxsaWV1czE6MTAyOXNoYWtl
TokenAuthentication(Token 헤더를 통한 인증 수행)
token으로 인증, 인증 요청을 보낼 시 key 값을 되돌려주는 방식 (client-server 관계에서 사용하기에 적절)
ex) Authorization: Token 401f7ac837da42b97f613d789819ff93537bee6a
RemoteUserAuthentication
User 정보가 다른 서비스에서 관리될 때, Remote 인증 (장고 공식문서)
Remote-User 헤더를 통한 인증 수행
Custom Authentication
개발자가 custom하여 authentication을 만들어서 사용할 수도 있다.
만약 모든 view에서 동일한 authentication을 사용하고 싶다면 아래와 같이 DEFAULT_AUTHENTICATION_CLASSES 추가해주면 원하는 인증 방식을 모든 View에서 default로 적용이 된다.
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework_simplejwt.authentication.JWTAuthentication', # jwt 토큰 인증 방식
'rest_framework.authentication.BasicAuthentication', # Basic 인증헤더를 통한 인증
'rest_framework.authentication.SessionAuthentication', # 세션을 통한 인증 방식
]
}
permission(권한)은 특정 서비스를 어느 정도로 이용할 수 있는지에 대한 권한을 의미한다.
django에서 제공하는 기본적인 권한
is_staff : True일 경우 admin 페이지 접속이 가능하며 그 외 일반 유저와 동일
is_active : 계정의 활성 상태를 나타내며 False 일 경우 모든 권한 불허하며 로그인도 되지 않는다.
DRF 에서 제공하는 기본 Permission
AllowAny: 인증/비인증 모두 허용 (default)
IsAuthenticated: 인증된 요청에 대해서만 view 호출
IsAdminUser: Staff User에 대해서만 요청 허용 (User.is_staff가 True여야 함)
IsAuthenticatedOrReadOnly: 비인증 요청에 대해서는 읽기만 허용
DjangoModelPermissions: 사용자 인증과 관련 모델 권한이 할당된 경우 허용 (django.contrib.auth 모델 permission과 관련 있음)
DjangoModelPermissionOrAnonReadonly: DjangoModelPermission과 유사, 비인증 요청에 대해서는 읽기만 허용
DjangoObjectPermissions: 모델에 대한 객체 별로 권한이 할당된 경우 허용
Custom Permission: 개발자가 custom 하게 permission을 만들어서 사용할 수도 있음
모든 View에 대해 동일한 permission을 적용하고 싶을 때는 settings.py에서 아래와 같이 DEFAULT_PERMISSION_CLASSES에 추가해주면 모든 View는 설정한 권한에 따라 접근할 수 있다.
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
# 모든 권한을 허가
'rest_framework.permissions.AllowAny',
# 인증된 사용자에 대해서는 모든 권한을 허가하고, 인증되지 않은 사용자는 읽기 권한만 허용
'rest_framework.permissions.IsAuthenticatedOrReadOnly',
# 인증된 사용자에 대해서만 모든 권한을 허가
'rest_framework.permissions.IsAuthenticated',
]
}
APIView 에서는 permission_classes 을 통해 권한을 지정할 수 있다.
아래와 같이 리스트 형식으로 permission을 설정해준다.
class ProfileView(APIView):
# 인증/비인증 모두에게 제어할 수 있는 권한을 허용 (default)
permission_classes = [permissions.AllowAny]
# 로그인 인증이 된 유저에게만 제어할 수 있는 권한을 허용
permission_classes = [permissions.IsAuthenticated]
# 로그인 인증이 된 유저는 view를 제어할 수 있는 권한을 주고, 인증되지 않은 유저는 읽기 권한만 부여
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
아래는 프로필 조회/수정을 하는 View이며 서비스는 로그인 후 이용할 수 있다는 가정하에 로직을 짜봤다.
우선 get 메소드의 permission은 IsAuthenticated로 설정했기 때문에 로그인 된 사용자만 접근할 수 있고, put 메소드는 해당 프로필이 본인일 경우에만 수정이 가능하도록 IsOwnerOrReadOnly라는 이름으로 permission을 커스텀하여 적용해주었다.
permission을 커스텀하기 위해 permissions.py를 생성하여 아래와 같이 코드를 작성해주면 해당 권한은 SAFE_METHOD(GET, HEAD, OPTIONS)로 요청이 들어온 경우에는 method를 허용을 해주고, 그 외 (PUT)에는 게시글의 user와 로그인된 user가 동일한 경우에만 권한을 허용해준다.
# 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)가 객체(Profile)의 user와 동일한지 확인
return obj.user == request.user
permissions.py에서 커스텀한 IsOwnerOrReadOnly를 import 해준 다음 pul 메소드에 적용하여 해당 프로필이 본인일 경우에만 수정이 가능하도록 권한을 설정했다.
from rest_framework.views import APIView
from users.serializers import ProfileSerializer, ProfileUpdateSerializer
from rest_framework import permissions
from rest_framework.permissions import IsAuthenticated
from .permissions import IsOwnerOrReadOnly
# views.py - 프로필 조회/수정
class ProfileView(APIView):
def get(self, request):
permission_classes = [permissions.IsAuthenticated]
profile = User.objects.get(id=request.user.id)
serializer = ProfileSerializer(profile)
return Response(serializer.data, status=status.HTTP_200_OK)
def put(self, request):
permission_classes = [IsOwnerOrReadOnly]
profile = User.objects.get(id=request.user.id)
serializer = ProfileUpdateSerializer(profile, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
참고한 블로그 링크