Django REST Framework
JSON을 응답하는 API 설계
설치
djangorestframework
패키지 설치settings.py
에 등록
pip install djangorestframework
INSTALLED_APPS = [
...
'rest_framework',
...
]
Serialization
- 직렬화
- 객체 또는 데이터 구조를 저장, 전송을 위해 다른 포맷으로 변경
- Queryset 혹은 Model의 Instance를 JSON, XML, YAML 등의 형태로 변환
- DRF :
serializers
를 통해 기능 제공
urls.py
from django.urls import path
from . import views
app_name = "articles"
urlpatterns = [
path("json-drf/", views.json_drf, name="json_drf"),
]
serializers.py
from rest_framework import serializers
from .models import Article
class ArticleSerializer(serializers.ModelSerializer):
class Meta:
model = Article
fields = "__all__"
views.py
@api_view(["GET"])
: 함수형 view 사용 시 필수
from rest_framework.decorators import api_view
from rest_framework.response import Response
from .serializers import ArticleSerializer
@api_view(["GET"])
def json_drf(request):
articles = Article.objects.all()
serializer = ArticleSerializer(articles, many=True)
return Response(serializer.data)
Postman
- API를 디자인, 테스트, 문서화, 공유를 할 수 있도록 도와주는 소프트웨어
- URL 과 Request를 활용하여 디자인 및 테스트
Seeding
- 설치 :
pip install django-seed
- 등록 :
settings.py
/INSTALLED_APPS
에 추가 - 명령어 :
python3 manage.py seed articles --number=30
No module named 'psycopg2'
에러 :pip install psycopg2
- 지정 Sedding
python3 manage.py seed articles --number=30 --seeder "article_id" 1
Class Based View
- Http Method에 대한 처리
- 함수형 : 조건으로 분리
- 클래서형 : 함수로 분리
- GET요청에 대한 처리는
get()
- POST 요청에 대한 처리는
post()
- GET요청에 대한 처리는
- 클래스를 사용하기 때문에 코드의 재사용성과 유지보수성이 향상됩니다.
- 기본
APIView
외에도 여러 편의를 제공하는 다양한 내장 CBV가 존재합니다.
종류
APIView
- DRF 베이스 클래스GenericAPIView
- 일반적인 API 작성을 위한 기능이 포함된 클래스
- CRUD 여러가지 기능 내장
Mixin
- 재사용 가능한 여러가지 기능
- 섞어서 사용
ListModelMixin
- 리스트 반환CreateModelMixin
- 객체를 생성
ViewSets
- endpoint를 한 번에 관리
- RESTful API에서 반복되는 구조를 더 편리하게 작성
Articles
models.py
- 기존 Django 와 동일
class Article(models.Model):
title = models.CharField(max_length=120)
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.title
class Comment(models.Model):
article = models.ForeignKey(
Article, on_delete=models.CASCADE, related_name="comments"
)
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
serializers.py
- 직렬화 도구 정의
read_only_fields = ("article",)
:"article"
은 참조만to_representation
: 보여지는 결과.pop('article')
: 본문 제거
ArticleDetailSerializer
ArticleSerializer
과 동일한 기능comments
내용 포함comments_count
: 개수 계산serializers.IntegerField
from rest_framework import serializers
from .models import Article, Comment
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
fields = "__all__"
read_only_fields = ("article",)
def to_representation(self, instance):
# 보여지는 결과
ret = super().to_representation(instance)
ret.pop('article')
return ret
class ArticleSerializer(serializers.ModelSerializer):
class Meta:
model = Article
fields = "__all__"
class ArticleDetailSerializer(ArticleSerializer):
comments = CommentSerializer(many=True, read_only=True)
comments_count = serializers.IntegerField(source="comments.count", read_only=True)
views.py
IsAuthenticated
: 로그인 여부 확인Response
: 결과 전달 메서드status
상태코드 설정 가능
from .models import Article
from rest_framework import status
from rest_framework.views import APIView
from rest_framework.response import Response
from django.shortcuts import get_object_or_404
from .serializers import (
ArticleSerializer,
ArticleDetailSerializer,
CommentSerializer
)
from rest_framework.permissions import IsAuthenticated
Articles
- 기본 로직은 함수형과 동일
get_object
: 공통적으로 사용하는 객체 불러오는 기능을 분리partial=True
: 일부만 수정하는 것 허용raise_exception=True
: 예외 처리 기능
class ArticleListAPIView(APIView):
permission_classes = [
IsAuthenticated,
]
def get(self, request):
articles = Article.objects.all()
serializer = ArticleSerializer(articles, many=True)
return Response(serializer.data)
def post(self, request):
serializer = ArticleSerializer(data=request.data)
if serializer.is_valid(raise_exception=True):
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
class ArticleDetailAPIView(APIView):
permission_classes = [
IsAuthenticated,
]
def get_object(self, pk):
return get_object_or_404(Article, pk=pk)
def get(self, request, pk):
article = self.get_object(pk)
serializer = ArticleDetailSerializer(article)
return Response(serializer.data)
def put(self, request, pk):
article = self.get_object(pk)
serializer = ArticleDetailSerializer(article, data=request.data, partial=True)
if serializer.is_valid(raise_exception=True):
serializer.save()
return Response(serializer.data)
def delete(self, request, pk):
article = self.get_object(pk)
article.delete()
data = {"pk": f"{pk} is deleted."}
return Response(data, status=status.HTTP_200_OK)
Comments
article
을 불러온 후 역참조하여comments
호출
class CommentListAPIViews(APIView):
permission_classes = [
IsAuthenticated,
]
def get(self, request, article_pk):
article = get_object_or_404(Article, pk=article_pk)
comments = article.comments.all()
serializer = CommentSerializer(comments, many=True)
return Response(serializer.data)
def post(self, request, article_pk):
article = get_object_or_404(Article, pk=article_pk)
serializer = CommentSerializer(data=request.data)
if serializer.is_valid(raise_exception=True):
serializer.save(article=article)
return Response(serializer.data, status=status.HTTP_201_CREATED)
from .models import Comment
class CommentDetailAPIView(APIView):
permission_classes = [
IsAuthenticated,
]
def get_object(self, comment_pk):
return get_object_or_404(Comment, pk=comment_pk)
def delete(self, request, comment_pk):
comment = self.get_object(comment_pk)
comment.delete()
data = {"pk": f"{comment_pk} is deleted."}
return Response(data, status=status.HTTP_200_OK)
def put(self, request, comment_pk):
comment = self.get_object(comment_pk)
serializer = CommentSerializer(comment, data=request.data, partial=True)
if serializer.is_valid(raise_exception=True):
serializer.save()
return Response(serializer.data)
urls.py
views.<APIView>.as_view()
형태로 사용
from django.contrib import admin
from django.urls import path
from . import views
app_name = "articles"
urlpatterns = [
path("", views.ArticleListAPIView.as_view(), name="article_list"),
path("<int:pk>/", views.ArticleDetailAPIView.as_view(), name="article_detail"),
path(
"<int:article_pk>/comments/",
views.CommentListAPIViews.as_view(),
name="comment_list",
),
path(
"comments/<int:comment_pk>/",
views.CommentDetailAPIView.as_view(),
name="comment_detail",
),
]
JSON Web Token
- 일정한 규칙을 가지고 있고 간단한 서명을 더한 문자열
- 토큰 자체에 유저에 대한 간단한 정보가 들어있는 형태입니다.
- Session DB나 인증을 위한 여러가지 로직 처리가 불필요
처리방식
- ID/PW를 서버로 전송
- 서버에서 ID/PW를 검증
- 유효한 경우 일정한 형식으로 서명 처리된 Token응답
- 이후 클라이언트는 모든 요청 Header에 토큰을 담아 서버로 요청을 전송
- 서버는 해당 토큰의 유효성을 검증하고 유저의 신원과 권한을 확인 후 요청을 처리
구조
- HEADER
- 토큰의 타입 또는 서명 부분의 생성에 어떤 알고리즘이 사용되었는지 등을 저장
- PAYLOAD
- 유저 정보: 토근 발급자, 토큰 대상자, 토큰 만료시간, 활성날짜, 발급시간 등
- 민감한 정보 제외 최소의 정보만 저장 : User, PK 등
- Claim : Key-Value 형태로 구성
- SIGNATURE
HEADER + PAYLOAD + 서버의 비밀키
: HEADER에 명시된 암호 알고리즘 방식으로 생성한 값- 서명의 유효여부 + 유효기간 내의 토큰인지 확인하여 인증
Token
Access Token
- 인증을 위해 헤더에 포함
- 매 요청 포함 / 보안 취약
- 짧은 만료기간 : 탈취되어도 만료되어 사용 불가
Refresh Token
- 새로 Access Token을 발급받기 위한 Token
- Access Token 보다 긴 유효기간
- 주로 사용자의 기기에 저장
- Refresh Token 만료 시 재인증
- BlackList : 탈취를 보완하기 위해 DB 리소스를 사용
accounts
simplejwt
pip install djangorestframework-simplejwt
settings
ROTATE_REFRESH_TOKENS
: 엑세스 토큰을 갱신 한 후 리프레시 토큰도 갱신BLACKLIST_AFTER_ROTATION
: 갱신 후 사용된 토큰은 블랙리스트로 관리하여 보안 강화
INSTALLED_APPS = [
...
'rest_framework',
'rest_framework_simplejwt.token_blacklist',
...
]
# Custom User Model
AUTH_USER_MODEL = 'accounts.User'
# JMT
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": [
"rest_framework_simplejwt.authentication.JWTAuthentication",
],
}
from datetime import timedelta
SIMPLE_JWT = {
"ACCESS_TOKEN_LIFETIME": timedelta(minutes=1),
"REFRESH_TOKEN_LIFETIME": timedelta(days=1),
"ROTATE_REFRESH_TOKENS": True,
"BLACKLIST_AFTER_ROTATION": True,
}
models.py
- 커스텀 모델을 생성하여 기능 추가에 대비
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
pass
urls.py
views.py
없이 작동
from django.urls import path
from rest_framework_simplejwt.views import (
TokenObtainPairView,
TokenRefreshView,
)
urlpatterns = [
path("signin/", TokenObtainPairView.as_view(), name="token_obtain_pair"),
path("token/refresh/", TokenRefreshView.as_view(), name="token_refresh"),
]
테스트
python3 manage.py createsuperuser
: 관리자 계정 생성- Postman 에 접속
- json 형식으로 ID/PASSWORD 전달
- Token 정보는 요청 Header에 담아서 전달