6. Authentication & Permissions
1. Adding information to our model
Snippet
모델 클래스에 몇 가지 변경 사항을 적용할 것이다.
- 먼저, 몇 개의 필드를 추가해 보자.
- 이러한 필드 중 하나는 코드 스니펫을 만든 사용자를 나타내는 데 사용되고, 다른 필드는 코드의 강조 표시된 HTML 표현을 저장하는 데 사용된다.
snippets/models.py
파일의Snippet
모델에 다음 두 필드를 추가한다.
snippets/models.py
owner = models.ForeignKey(
"auth.User", related_name="snippets", on_delete=models.CASCADE
)
highlighted = models.TextField()
- 또한 모델이 저장될 때
pygments
코드 강조 라이브러리를 사용하여 강조 표시된 필드를 채우는지 확인해야 한다. - 해당 파일에 다음과 같이 추가적으로 임포트한다.
snippets/models.py
from pygments.lexers import get_lexer_by_name
from pygments.formatters.html import HtmlFormatter
from pygments import highlight
- 이제 모델 클래스에
.save()
메서드를 추가할 수 있다.
snippets/models.py
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().save(*args, **kwargs)
- 모든 작업이 완료되면 데이터베이스 테이블을 업데이트해야 한다.
- 이를 수행하기 위해 데이터베이스 마이그레이션을 생성하지만, 튜토리얼을 위해 데이터베이스를 삭제하고 다시 시작한다.
- 다음 명령을 사용하여 데이터베이스 및 마이그레이션을 삭제한다.
- 이제 마이그레이션을 생성한다.
- API 테스트에 사용할 몇 명의 다른 사용자를 생성할 수도 있다.
- 이를 수행하는 가장 빠른 방법은
createsuperuser
명령을 사용하는 것이다.
2. Adding endpoints for our User models
- 이제 작업할 사용자가 있으므로 해당 사용자를 표시해 보자.
snippets/serializers.py
파일에 다음과 같이 추가한다.
snippets/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
클래스를 사용할 때 기본적으로 포함되지 않으므로 이에 대한 명시적 필드를 추가했다.
- 또한
snippets/views.py
파일에 몇 가지 뷰를 추가할 것이다. - 사용자 표현에 대해 읽기 전용 뷰만 사용하고 싶으므로
ListAPIView
및RetrieveAPIView
일반(generics
) 클래스 기반의 뷰를 사용한다.
snippets/views.py
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
클래스를 임포트했는지 꼭 확인한다.
- 마지막으로
snippets/urls.py
파일의urlpatterns
를 다음과 같이 수정한다.
snippets/urls.py
urlpatterns = [
path("snippets/", views.SnippetList.as_view()),
path("snippets/<int:pk>/", views.SnippetDetail.as_view()),
path("users/", views.UserList.as_view()),
path("users/<int:pk>/", views.UserDetail.as_view()),
]
urlpatterns = format_suffix_patterns(urlpatterns)
3. Association Snippets with Users
- 지금 상황에서 코드 스니펫을 생성했다고 해도 스니펫을 생성한 사용자를 스니펫 인스턴스와 연결할 방법이 없다.
- 사용자는 직렬화된 표현의 일부로 전송되지 않고 들어오는 요청의 속성으로 전송된다.
- 이를 처리하는 방법은 스니펫 뷰에서
.perform_create()
메서드를 재정의하는 것이다. - 이를 통해 인스턴스 저장이 관리되는 방식을 수정하고 들어오는 요청 또는 요청된 URL에 내재된 모든 정보를 처리할 수 있다.
snippets/views.py
파일의SnippetList
뷰 클래스에 다음 메서드를 추가한다.
- 이제 직렬 변환기의
create()
메서드에 요청된 검증된 데이터와 함께'owner'
필드가 전달된다.
4. Updating our serializer
- 이제 스니펫이 이를 생성한 사용자와 연결되었으므로 이를 반영하도록
snippets/serializers.py
파일의SnippetSerializer
를 다음과 같이 업데이트한다.
snippets/serializers.py
class SnippetSerializer(serializers.ModelSerializer):
owner = serializers.ReadOnlyField(source="owner.username")
class Meta:
model = Snippet
fields = ["id", "title", "code", "linenos", "language", "style", "owner"]
source
인수는 필드를 채우는 데 사용되는 속성을 제어하고 직렬화된 인스턴스의 모든 속성을 가리킬 수 있다.- 또한
.
(점) 표기법을 사용할 수도 있는데, 이 경우 Django의 템플릿 언어와 함께 사용되는 것과 유사한 방식으로 주어진 속성을 순회한다. ReadOnlyField
클래스는CharField
,BooleanField
등과 달리 타입이 지정되지 않은 클래스이다.- 타입이 지정되지 않은
ReadOnlyField
는 항상 읽기 전용이며 직렬화된 표현에 사용되지만 역직렬화될 때 모델 인스턴스를 업데이트하는 데 사용된다. - 여기서
CharField(read_only=True)
를 사용할 수도 있다.
5. Adding required permissions to views
- 이제 코드 스니펫이 사용자와 연결되었으므로 인증된 사용자만 코드 스니펫을 생성, 업데이트 및 삭제할 수 있도록 한다.
- REST framework에는 주어진 뷰에 액세스할 수 있는 사람을 제한하는 데 사용할 수 있는 여러 권한 클래스가 포함되어 있다.
- 그 중 하나는
IsAuthenticatedOrReadOnly
이며, 인증된 요청은 읽기-쓰기 액세스 권한을 얻고 인증되지 않은 요청은 읽기 전용 액세스 권한을 얻는다.
- 먼저,
snippets/views.py
파일에 다음과 같이 임포트한다.
- 그런 다음
SnippetList
와 SnippetDetail 클래스 기반의 뷰에 다음의 코드를 추가한다.
6. Adding login to the Browsable API
- 현재 상황에서는 웹 브라우저에서 새 코드 스니펫을 만들 수 없다.
- 그렇게 하려면 사용자로 로그인해야 한다.
- 프로젝트 루트
tutorial/urls.py
파일에서 URLConf를 편집하여 탐색 가능한 API(Browsable API)와 함께 사용할 로그인 뷰를 추가할 수 있다. - 먼저, 다음과 같이 임포트한다.
- 그리고 맨 아래에 탐색 가능한 API에 대한 로그인 및 로그아웃 뷰를 포함하는 패턴을 추가한다.
- 패턴의
"api-auth/"
부분은 실제로 사용하려는 URL이 된다. - 이제 웹 브라우저를 다시 열고 페이지를 새로고침하면 페이지 오른쪽 상단에 'Login' 링크가 표시된다.
- 이전에 생성한 사용자 중 하나로 로그인하면 코드 스니펫을 다시 생성할 수 있다.
- 코드 스니펫을 여러 개 생성했으면
'/users/'
엔드포인트로 이동하여 각 사용자의 '스니펫' 필드에 각 사용자와 연결된 스니펫 ID 목록이 표시에 포함되어 있는지 확인한다.
7. Object level permissions
- 모든 코드 스니펫을 누구나 볼 수 있게 하면서, 코드 스니펫을 만든 사용자만 코드 스니펫을 업데이트하거나 삭제할 수 있도록 해야 한다.
- 그렇게 하려면 사용자 지정 권한을 만들어야 한다.
snippets/permissions.py
파일을 새로 생성한 후 다음과 같이 작성한다.
snippets/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
속성을 편집하여 사용자 지정 권한을 스니펫 인스턴스 엔드포인트에 추가할 수 있다. - 먼저
IsOwnerOrReadOnly
클래스를 임포트한다.
permission_classes
속성을 다음과 같이 편집한다.
- 이제 웹 브라우저를 다시 열면, 코드 스니펫을 만든 동일한 사용자로 로그인한 경우에만 'DELETE' 및 'PUT' 작업이 스니펫 인스턴스 엔드포인트에 나타난다.
8. Authentication with the API
- 이제 API에 대한 권한 집합이 있으므로 스니펫을 편집하려면 API에 대한 요청을 인증해야 한다.
- 인증 클래스를 설정하지 않았으므로 현재 기본값인
SessionAuthentication
및BasicAuthentication
이 적용된다. - 웹 브라우저를 통해 API와 상호 작용할 때 로그인할 수 있으며 브라우저 세션은 요청에 필요한 인증을 제공한다.
- 프로그래밍 방식으로 API와 상호 작용하는 경우 각 요청에 대해 인증 자격 증명을 명시적으로 제공해야 한다.
- 만약 인증하지 않고 스니펫을 만들려고 하면 오류가 발생한다.
HTTP/1.1 403 Forbidden
Allow: GET, POST, HEAD, OPTIONS
Content-Length: 58
Content-Type: application/json
Date: Fri, 17 Dec 2021 10:01:53 GMT
Referrer-Policy: same-origin
Server: WSGIServer/0.2 CPython/3.8.8
Vary: Accept, Cookie
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
{
"detail": "Authentication credentials were not provided."
}
- 이런 경우 이전에 생성한 사용자 중 한 명의 사용자 이름과 비밀번호를 포함하여 요청할 수 있다.
HTTP/1.1 201 Created
Allow: GET, POST, HEAD, OPTIONS
Content-Length: 110
Content-Type: application/json
Date: Fri, 17 Dec 2021 10:03:07 GMT
Referrer-Policy: same-origin
Server: WSGIServer/0.2 CPython/3.8.8
Vary: Accept, Cookie
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
{
"code": "print(123)",
"id": 3,
"language": "python",
"linenos": false,
"owner": "admin",
"style": "friendly",
"title": ""
}