장고(django)에 JWT 사용하기

2019-06-06

장고(django) 프로젝트에서 API를 사용할때 JWT(Json Web Token) 인증을 사용하는 방법에 대해서 알아보자

개요

리액트 네이티브(React Native)로 개발된 앱(App)의 로그인, 정보 갱신 등의 API 서버를 장고(django)를 사용하여 개발하려고 합니다. 이 때, 앱(App)과 API 서버에 자주 사용되는 JWT(Json Web Token) 인증을 장고(django)를 사용하여 구현하려고 합니다. 이번 블로그에서는 Django REST framework JWT를 사용하여 장고(django)의 JWT를 구현하는 방법에 대해서 설명합니다.

이 블로그에서 사용하는 소스코드는 github에 공개되어 있습니다. 아래에 링크를 통해 확인 가능합니다.

장고(django) 프로젝트 준비

이전 블로그 시리즈에서 장고(django) 프로젝트를 사용하는 방법에 대해서 설명하였습니다. 장고(django)를 사용하여 프로젝트를 구성하는 방법에 대한 자세한 내용은 아래에 링크를 통해 확인하시기 바랍니다.

이 블로그에서는 장고(django) 설치와 프로젝트 설정등에 대해서는 설명하지 않도록 하겠습니다. 아래에 명령어를 통해 장고(django) 프로젝트를 생성합니다.

django-admin startproject django_jwt

아래에 블로그를 참고하여 데이터베이스 연동 및 테이블을 생성합니다.

데이터베이스 연동이 완료되었다면 아래에 명령어를 통해 관리자(superuser)를 생성합니다.

python manage.py createsuperuser

아래에 블로그를 참고하여 Blog 장고(django) 앱(App)과 Post 모델(Model)을 생성합니다.

그리고 아래에 블로그를 참고하여 테스트 데이터를 추가합니다.

Django REST framework JWT 설치

JWT 구현은 상단히 많은 부분을 신경써야하는 귀찮은 작업입니다. 그래서 대부분의 플랫폼에는 잘 만들어진 모듈 혹은 라이브러리가 존재합니다. 우리는 여기서 Django REST framework JWT을 사용하여 JWT를 구현하도록 하겠습니다.

아래에 명령어를 통해 Django REST framework JWT을 설치합니다.

pip install djangorestframework djangorestframework-jwt
  • djangorestframework: 장고(django)의 Restful API를 좀 더 쉽게 사용할 수 있게 해주는 모듈입니다.
  • djangorestframework-jwt: 장고(django)의 Restful API에서 JWT를 쉽게 구현할 수 있게 도와주는 모듈입니다.

설치가 완료되면 잊지 않고 requirements.txt를 갱신합니다.

pip freeze > requirements.txt

Django Rest framework JWT 설정

Django Rest framework JWT 설정하기 위해 django_jwt/settings.py를 열고 아래와 같이 수정합니다.

...
REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated',
    ),
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.BasicAuthentication',
    ),
}

JWT_AUTH = {
    'JWT_SECRET_KEY': SECRET_KEY,
    'JWT_ALGORITHM': 'HS256',
    'JWT_ALLOW_REFRESH': True,
    'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7),
    'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(days=28),
}
...

좀 더 자세히 살펴보면,

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated',
    ),
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
    ),
}

위에서 설치한 djangorestframework에 관한 설정입니다.

  • 로그인 여부를 확인하는 클래스(DEFAULT_PERMISSION_CLASSES)를 rest_framework.permissions.IsAuthenticated로 사용하도록 설정하였습니다.
  • 로그인과 관련된 클래스(DEFAULT_AUTHENTICATION_CLASSES)를 JWT(rest_framework_jwt.authentication.JSONWebTokenAuthentication)을 사용하도록 하였습니다.
JWT_AUTH = {
    'JWT_SECRET_KEY': SECRET_KEY,
    'JWT_ALGORITHM': 'HS256',
    'JWT_ALLOW_REFRESH': True,
    'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7),
    'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(days=28),
}
  • JWT_SECRET_KEY: JWT의 비밀키(Secret Key)로 어떤걸 사용할지 작성합니다. 여기에서는 장고(django)와 같은 비밀키를 사용하였지만 사용할 때는 다른 키를 사용하시길 권장합니다.
  • JWT_ALGORITHM: JWT 암호화에 사용되는 알고리즘을 지정합니다.
  • JWT_ALLOW_REFRESH: JWT 토큰을 갱신할 수 있게 할지 여부를 결정합니다.
  • JWT_EXPIRATION_DELTA: JWT 토큰의 유효 기간을 설정합니다.
  • JWT_REFRESH_EXPIRATION_DELTA: JWT 토큰 갱신의 유효기간 입니다.

JWT_EXPIRATION_DELTA와 JWT_REFRESH_EXPIRATION_DELTA이 잘 이해가 되지 않는데요. 위와 같이 설정한 경우 JWT 토큰을 7일 안에 갱신하지 않으면 JWT 토큰을 사용할 수 없고 로그아웃됩니다. 또한 7일안에 열심히 갱신해도 28일 후에는 갱신할 수 없습니다. 즉, 열심히 갱신해도 28일 후에는 로그아웃 처리가 되는 것을 의미합니다.

Django Rest framework JWT을 위한 URL 설정

이제 JWT 인증을 위한 URL을 추가해야 합니다. django_jwt/urls.py를 열고 아래와 같이 수정합니다.

from django.contrib import admin
from django.urls import path, include
from rest_framework_jwt.views import obtain_jwt_token, verify_jwt_token, refresh_jwt_token

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/token/', obtain_jwt_token),
    path('api/token/verify/', verify_jwt_token),
    path('api/token/refresh/', refresh_jwt_token),
    path('api/blog/', include('blog.urls'))
]
  • from rest_framework_jwt.views import obtain_jwt_token, verify_jwt_token, refresh_jwt_token: JWT 인증을 위해 필요한 요소들을 불러옵니다.
  • obtain_jwt_token: JWT 토큰을 발행할 때 사용합니다. (path('api/token/', obtain_jwt_token))
  • verify_jwt_token: JWT 토큰이 유효한지 검증할 때 사용합니다. (path('api/token/verify/', verify_jwt_token))
  • refresh_jwt_token: JWT 토큰을 갱신할 때 사용합니다. (path('api/token/refresh/', refresh_jwt_token))
  • path(‘api/blog/’, include(‘blog.urls’)): 우리가 만든 Blog 앱의 URL을 연결합니다.

우리가 만든 Blog 앱의 URL을 만들기 위해 blog/urls.py를 만들고 아래와 같이 수정합니다.

from django.urls import path
from . import views

urlpatterns = [
    path('posts/', views.posts, name='posts'),
]

뷰(View) 만들기

이제 실제로 JWT 인증을 사용할 API를 만들어 봅시다. blog/views.py를 열고 아래와 같이 수정합니다.

from django.shortcuts import render
from django.core import serializers
from django.http import HttpResponse
from rest_framework.decorators import api_view, permission_classes, authentication_classes
from rest_framework.permissions import IsAuthenticated
from rest_framework_jwt.authentication import JSONWebTokenAuthentication

from .models import Post


@api_view(['GET'])
@permission_classes((IsAuthenticated, ))
@authentication_classes((JSONWebTokenAuthentication,))
def posts(request):
    posts = Post.objects.filter(
        published_at__isnull=False).order_by('-published_at')
    post_list = serializers.serialize('json', posts)
    return HttpResponse(post_list, content_type="text/json-comment-filtered")

이제까지 만들어 보던 뷰(View)와 많이 다릅니다. 간단히 살펴보도록 하겠습니다.

from django.shortcuts import render
from django.core import serializers
from django.http import HttpResponse
...
from .models import Post
...
def posts(request):
    posts = Post.objects.filter(published_at__isnull=False).order_by('-published_at')
    post_list = serializers.serialize('json', posts)
    return HttpResponse(post_list, content_type="text/json-comment-filtered")

이 부분은 이전 블로그(장고(django)의 모델(Models)을 JSON으로 응답(Response)하기)에서 이미 소개해 드렸습니다. 모델(Models)의 결과(QuerySet)을 JSON으로 응답(Response)하기 위한 부분입니다. 이제 새롭게 추가된 부분을 살펴봅시다.

...
from rest_framework.decorators import api_view, permission_classes, authentication_classes
from rest_framework.permissions import IsAuthenticated
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
...
@api_view(['GET'])
@permission_classes((IsAuthenticated, ))
@authentication_classes((JSONWebTokenAuthentication,))
def posts(request):
...
  • from rest_framework.decorators import api_view, permission_classes, authentication_classes: 우리가 설치한 djangorestframework이 제공하는 파이썬 데코레이터(Python Decorator)입니다. 데코레이터(Decorator)를 설명하자면 많이 길어질거 같아, 간단하게 이해하면 전처리로 생각하면 간단할 거 같습니다.
  • from rest_framework.permissions import IsAuthenticated: 로그인 여부를 확인할 때 사용합니다.
  • from rest_framework_jwt.authentication import JSONWebTokenAuthentication: JWT 인증을 확인하기 위해 사용합니다.
  • @api_view(['GET']): 위에서 언급한 데코레이터(Decorator)입니다. 하위에 있는 함수(def posts(request):)를 실행하기 전에 이 부분이 먼저 실행됩니다. 이 데코레이터(Decorator)는 GET 요청인지 검증하고 GET이 아니면 에러를 JSON 타입으로 반환합니다.
  • @permission_classes((IsAuthenticated, )): 권한을 체크합니다. 여기서는 로그인 했는지 여부만 체크하도록 하였습니다.
  • @authentication_classes((JSONWebTokenAuthentication,)): JWT 토큰을 확인합니다. 토큰이 이상이 있으면 에러를 JSON 형식으로 반환합니다.

확인

지금까지 작업한 내용을 Postman을 통해 확인해 보도록 하겠습니다. 아래에 명령어로 장고(django)의 테스트 서버를 실행합니다.

python manage.py runserver

그리고 Postman을 열고 아래와 같이 http://localhost:8000/api/blog/posts/에 GET 요청을 보냅니다.

장고(django)에 JWT 사용하기 - JWT 인증 에러

그러면 위와 같이 에러 메세지를 확인할 수 있습니다. 우리가 만든 API가 JWT 인증으로 보호되고 있음을 확인할 수 있습니다.

그럼 JWT 인증으로 정보를 가져오기 위하 JWT 토큰을 발행해 봅니다. http://localhost:8000/api/token/에 POST로 usernamepassword를 설정하여 요청을 보냅니다. 여기서 사용하는 username과 password는 우리가 만든 장고(django)의 superuser입니다.(장고 관리자 페이지에 로그인할 때 사용하는 username과 password)

장고(django)에 JWT 사용하기 - JWT 토큰 발행

위와 같이 JWT 토큰이 잘 발행되는 것을 확인할 수 있습니다. 그럼 발행한 JWT 토큰으로 다시 위에 요청(http://localhost:8000/api/blog/posts/)을 보내보도록 하겠습니다. 여기에서는 요청의 HeaderAuthorization을 설정해서 보냅니다. jwt [jwt key]와 같은 형식으로 작성해야 합니다.

장고(django)에 JWT 사용하기 - JWT 토큰을 사용하여 데이터 가져오기

이전과 다르게 데이터를 잘 가져오는 것을 확인할 수 있습니다. 그럼 JWT 토큰을 검증하는 방법과 갱신하는 방법에 대해서 알아보겠습니다. JWT 토큰을 검증하는 방법은 http://localhost:8000/api/token/verify/에 POST로 위에서 발급 받은 토큰을 전송합니다.

장고(django)에 JWT 사용하기 - JWT 토큰 검증

그러면 위와 같이 문제가 없는 것을 확인할 수 있습니다. JWT 토큰에 문제가 있으면 아래와 같이 에러 메세지를 전달 받습니다.

장고(django)에 JWT 사용하기 - JWT 토큰 검증 에러

그럼 JWT 토큰을 갱신(Refresh)해 볼까요? http://localhost:8000/api/token/refresh/에 POST로 이미 발급받은 JWT 토큰을 전달합니다.

장고(django)에 JWT 사용하기 - JWT 토큰 갱신

그럼 위와 같이 새로운 JWT 토큰을 발급받을 수 있습니다. 만약 JWT 토큰을 갱신하지 않거나 유효 기간 이후 갱신하게 되면 아래와 같은 에러 메세지를 확인할 수 있습니다.

장고(django)에 JWT 사용하기 - JWT 토큰 에러

완료

이것으로 장고(django)에서 JWT를 사용하는 방법에 대해서 알아보았습니다. 이제 JWT 토큰을 사용하여 앱과 서버간에 통신이나, 프론트엔드와 서버간 통신에 사용할 수 있습니다.

참고

책 홍보

저도 블로그를 시작한지 1년만에...책을 다 써봅니다...인생에서 이런 날도 오는군요...타국에서 책 출판도 할 수 있고, 참 좋은 세상입니다.

이번에 쓴 책은 스무디 한 잔 마시며 끝내는 React Native입니다. 다양한 예제를 통해 리액트 네이티브를 공부할 수 있도록 구성해 보았습니다. 또한 설치부터 배포까지 실전에서도 사용할 수 있는 내용들을 담고 있습니다.

아래 링크를 통해 제가 쓴 책을 구매하실 수 있습니다.
많은 분들에게 도움이 되면 좋겠네요.
스무디 한 잔 마시며 끝내는 React Native React Native로 실전 스마트폰 앱 만들기
Buy me a coffeeBuy me a coffee
Posts