객체 레벨 권한(Object-level Permission) 직접 만들기

DRF에서 특정 객체에 대한 세밀한 권한 제어를 위해 커스텀 권한 클래스를 직접 구현하는 방법

객체 레벨 권한이 필요한 이유

  • 사용자는 자신이 작성한 게시글만 수정/삭제 가능

  • 관리자는 자신이 담당하는 상품 정보만 변경 가능

  • 팀원은 자신이 속한 프로젝트의 이슈만 조회 가능

  • IsAdminUserIsAuthenticated 같은 전역 권한으로는 세밀한 제어 불가능

DRF 권한 검사 흐름

  • has_permission: View에 대한 전역적 접근 권한 검사 (객체 조회 전)

  • has_object_permission: 특정 객체에 대한 접근 권한 검사 (객체 조회 후)

  • has_object_permission은 List API에서는 호출되지 않음 (Detail API에서만 호출)

IsOwnerOrReadOnly 권한 클래스 구현

모델 정의

class Post(models.Model):
    author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    title = models.CharField(max_length=100)
    content = models.TextField()

커스텀 권한 클래스

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
        
        # 쓰기 권한은 소유자만 허용
        return obj.author == request.user

ViewSet에 적용

class PostViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer
    permission_classes = [IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly]

실무 팁

관리자 예외 처리

def has_object_permission(self, request, view, obj):
    if request.method in permissions.SAFE_METHODS:
        return True
    
    if request.user.is_staff:
        return True
    
    return obj.author == request.user

List 뷰 필터링

def get_queryset(self):
    user = self.request.user
    if user.is_staff:
        return Post.objects.all()
    return Post.objects.filter(author=user)

N+1 쿼리 방지

queryset = Post.objects.select_related('author').all()

범용 소유권 확인

def has_object_permission(self, request, view, obj):
    if request.method in permissions.SAFE_METHODS:
        return True
    
    if hasattr(obj, 'author'):
        return obj.author == request.user
    if hasattr(obj, 'user'):
        return obj.user == request.user
    
    return False

Last updated