튜토리얼4: Authentication & Permissions

현재 API에는 코드 snippet을 편집하거나 삭제할 수있는 사람에 대한 제한이 없습니다. 다음 사항을 확인하기 위해 8좀 더 발전된 행동을 원합니다.

  • 코드 snippet은 항상 작성자와 연결됩니다.
  • 인증된 사용자만 snippet을 만들수 있습니다.
  • snippet 작성자 만 업데이트 또는 삭제할 수 있습니다.
  • 인증되지 않은 요청에는 완전한 읽기 전용 액세스 권한이 있어야합니다.

모델에 정보 추가

우리는 Snippet 모델 클래스에 몇 가지 변경을 가할 것입니다. 먼저 두 개의 필드를 추가해 보겠습니다. 이 필드 중 하나는 코드 snippet을 만든 사용자를 나타내는 데 사용됩니다. 다른 필드는 코드의 강조 표시된 HTML 표현을 저장하는 데 사용됩니다.

models.pySnippet 모델에 다음 두 필드를 추가하십시오.

owner = models.ForeignKey('auth.User', related_name='snippets', on_delete=models.CASCADE)
highlighted = models.TextField()

또한 모델이 저장 될 때 pygments 코드 강조 라이브러리를 사용하여 강조 표시된 필드가 채워지도록해야합니다.

추가 import

from pygments.lexers import get_lexer_by_name
from pygments.formatters.html import HtmlFormatter
from pygments import highlight

이제 모델 클래스에 .save() 메서드를 추가 할 수 있습니다.

def save(self, *args, **kwargs):
    """
    Use the `pygments` library to create a highlighted HTML
    representation of the code snippet.
    """
    lexer = get_lexer_by_name(self.language)
    linenos = 'table' if self.linenos else False
    options = {'title': self.title} if self.title else {}
    formatter = HtmlFormatter(style=self.style, linenos=linenos,
                              full=True, **options)
    self.highlighted = highlight(self.code, lexer, formatter)
    super(Snippet, self).save(*args, **kwargs)

모든 작업이 완료되면 데이터베이스 테이블을 업데이트해야합니다. 일반적으로 데이터베이스 마이그레이션을 만들려고하는데, 이 튜토리얼에서는 데이터베이스를 삭제하고 다시 시작해보겠습니다.

rm -f db.sqlite3
rm -r snippets/migrations
python manage.py makemigrations snippets
python manage.py migrate

API 테스트를 위해 몇 명의 사용자를 생성 할 수도 있습니다. 이를 수행하는 가장 빠른 방법은 createsuperuser 명령을 사용하는 것입니다.

python manage.py createsuperuser

유저 모델에 엔드포인트 추가

이제 일부 사용자가 작업 할 수있게되었으므로 해당 사용자의 표현을 API에 추가하는 것이 좋습니다. 새로운 시리얼 라이저를 만드는 것은 쉽습니다. serializers.py에 추가

from django.contrib.auth.models import User

class UserSerializer(serializers.ModelSerializer):
    snippets = serializers.PrimaryKeyRelatedField(many=True, queryset=Snippet.objects.all())

    class Meta:
        model = User
        fields = ('id', 'username', 'snippets')

'snippets'는 User 모델에서 역방향 관계이므로 ModelSerializer 클래스를 사용할 때 기본적으로 포함되지 않으므로 명시 적 필드를 추가해야했습니다.

우리는 또한 views.py에 몇가지 보기를 추가 할 것입니다. 사용자 표현에 대해 읽기전용 보기만 사용하고자하므로 ListAPIViewRetrieveAPIView 제네릭 클래스 기반 뷰를 사용합니다.

from django.contrib.auth.models import User


class UserList(generics.ListAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer


class UserDetail(generics.RetrieveAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer

UserSerializer import

from snippets.seralizers import UserSerializer

마지막으로 이러한 뷰를 URL conf에서 참조하여 API에 추가해야합니다. urls.py의 패턴에 다음을 추가하십시오.

url(r'^users/$', views.UserList.as_view()),
url(r'^users/(?P<pk>[0-9]+)/$', views.UserDetail.as_view()),

snippet을 유저와 연결

지금 코드 snippet을 만든 경우 snippet을 만든 사용자와 snippet 인스턴스를 연결할 방법이 없습니다. 사용자는 일련화 된 표현의 일부로 전송되지 않고 들어오는 요청의 특성입니다.

처리하는 방법은 snippet 뷰에서 .perform_create() 메서드를 재정의하여 인스턴스 저장을 관리하는 방법을 수정하고 들어오는 요청이나 요청 된 URL에 암시 적으로 포함 된 정보를 처리하는 것입니다.

SnippetList 뷰 클래스에서 다음 메서드를 추가하십시오.

def perform_create(self, serializer):
    serializer.save(owner=self.request.user)

serializer의 create() 메소드에는 요청의 유효성이 검사 된 데이터와 함께 추가 'owner'필드가 전달됩니다.

Serializer 업데이트

이제 snippet은 snippet을 만든 사용자와 연결되어 있으므로 이를 반영하도록 SnippetSerializer를 업데이트합시다. serializers.py의 serializer 정의에 다음 필드를 추가합니다.

owner = serializers.ReadOnlyField(source='owner.username')

내부 Meta 클래스의 필드 목록에 'owner',도 추가해야합니다.

이 분야는 꽤 흥미로운 일을하고 있습니다. source 인수는 필드를 채우는 데 사용되는 속성을 제어하며 직렬화 된 인스턴스의 모든 속성을 지정할 수 있습니다. 또한 위의 점으로 구분 된 표기법을 사용할 수 있습니다. 이 경우 Django의 템플릿 언어에서 사용되는 것과 비슷한 방식으로 주어진 속성을 트래버스합니다.

우리가 추가 한 필드는 형식화되지 않은 ReadOnlyField 클래스입니다. CharField, BooleanField 등의 다른 형식화 된 필드와 달리 형식화되지 않은 ReadOnlyField는 항상 읽기 전용이며 직렬화 된 표현에 사용되지만 실제로는 사용되지 않습니다. deserialize 될 때 모델 인스턴스를 업데이트하는 데 사용됩니다. 여기에서도 CharField(read_only=True)를 사용할 수 있습니다.

뷰에 필요한 권한 추가

코드 snippet이 사용자와 연결되어 있으므로 인증 된 사용자 만 코드 snippet을 생성, 업데이트 및 삭제할 수 있습니다.

REST 프레임워크에는 주어진 뷰에 액세스 할 수있는 사용자를 제한하는 데 사용할 수있는 많은 권한 클래스가 포함되어 있습니다. 이 경우 IsAuthenticatedOrReadOnly를 사용하면 인증 된 요청에 읽기-쓰기 액세스 권한이 부여되고 인증되지 않은 요청에는 읽기-전용 액세스 권한이 부여됩니다.

view에 다음을 import

from rest_framework import permissions

SnippetList와 ‘SnippetDetail` 뷰 클래스에 다음 속성을 추가

permission_classes = (permissions.IsAuthenticatedOrReadOnly,)

Browsable API에 로그인 추가

현재 브라우저를 열고 탐색 가능한 API로 이동하면 더 이상 새 코드 snippet을 만들 수 없다는 것을 알 수 있습니다. 그렇게하기 위해 우리는 사용자로 로그인 할 수 있어야합니다.

프로젝트 레벨 urls.py 파일에서 URLconf를 편집하여 브라우저 API에 사용할 로그인보기를 추가 할 수 있습니다.

from django.conf.urls import include
urlpatterns += [
	url(r'^api-auth/', include('rest_framework.urls')),
]

패턴의 r'^api-auth/'부분은 실제로 사용하고자하는 URL이 될 수 있습니다.

이제 브라우저를 다시 열고 페이지를 새로 고침하면 페이지 오른쪽 상단에 ‘로그인’링크가 표시됩니다. 이전에 만든 사용자 중 하나로 로그인하면 코드 snippet을 다시 만들 수 있습니다.

몇 가지 코드 snippet을 만든 다음 ‘/users/’ 엔드포인트로 이동하여 각 사용자의 ‘snippet’입력란에 각 사용자와 연결된 snippet ID 목록이 표시되는지 확인합니다.

Object level permissions

snippets app에 permissions.py를 만듭니다.

from rest_framework import permissions


class IsOwnerOrReadOnly(permissions.BasePermission):
    """
    Custom permission to only allow owners of an object to edit it.
    """

    def has_object_permission(self, request, view, obj):
        # Read permissions are allowed to any request,
        # so we'll always allow GET, HEAD or OPTIONS requests.
        if request.method in permissions.SAFE_METHODS:
            return True

        # Write permissions are only allowed to the owner of the snippet.
        return obj.owner == request.user

이제 SnippetDetail 뷰 클래스의 permission_classes 속성을 편집하여 snippet 인스턴스 엔드포인트에 해당 사용자 지정 권한을 추가 할 수 있습니다.

permission_classes = (permissions.IsAuthenticatedOrReadOnly,
                      IsOwnerOrReadOnly,)

IsOwnerOrReadOnly클래스를 import

from snippets.permissions import IsOwnerOrReadOnly

이제 브라우저를 다시 열면 코드 snippet을 만든 사용자와 동일한 사용자로 로그인 한 경우 ‘DELETE’및 ‘PUT’작업 만 스 니펫 인스턴스 끝점에 나타납니다.

API로 인증하기

이제는 API에 대한 권한 집합이 있으므로 snippet을 편집하려면 API에 대한 요청을 인증해야합니다. 우리는 어떤 인증 클래스도 설정하지 않았기 때문에 현재 SessionAuthenticationBasicAuthentication 인 기본값이 적용됩니다.

웹 브라우저를 통해 API와 상호 작용할 때 우리는 로그인 할 수 있으며 브라우저 세션은 요청에 필요한 인증을 제공합니다.

프로그래밍 방식으로 API와 상호 작용할 경우 각 요청에 대해 인증 자격 증명을 명시 적으로 제공해야합니다.

인증없이 snippet을 만들려고하면 오류가 발생합니다.

http POST http://127.0.0.1:8000/snippets/ code="print 123"

{
    "detail": "Authentication credentials were not provided."
}

이전에 생성한 사용자 중 하나의 사용자 이름과 비밀번호를 포함시켜 요청을 성공적으로 처리 할 수 있습니다.

http -a admin:password123 POST http://127.0.0.1:8000/snippets/ code="print 789"

{
    "id": 1,
    "owner": "admin",
    "title": "foo",
    "code": "print 789",
    "linenos": false,
    "language": "python",
    "style": "friendly"
}

요약

우리는 이제 웹 API와 시스템 사용자 및 그들이 작성한 코드 snippet에 대한 사용 권한을 상당히 세분화했습니다.