Django에선 편하게 restframework-simplejwt를 이용해서 jwt 인증을 구현할 수 있다. jwt에 필요한 기능은 아래와 같다.
- 로그인
- 토큰 유효성 검사
- 로그아웃
또한 우리는 Blacklist 를 포함해서 구현할 것이다.
설정
먼저 라이브러리를 설치한다. poetry를 사용하는 경우엔 poetry add를 하면 된다.
pip install djangorestframework-simplejwt
settings.py에 아래와 같이 추가한다. 라이브러리의 앱과 JWT 사용 옵션, 인증 클래스를 추가한다.
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"rest_framework",
# simplejwt start
"rest_framework_simplejwt",
"rest_framework_simplejwt.token_blacklist",
"account",
# simplejwt end
"users",
"snippets",
]
REST_USE_JWT = True
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": (
# simplejwt
"rest_framework.authentication.BasicAuthentication",
"rest_framework.authentication.SessionAuthentication",
"rest_framework_simplejwt.authentication.JWTAuthentication",
),
"DEFAULT_PERMISSION_CLASSES": [
"rest_framework.permissions.IsAuthenticated",
],
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
"PAGE_SIZE": 10,
}
JWT_AUTH = {
"JWT_SECRET_KEY": SECRET_KEY,
"JWT_ALGORITHM": "HS256",
"JWT_ALLOW_REFRESH": True,
"JWT_EXPIRATION_DELTA": datetime.timedelta(minutes=1),
"JWT_REFRESH_EXPIRATION_DELTA": datetime.timedelta(days=3),
"ROTATE_REFRESH_TOKENS": True,
"BLACKLIST_AFTER_ROTATION": True,
}
account 앱을 만들고 앱에 urls.py 파일을 생성한다.
django-admin startapp account
인증 구현
views.py 에 아래 코드를 작성한다. TokenVerifyBlackView는 verify에 Blacklist 체크 기능을 추가한 뷰이다. 코드 순서는 아래와 같다.
- 유효성 검사
- BlacklistedTokend은 UntypedToken을 참조하므로 토큰을 UntypedToken으로 감쌈
- UntypedToken에서 해당 토큰 가져옴. 없으면 예외 처리
- BlacklistedToken에 필터로 사용해 존재 유무 체크. 존재하면 Blacklisted 된 토큰이므로 예외 처리
- 200 코드 반환
from rest_framework_simplejwt.views import TokenVerifyView
from rest_framework_simplejwt.exceptions import InvalidToken, TokenError
from rest_framework_simplejwt.token_blacklist.models import BlacklistedToken
from rest_framework_simplejwt.tokens import UntypedToken
from rest_framework import status
from rest_framework.response import Response
from rest_framework.serializers import ModelSerializer
from rest_framework_simplejwt.token_blacklist.models import OutstandingToken
from rest_framework_simplejwt.views import TokenVerifyView
from rest_framework_simplejwt.exceptions import InvalidToken, TokenError
class TokenBlackVerifyView(TokenVerifyView):
def post(self, request, *args, **kwargs):
serializer: ModelSerializer = self.get_serializer(data=request.data)
try:
serializer.is_valid(raise_exception=True)
except TokenError as e:
raise InvalidToken(e.args[0])
token = UntypedToken(serializer.initial_data["token"])
# Check if token is in the blacklist
try:
outstanding_token = OutstandingToken.objects.get(token=token)
if BlacklistedToken.objects.filter(token=outstanding_token).exists():
raise InvalidToken("Token is blacklisted")
except OutstandingToken.DoesNotExist:
raise InvalidToken("Token does not exist")
return Response(status=status.HTTP_200_OK)
이제 account 앱의 urls.py에 뷰를 추가해준다.
from django.contrib import admin
from django.urls import path, include
from rest_framework_simplejwt.views import TokenVerifyView
from rest_framework import routers
from users.views import UserViewSet, GroupViewSet
router = routers.DefaultRouter()
router.register(r"users", UserViewSet)
router.register(r"groups", GroupViewSet)
urlpatterns = [
path("admin/", admin.site.urls),
# Add Log in Button in rest_framework page
path("api-auth/", include("rest_framework.urls")),
# simple-jwt
path("account/", include("account.urls")),
path("snippets/", include("snippets.urls")),
]
urlpatterns += router.urls
확인
이제 각 API를 확인해보자.
로그인
로그인 API에 superuser로 로그인한다. refresh와 access token을 확인할 수 있다.
유효성 확인
verify API에 refresh를 복사해서 Post 요청을 보낸다. 아래와 같이 200 코드를 볼 수 있다.
로그아웃
아까 복사한 토큰을 넣어서 logout API에 Post로 요청한다. 200 코드를 확인할 수 있다.
한 번 더 요청하면 아래와 같이 이미 블랙리스트에 등록 되었단 메세지를 볼 수 있다.
다시 verify API로 가서 동일한 토큰으로 요청을 보내면 아래와 같이 Blacklisted 된 토큰이라는 메세지를 볼 수 있다.
Why token put in Blacklist?
logout을 할 때 해당 토큰을 Blacklist에 넣는 이유는 해당 Refresh 토큰이 탈취 되었을 때를 방지하는 것이다. 어떠한 원인에 의해 토큰이 탈취 당했을 때 logout이 단순히 클라이언트에서 토큰을 지우는 기능만 한다면 해커가 해당 토큰을 이용해서 악의적인 요청을 서버에 보낼 수 있게 된다. 그러므로 아에 Refresh 토큰을 Blacklist에 추가해서 재사용이 불가능하게 만들어 토큰이 탈취 되었을 때를 방지한다. Blacklist에 추가 해도 여전히 logout을 하지 않는다면 토큰을 탈취 당해 악의적인 요청을 당할 위험이 있으므로 외부에서 계정을 이용할 땐 꼭 logout을 해야 한다.
'Backend > Django' 카테고리의 다른 글
Django docker-compose 에서 로컬, 도커 환경 분리하기 (0) | 2024.03.16 |
---|---|
Django Abstract Model 사용 (0) | 2024.03.13 |
Django 멀티 디비 설정 (0) | 2024.03.13 |
Django order_by와 annotate, extra 활용 (0) | 2024.03.13 |
Django queryset 파보기 (0) | 2024.03.12 |