[DRF] Concept part - Swagger : drf-yasg

2023. 2. 20. 01:32·Backend/Django REST Framework
목차
  1. 1. What is Swagger?
  2. 2. drf-yasg
  3. 3. API Document
  4. 4. Body 커스텀
  5. 5. Header 커스텀
  6. 5-1. Header의 Authorization에 JWT Token 넣기
  7. 6. Query String

최근에 여행 다녀오느라 장기간 블로그가 방치되어 있었더니 가슴이 아팠는데, 오랜만에 DRF 포스팅을 하게 됐다.

사용법 자체는 너무 쉬워서 허무할 정도라 가볍게 읽어도 괜찮을 듯 하다.

 

목차
1. What is Swagger?
2. drf-yasg
3. API Document
4. body 커스텀
5. header 커스텀
5-1. header의 Authorization에 jwt_token 넣기 
6. Query String

1. What is Swagger?

 

API 서버를 개발해본 사람은 알겠지만, 백엔드에서 중요한 작업 중 하나로 API Document 작성이 있다.

Client에서 자원을 얻기 위해 API 사용법(메서드)을 알려주는 것이라 보면 되는데, 나는 예전에 무식하게 노션에 전부 적어놨었다.

지금 생각하면 무식하기 짝이 없다.

 

그런데 최근 DevOps를 공부하다가 Swagger라는 기술이 계속 언급이 되어 찾아보니 API 문서화 도구라고 한다.

end-pint, parameter, request/response, header 등을 문서화할 수 있고, UI로 표시하여 개발자가 쉽게 접근할 수 있도록 도와준다.

즉, 위에 저 내용들을 모두 자동으로 생성하고 보기 좋게 정리까지 해주는 툴이다.


2. drf-yasg

 

 

Describing authentication schemes — drf-yasg 1.20.1 documentation

When using the swagger-ui frontend, it is possible to interact with the API described by your Swagger document. This interaction might require authentication, which you will have to describe in order to make swagger-ui work with it. swagger-ui as OAuth2 cl

drf-yasg.readthedocs.io

 

swagger는 Django에 국한된 기술이 아니기 때문에 DRF에서는 drf-yasg라는 라이브러리를 사용한다.

설치하려는 프로젝트로 이동하여 가상환경을 실행시키고 터미널에 다음과 같이 입력한다.

pip install drf-yasg

 

설치가 정상적으로 완료됐다면, settings.py의 INSTALLED_APPS에 추가해준다.

// settings.py
INSTALLED_APPS = [
    (...)
    # drf
    'rest_framework',
    # swagger
    'drf_yasg'
]

 

swagger로 작성된 API Document를 UI로 확인하기 위해서는 end-point를 지정해주어야 한다.

경로는 최상위 urls.py에서 명시한다.

from django.contrib import admin
from django.urls import path, include, re_path

from django.conf import settings
from django.conf.urls.static import static

from rest_framework import permissions
from drf_yasg.views import get_schema_view
from drf_yasg import openapi

schema_view = get_schema_view(
    openapi.Info(
        title='프로젝트 이름',
        default_version='프로젝트 버전',
        description='API Doc 설명',
        terms_of_service="https://www.google.com/policies/terms/",
        contact=openapi.Contact(email="a@a.com"),     # 부가 정보
        license=openapi.License(name="test")
    ),
    public=True,
    permission_classes=[permissions.AllowAny]
)

urlpatterns = [
    path('admin/', admin.site.urls),
    (...)
]

if settings.DEBUG:
    urlpatterns += [
        re_path(r'^swagger(?P<format>\.json|\.yaml)$', schema_view.without_ui(cache_timeout=0), name="schema-json"),
        re_path(r'^swagger/$', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
        re_path(r'^redoc/$', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc')]

 

urlpatterns에 바로 추가하지 않고 조건문을 넣은 이유는 api 문서는 외부에 노출되어서는 안 되는 정보다.

따라서 디버깅하는 경우에만 문서가 보여지도록 설정한다.

 

놀랍게도 이게 끝이다.

이제 결과물을 확인해보자.


3. API Document

 

포트 번호를 변경하지 않았다면 "http://127.0.0.1:8000/swagger/"로 접속했을 때 확인할 수 있다.

 

부가 설명으로 적은 내용들도 잘 나와 있다.
내가 사용한 모델 정보까지 표시해주고 있다.

닫혀있던 토글을 클릭하면 이렇게 메서드 별로 정리가 잘 되어 있는 것을 확인할 수 있는데, 아무거나 하나 눌러보면

 

이렇게 url 별로 response 정보를 알려준다.

더 놀라운 기능은 테스트를 하기 위해서 서버를 실행하지 않고도 해당 페이지에서 바로 테스트가 가능하다는 점.

우측 상단의 Try it out을 누르고 excute를 누르면 response가 돌아온다.


4. Body 커스텀

 

Request 요청 시에는 body에 정보를 담아야 하는 post 요청같은 것들이 존재한다.

Swagger가 기본으로 제공하기는 하지만 기준이 해당 view에서 사용한 Serializer 기반으로 작성된다.

만약 Serializer로 직렬화한 데이터가 있지만 그 필드는 request 시에 필요하지 않을 수도 있고,

내 경우에는 로그인을 위한 정보와 리턴받는 정보가 완전히 다르다 보니 Serializer를 아예 사용하지 않아서 Swagger가 자동으로 body를 생성해주지 않는 문제도 있었다.

이럴 때는 커스텀을 해줄 필요가 있다.

 

내가 작성했던 유저 모델을 다음과 같다.

class SignInUserView(APIView):
    permission_classes = [AllowAny]

    def post(self, request):
        nickname = request.data.get("nickname")
        password = request.data.get("password")
        
        if not nickname or not password:
            return Response(status=status.HTTP_400_BAD_REQUEST)
        user = authenticate(
            nickname=nickname,
            password=password,
        )

        if user:
            token = TokenObtainPairSerializer.get_token(user)
            refresh_token = str(token)
            access_token = str(token.access_token)

            res = Response(
                {
                    "refresh": str(token),
                    "access": str(token.access_token),
                },
                status=status.HTTP_200_OK,
            )
            res.set_cookie("access", access_token, httponly=True)
            res.set_cookie("refresh", refresh_token, httponly=True)
            return res
        return Response(status=status.HTTP_401_UNAUTHORIZED)

유저 정보를 받고 토큰을 돌려주니 Serializer 과정이 필요없어 사용하지 않는 덕에 Parameters가 생성이 되지 않았다.

그럼 "serializer를 사용해주면 끝나는 문제 아니냐?"

틀린 말은 아니지만 필요도 없는 직렬화 과정을 동반하는 것은 성능과 가독성을 모두 저해하는 수단이다.

이럴 땐 라이브러리에서 지원하는 기능을 이용하자.

 

# api/users/views.py
from drf_yasg.utils import swagger_auto_schema
from .swaggers import CustomUserBodySerializer

class SignInUserView(APIView):
    permission_classes = [AllowAny]

    @swagger_auto_schema(request_body=CustomUserBodySerializer)
    def post(self, request):
        (...)

swagger_auto_schema 어노테이션을 걸어주고 request_body에 필요한 정보를 받도록 Serializer를 명시해준다.

serializers.py에 정의하면 너무 지저분해질 것 같아서 swaggers.py를 따로 만들어서 사용했다.

 

from rest_framework import serializers

class CustomUserBodySerializer(serializers.Serializer):
   nickname = serializers.CharField(help_text="닉네임")
   password = serializers.CharField(help_text="패스워드")

닉네임과 패스워드만 받으면 되니까 간단히 설정해주고 다시 API 문서를 확인해보자.

 

정상적으로 body에 정보를 담을 수 있게 되었고 실행을 해보면

 

토큰 정보를 리턴해오는 것을 확인할 수 있다.

이런 기능을 하는 API 문서를 만들어 놓으면 프론트 엔드랑 조금은 덜 싸우고 평화롭게 개발할 수 있을 뿐 아니라,

정작 자신이 개발해놓고 무슨 기능을 하는 메서드인지 잊어먹는 경우가 꽤 흔하다.

나 자신을 위해서라도 문서화는 게을리하지 말자.

 

참고로 POST 메서드에 body 항목 자체가 필요 없는 경우에는

@swagger_auto_schema(request_body=no_body)

라고 정의해주면 된다.

 


5. Header 커스텀

 

권한 외에 헤더를 추가할 일은 많이 없겠지만, 방법이 조금 달라서 내용을 분리시켜버렸다.

 

헤더 추가는 그렇게 어렵지 않다.

class SignOutUserView(APIView):
    permission_classes = [IsAuthenticated]

    @swagger_auto_schema(manual_parameters=[openapi.Parameter('Authorization', openapi.IN_HEADER, description="access token", type=openapi.TYPE_STRING)])
    def post(self, request, *args):
        user = RefreshTokenSerializer(data=request.data)
        user.is_valid(raise_exception=True)
        user.save()

        reset = ""
        res = Response({"message": "logout success"}, status=status.HTTP_204_NO_CONTENT)
        res.set_cookie("access", reset)
        res.set_cookie("refresh", reset)

        return Response({"message": "logout success"}, status=status.HTTP_204_NO_CONTENT)

name 인자로 'Authorization'을 _in 인자로 openapi.IN_HEADER를 던져주어 헤더를 명시하면 된다.

(쓰다가 사진이 날아가버렸는데 여기까진 별 중요한 내용이 아니니 넘기자.)

 

문제는 개발자가 헤더를 작성하는 경우는 보통 'Authorization' 필드를 사용하기 위함인데,

여기에 어떻게 JWT Token의 Access Token을 넣을 수 있을까?

 


5-1. Header의 Authorization에 JWT Token 넣기

 

진짜 이것 때문에 너무 열받아서 스트레스 잔뜩 받고 있었는데, 내가 그냥 바보짓을 연속으로 2번이나 하고 있었다.......

차근차근 알아보자.

 

만약, 로그인한 유저만 자원을 얻을 수 있도록 권한 설정을 한 경우 헤더에 정보를 실어서 보내야 하는 경우도 있다.

대부분의 서비스에서 로그인 기능은 필수적이므로 모르는 사람은 없을 것이라 생각한다.

 

예를 들어, 나는 JWT Token을 이용하여 유저 관리를 하고 있으므로 로그아웃을 하려면 access token 정보를 던져주어야 한다.

하지만 현재 상태에서 확인해보면 access token을 던져줄 헤더가 없어서 권한 문제가 생긴다.

 

공식 문서에 따르면 HTTP 기본 인증 및 Authorization 헤더 API 토큰을 수락하는 정의는 다음과 같다.

SWAGGER_SETTINGS = {
   'SECURITY_DEFINITIONS': {
      'Basic': {
            'type': 'basic'
      },
      'Bearer': {
            'type': 'apiKey',
            'name': 'Authorization',
            'in': 'header'
      }
   }
}

SECURITY_DEFINITIONS 설정을 추가해서 API에서 지원하는 모든 인증 체계를 선언함으로써 drf-yasg에게 알려주어야 하는 것이다.

이게 어떤 거냐면, swagger-ui를 처음 열어보면 session DB로 관리하는 로그인 폼을 지원하는데 이 기능을 끄고 JWT 방식 로그인으로 바꿔주면 이후에 헤더가 알아서 부가된다.

 

# settings.py

# Swagger

SWAGGER_SETTINGS = {
    'USE_SESSION_AUTH': False,
    'SECURITY_DEFINITIONS': {
        'BearerAuth': {
            'type': 'apiKey',
            'name': 'Authorization',
            'in': 'header',
            'description': "JWT Token"
        }
    },
    'SECURITY_REQUIREMENTS': [{
        'BearerAuth': []
    }]
}

우선 settings.py에 이렇게 선언을 해주면 Session 인증 방식을 끄고 token 인증 방식으로 활성화 시키도록 한다.

대부분의 경우 이렇게만해도 이후에 문제없이 동작한다.

 

UI 가장 위로 올라와보자.

우측 하단에 Authorize 버튼으로 바뀐 것을 볼 수 있는데, 눌러보면 token 정보를 입력하라고 나온다.

signin 경로로 받은 access token을 넣어주자.

 

앞에 jwt를 붙여주는 걸 절대 잊지 말자...

 

이제 권한 인증이 필요한 메서드를 아무거나 실행시켜 보면 무사히 작동한다.

 

다만, 로그아웃 기능의 경우에는 drf-yasg에서 관리하는 토큰 정보를 삭제하기 위해 별다른 조치가 필요할 것 같은데 솔직히 그런 기능까지 구현할 필요가 있을까 싶어서 만져보다가 관두기로 했다.

 


6. Query String

 

Query String은 단순하게 구현 가능하지만, 마찬가지로 Serializer를 필요로 한다.

@swagger_auto_schema(query_serializer=TestSerializer)

어노테이션을 추가해주고 TestSerializer 안에는 body 정보를 추가할 때처럼 작성해주면 된다.

 

class TestSerializer(serializers.Serializer):
    id = serializers.CharField(required=True)
    name = serializers.CharField(required=False)
    query1 = serializers.ChoiceField(required=False)
    query2 = serializers.ChoiceField(required=False)
    query3 = serializers.ChoiceField(required=False)

require 속성은 Swagger 문서 작성 시, 필수로 입력받아야 하는 값인지를 알려주는 용도다.

저작자표시 비영리 (새창열림)
  1. 1. What is Swagger?
  2. 2. drf-yasg
  3. 3. API Document
  4. 4. Body 커스텀
  5. 5. Header 커스텀
  6. 5-1. Header의 Authorization에 JWT Token 넣기
  7. 6. Query String
'Backend/Django REST Framework' 카테고리의 다른 글
  • [DRF] Concept part - Mixins, generics, Viewset & router(with. nested router)
  • [DRF] Concept Part - Request & Response
  • [DRF] Concept Part - Serializer
  • [DRF] Concept Part - Django ORM
나죽못고나강뿐
나죽못고나강뿐
싱클레어, 대부분의 사람들이 가는 길은 쉽고, 우리가 가는 길은 어려워요. 우리 함께 이 길을 가봅시다.
  • 나죽못고나강뿐
    코드를 찢다
    나죽못고나강뿐
  • 전체
    오늘
    어제
    • 분류 전체보기 (458)
      • Computer Science (60)
        • Git & Github (4)
        • Network (17)
        • Computer Structure & OS (13)
        • Software Engineering (5)
        • Database (9)
        • Security (5)
        • Concept (7)
      • Frontend (21)
        • React (13)
        • Android (4)
        • iOS (4)
      • Backend (77)
        • Spring Boot & JPA (50)
        • Django REST Framework (14)
        • MySQL (8)
        • Nginx (1)
        • FastAPI (4)
      • DevOps (24)
        • Docker & Kubernetes (11)
        • Naver Cloud Platform (1)
        • AWS (2)
        • Linux (6)
        • Jenkins (0)
        • GoCD (3)
      • Coding Test (112)
        • Solution (104)
        • Algorithm (7)
        • Data structure (0)
      • Reference (134)
        • Effective-Java (90)
        • Pragmatic Programmer (0)
        • CleanCode (11)
        • Clean Architecture (2)
        • Test-Driven Development (4)
        • Relational Data Modeling No.. (0)
        • Microservice Architecture (2)
        • 알고리즘 문제 해결 전략 (9)
        • Modern Java in Action (0)
        • Spring in Action (0)
        • DDD start (0)
        • Design Pattern (6)
        • 대규모 시스템 설계 (6)
        • JVM 밑바닥까지 파헤치기 (4)
      • Service Planning (2)
      • Side Project (5)
      • AI (0)
      • MATLAB & Math Concept & Pro.. (1)
      • Review (18)
      • Interview (2)
      • IT News (2)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • 깃
  • 공지사항

    • 한동안 포스팅은 어려울 것 같습니다. 🥲
    • N Tech Service 풀스택 신입 개발자가 되었습니다⋯
    • 취업 전 계획 재조정
    • 취업 전까지 공부 계획
    • 앞으로의 일정에 대하여..
  • 인기 글

  • 태그

  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.2
나죽못고나강뿐
[DRF] Concept part - Swagger : drf-yasg

개인정보

  • 티스토리 홈
  • 포럼
  • 로그인
상단으로

티스토리툴바

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.