본문 바로가기

장고(DJango)

(작성중) 장고 JWT 이용하여 회원가입, 로그인 기능 구현 (Django JWT Token)

JWT(JSON Web Token)란?

http 프로토콜을 통해 사용자는 웹사이트를 이용한다. 그런데 이 http 프로토콜의 특징 중 하나가 바로 stateless, 즉 상태 유지를 하지 않는다는 것이다. 이렇게 되면 사용자는 매번 사이트의 다른 페이지에 접근할 때마다 로그인을 해주어야 하는데 등의 큰 불편함이 있다. 예를 들어 네이버에서 로그인을 하고 블로그 페이지를 누르면 로그아웃이 되어 다시 로그인을 해주어야 하는 등의 문제가 발생한다.

이와 같은 문제들을 해결하기 위해 세션, 쿠키, 토큰 등의 기능을 이용하는 것이다. 관련한 자세한 내용은 다음 포스팅을 참조하길 바란다.

https://hooeverything.tistory.com/12

 

여러 가지 방법 중 관리가 편하고, 서버 측 자원이 많이 소모되지 않는 JWT를 이용해 장고 로그인, 회원가입 기능을 구현하기로 하였다.

토큰을 사용하는 핵심적인 이유는 사용자에 대한 정보들을 저장하기 위해서 이다.

 

jwt 문서

https://jwt.io/introduction


 

이제 JWT 토큰의 구조와 내용에 대해서 알아보자.

JWT는 위와 같이 세 부분으로 나누어지며 각 부분은.으로 구분한다. 해당 문자들은 Base64Url 인코딩을 사용하여 정한다.

Header - 토큰의 타입과 서명 생성에 어떤 알고리즘이 사용되었는지를 저장하고 있다. 서명에 대한 설명은 추후 포스팅 예정이다.

{
	"typ": "JWT",
	"alg": "HS512"
}

 

payload - 사용자의 정보들을 저장하고 있다. 서버는 JWT를 클라이언트에게서 받으면 이것이 올바른 토큰인지 확인 후 payload 안의 내용을 읽고 이에 맞는 응답을 보내준다.

payload 안에 넣을 값들은 사용자가 지정할 수도 있고, 표준 스펙을 사용할 수도 있다.

 

{
	"id": "test",
	"email": "abcd@naver.com"
}

 

 

signature - JWT의 보안을 책임지는 부분이다.

signature는 인코딩 된 header, 인코딩 된 payload, secret을 이용하여 만든다.

 

더보기

Simple JWT에서는 secret으로 장고의 settings.py에 있는 sercret_key를 기본으로 이용한다.

 

이 셋을 합쳐 header에 지정해 둔 알고리즘을 이용해 해싱한다.

 

이렇게 해싱한 덕에 변조와 열람을 막을 수 있다.

 


로그인/로그아웃 구현

 

이제 JWT가 무엇인지 간략하게나마 알게 되었으니 회원가입과 로그인 기능을 구현해보자. 기본 구현 방법은 아주아주 쉽다.

 

다른 장고와 관련된 jwt 패키지는 서비스를 중단하였기 때문에 이 simplejwt를 이용하였다.

restframework는 반드시 설치되어 있어야 한다.

https://django-rest-framework-simplejwt.readthedocs.io/en/latest/index.html

pip install djangorestframework-simplejwt
pip install djangorestframework

설치가 완료되었으면 settings.py를 수정하자.

INSTALLED_APPS에 'rest_framework'를 추가해 준다.

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django.contrib.sites',
   
    'rest_framework',
]

 

두 번째로 REST_FRAMEWORK를 추가해 준다.

REST_FRAMEWORK = {
#	모든 서버 호출시 인증이 필요함.
#    'DEFAULT_PERMISSION_CLASSES': (
#        'rest_framework.permissions.IsAuthenticated',
#    ),
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    ),
}

 

 

DEFAULT_PERMISSION_CLASSES는 기본 허가 클래스이다. 사이트 접속 시 모든 사용자에게 접속을 허가할지, 어떤 인증을 거친 사람만 허가할지 등 웹 사이트 접근 권한에 관한 설정이다. rest_framework.permissions.IsAuthenticated는 인증된 사용자에게만 올바른 응답을 해주겠다는 뜻이다. 기본 설정은 인증 필요 없음이다.

 

DEFAULT_AUTHENTICATION_CLASSES는 기본 인증 클래스이다. 기본 인증에 어떤 방법을 사용할지 정하는 것이다.

rest_framework_simplejwt.authentication.JWTAuthentication는 우리가 받은 sinplejwt에서 JWT 인증을 기본으로 사용하겠다는 말이다.

 

이제 setting.py는 끝났다.

urls.py를 수정하자.

from django.contrib import admin
from django.urls import path
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView, TokenVerifyView

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
    path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
    path('api/token/verify/', TokenVerifyView.as_view(), name='token_verify'),
]

 

놀랍게도 이렇게만 해주면 JWT 토큰을 이용한 인증이 가능하다.

 

api/token/에 POST 방식으로 createsuperuser를 통해 만든 계정의 아이디와 패스워드를 보내주면, access token과 refresh token을 반환해 준다.

서버를 실행시키자.

 

1. curl을 이용한다.

curl \
-X POST\
-H "Content-Type: application/json" \
-d '{"username": "qwer", "password": "12341234"}' \
http://localhost:8000/api/token/

 

2. postman을 이용한다.

 

api/token/refresh/ 는 처음 받은 refresh token을 전달하면, 새로운 access token을 돌려준다. access token이 만료되었을 때 등에 사용한다.

 

1. curl

curl \
-X POST \
-H "Content-Type: application/json" \
-d '{"refresh":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX3BrIjoxLCJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImNvbGRfc3R1ZmYiOiLimIMiLCJleHAiOjIzNDU2NywianRpIjoiZGUxMmY0ZTY3MDY4NDI3ODg5ZjE1YWMyNzcwZGEwNTEifQ.aEoAYkSJjoWH1boshQAaTkf8G3yn0kapko6HFRt7Rh4"}' \
http://localhost:8000/api/token/refresh/

 

2. postman

 

 

username과 password는 넣지 않아도 된다.

 

 

api/token/verify/ 는 토큰이 올바른지 확인하기 위해 사용한다.

 

1. curl

curl \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX3BrIjoxLCJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiY29sZF9zdHVmZiI6IuKYgyIsImV4cCI6MTIzNDU2LCJqdGkiOiJmZDJmOWQ1ZTFhN2M0MmU4OTQ5MzVlMzYyYmNhOGJjYSJ9.NHlztMGER7UADHZJlxNG0WSi22a2KaYSfd1S-AuT7lU" \
http://localhost:8000/api/verify/

 

2. postman

올바를 경우

 

올바르지 않을 경우

 

 

 

그럼 이렇게 만든 토큰을 이용해서 인증된 사용자만 접속할 수 있게 하려면 어떻게 해야 하는지 알아보자.

 

1. 모든 페이지에 접속할 때마다 인증하게 하기.

위에 REST_FRAMEWORK에서 주석처리해둔 것을 풀어준다. 이렇게 하면 모든 접속 때마다 헤더에 인증 토큰을 첨부해서 보내야 한다.

그리고 이 인증에서 주의할 점은 Django 방식이 아니라 Django restframework 방식의 리턴 즉 api 리턴 방식에만 적용된다.

 

FBV, CBV 방식으로 각각 작성하였다.

 

views.py

from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework.views import APIView


# 데코레이터 반드시 필요함.
@api_view(['GET'])
# @permission_classes([IsAuthenticated])
def test(request):
    content = {'test': "good"}
    return Response(content)


class ExampleView(APIView):
    def get(self, request, format=None):
        content = {'status': 'request was permitted'}
        return Response(content)

 

urls.py

from django.urls import path

from accounts.views import test, ExampleView

urlpatterns = [
    path('test/', test),
    path('example/', ExampleView.as_view())
]

 

이제 접속을 시도해보자.

 

 

인증이 필요하다는 문구가 나온다.

 

그럼 이제 헤더에 발급받은 JWT 토큰을 넣고 request를 보내보자.

 

위와 같이 헤더에 Bearer JWTTOKEN을 넣게 되면 원하던 response를 해 주는 것을 볼 수 있다. Bearer는 토큰의 타입이 무엇인지 명시해 주는것이다. 마치 c++에서 정수형 데이터를 선언할 때 int를 사용하듯이 말이다.


참고

 

https://www.qu3vipon.com/django-jwt

https://brunch.co.kr/@jinyoungchoi95/1

https://django-rest-framework-simplejwt.readthedocs.io/en/latest/getting_started.html