
아래 코드와 같이 login과 logout에 관한 토큰도 추가해준다. 이전에는 DRF(Django Rest Framwork) 토큰만 작성되어 있었다.
users/urls.py
from django.urls import path
from . import views
urlpatterns = [
path("login", views.Login.as_view()), # django session login
path("logout", views.Logout.as_view()) # django session logout
]
POST 생성
users/views.py
# django의 session을 활용한 로그인
from rest_framework.exceptions import ParseError
from django.contrib.auth import authenticate, login
from rest_framework import status
# api/v1/users/login
class Login(APIView):
def post(self, request):
username = request.data.get("username")
password = request.data.get("password")
if not username or not password:
raise ParseError()
# 위 아래 둘 중 하나 사용
# if not (username and password):
# raise ParseError()
user = authenticate(request, username=username, password=password)
print(user)
if user:
# 아래 코드 실행시 sessionid, csrftoken이 생성
# sessionid는 사용자 세션과 관련된 상태 정보를 식별하기 위한 고유한 식별자입니다.
# 이를 클라이언트(웹 브라우저)에 쿠키로 저장함으로써,
# 브라우저가 서버에 요청할 때마다 해당 세션과 관련된 데이터를 식별하여 사용자 상태를 유지할 수 있습니다.
# 단, localhost:3000에서는 실행 안됨.
# csrftoken: csrf공격 방지를 위해 필요한 토큰
# ex) 은행앱에 로그인된 상태에서 악의적인 사이트에서 어떤 행동들을 하면
# 쿠키에 있는 값을 털어서 해당 사용자인척 은행 API로 송금 요청을 시도
# 일반적으로 Django의 기본 설정에서 세션과 CSRF 토큰은 세션 쿠키와 관련된 도메인과 경로에 한정하여 사용됩니다.
# 따라서 http://127.0.0.1:3000/에서 로그인한 후 다른 URL로 이동하면 해당 URL과 경로에 대한 세션과 CSRF 토큰은 적용되지 않습니다.
login(request, user)
return Response(status=status.HTTP_200_OK)
else:
return Response(status=status.HTTP_403_FORBIDDEN)
sessionid 쿠키를 클라이언트에게 보낸다. 이 쿠키는 사용자의 세션을 식별하는 데 사용된다.csrftoken 쿠키도 클라이언트에 보내진다.로그인 전 → 후
로그인
{
"username":"본인 username",
"password":"본인 pw"
}


- 로그인 전

로그인 POST 후
: 로그인 성공시 { “message”: “success” } 를 출력하려면
class Login(APIView):
def post(self, request):
username = request.data.get("username")
password = request.data.get("password")
if not username or not password:
raise ParseError()
user = authenticate(request, username=username, password=password)
print(user)
if user:
login(request, user)
return Response({"message":"success"}, status=status.HTTP_200_OK) # 이 부분을 수정
else:
return Response(status=status.HTTP_403_FORBIDDEN)
+) config/settings.py에서 아래 코드 부분을 주석처리 해야 로그인이 유지되서 이후 실습인 로그아웃을 할 수 있다.
# REST_FRAMEWORK = {
# "DEFAULT_AUTHENTICATION_CLASSES": [
# "rest_framework.authentication.TokenAuthentication",
# ],
# }
POST 생성
users/views.py
# django의 session을 활용한 로그아웃
from django.contrib.auth import logout
class Logout(APIView):
# login한 user에 대한 확인 필요.
permission_classes = [IsAuthenticated]
def post(self, request):
print("request", request.headers) # header 데이터 체크
logout(request)
return Response(status=status.HTTP_200_OK)


http://localhost:8000/api/v1/users/login{
"username": "your_username",
"password": "your_password"
}http://localhost:8000/api/v1/users/logout사용자 정의 JWT 인증 시스템을 구현하는 방법으로, Django Project에서 Custom JWT 기반 인증을 구현하는 방법에 대해서 설명한다.
Django Project에서 JWT를 사용하기 위해 PyJWT 라이브러리가 필요하다. 이를 설치하기 위해 다음 명령어를 사용한다.
> pip install PyJWT # venv
> poetry add PyJWT # poetry
Django Project의 설정 파일(config/settings.py)에 새로운 인증 클래스를 추가한다. (기존의 인증 방식은 주석 처리)
# config/settings.py
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": [
"config.authentication.JWTAuthentication" # 커스텀 JWT 인증 클래스 사용
],
}
JWTAuthentication 클래스를 생성하여 JWT를 사용한 인증 로직을 구현한다.
이 클래스는 BaseAuthentication을 상속받아 authenticate 메서드를 오버라이드한다.
config/authentication.py 파일 생성
from django.conf import settings
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from users.models import User
import jwt
class JWTAuthentication(BaseAuthentication):
def authenticate(self, request):
token = request.headers.get("jwt-auth")
if not token:
return None
try:
decoded = jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"])
user_id = decoded.get("id")
if not user_id:
raise AuthenticationFailed("Invalid Token")
user = User.objects.get(id=user_id)
return (user, None)
except jwt.ExpiredSignatureError:
raise AuthenticationFailed("Token has expired")
except jwt.DecodeError:
raise AuthenticationFailed("Error decoding token")
except User.DoesNotExist:
raise AuthenticationFailed("User not found")
인증 과정에서 사용할 JWT 토큰을 생성하고 검증하는 로직을 구현한다. 토큰은 사용자 로그인 시 생성되어 반환되며, 이후의 요청에서는 해당 토큰을 헤더에 포함하여 서버로 전송한다.
users/views.py
import jwt
from django.conf import settings
class JWTLogin(APIView):
def post(self, request):
username = request.data.get("username")
password = request.data.get("password")
if not username or not password:
raise ParseError
user = authenticate(request, username=username, password=password)
if user:
payload = {"id": user.id, "username": user.username}
token = jwt.encode(
payload,
settings.SECRET_KEY,
algorithm="HS256",
)
return Response({"token": token})
users/urls.py
urlpatterns = [
path("login/jwt", JWTLogin.as_view()) # jwt login
]
코드를 수정한 뒤 Postman에서 테스트를 진행한다.
(URL [POST] : http://127.0.0.1:8000/api/v1/users/login/jwt)

여기서 발급받은 토큰을 user는 어딘가에 저장해야한다. 하지만 쿠키에 저장하면 보안으로 취약하다. 로컬에 있는 암호화 시킨 파일에 저장하면 그나마 괜찮다.
모든 설정이 완료되었으면, 실제로 API를 호출하여 사용자 정의 JWT 인증 시스템이 정상적으로 작동하는지 테스트한다. API를 호출할 때, HTTP 요청 헤더에 jwt-auth: <jwt_token>을 포함시킨다.
주의사항
: 예외 처리) 인증 과정에서 다양한 종류의 예외가 발생할 수 있으므로, 적절한 예외 처리가 중요하다.
users./views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
from config.authentication import JWTAuthentication
class UserDetailView(APIView):
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]
def get(self, request):
user = request.user
return Response({
"id": user.id,
"username": user.username
})
users/urls.py
urlpatterns = [
path("login/jwt/info", views.UserDetailView.as_view())
]
Postman에서 테스트한다.
(URL [GET] : http://127.0.0.1:8000/api/v1/users/login/jwt/info)

authenticate 함수로 검증한다.jwt.encode를 사용하여 직접 JWT 토큰을 생성한다.PyJWT)를 사용한다.django-rest-framework-simplejwt 사용 (Simple JWT)TokenObtainPairView 및 관련 뷰를 사용하여 자격 증명을 검증하고 토큰을 발급한다.
토큰 생성 및 검증 로직이 라이브러리에 내장되어 있어 복잡한 구현을 요구하지 않는다.
액세스 토큰과 리프레시 토큰을 자동으로 관리한다.
django-rest-framework-simplejwt 설정을 통해 토큰 동작을 조정할 수 있다.
사용 상황에 따른 선택
djangorestframework-simplejwt을 활용한 JWT 인증 방법이다.
djangorestframework-simplejwt 라이브러리 설치한다.
> pip install djangorestframework-simplejwt # venv
> poetry add djangorestframework-simplejwt # poetry
config/settings.py
INSTALLED_APPS = [
...
'rest_framework_simplejwt',
...
]
REST_FRAMEWORK = {
...
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.TokenAuthentication',
'rest_framework_simplejwt.authentication.JWTAuthentication'
)
...
}
BasicAuthentication은 HTTP 기본 인증을 사용한다.SessionAuthentication은 장고의 세션 프레임워크를 이용한 인증을 사용한다.TokenAuthentication은 토큰 기반 인증을 사용한다.settings.py에서 Django REST framework의 기본 인증 설정을 JWT 인증으로 변경한다. (파일내 맨 마지막에 코드를 추가해준다.)
설정값 예시
from datetime import timedelta
SIMPLE_JWT = {
# 액세스 토큰의 유효 기간을 5분으로 설정한다.
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5),
# 리프레시 토큰의 유효 기간을 1일로 설정한다.
'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
# 리프레시 토큰을 갱신할 때마다 새 토큰을 생성하지 않도록 설정한다.
'ROTATE_REFRESH_TOKENS': False,
# 토큰을 갱신한 후 이전 토큰을 블랙리스트에 추가한다.
'BLACKLIST_AFTER_ROTATION': True,
# JWT에 사용할 서명 알고리즘으로 HS256을 사용한다.
'ALGORITHM': 'HS256',
# JWT를 서명하는 데 사용할 키로 Django의 SECRET_KEY를 사용한다.
'SIGNING_KEY': SECRET_KEY,
# JWT 검증에 사용할 키입니다. HS256 알고리즘에서는 None으로 설정된다.
'VERIFYING_KEY': None,
# 인증 헤더의 타입으로 'Bearer'를 사용한다.
# Authorization: Bearer <token>
'AUTH_HEADER_TYPES': ('Bearer',),
# 토큰에 포함될 사용자 식별자 필드로 'id'를 사용한다.
'USER_ID_FIELD': 'id',
# 토큰 클레임에서 사용자 식별자에 해당하는 키로 'user_id'를 사용한다.
'USER_ID_CLAIM': 'user_id',
# 사용할 토큰 클래스로 AccessToken을 사용한다.
'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',),
}
Bearer
표준 규약
: "Bearer" 용어는 OAuth 2.0 표준에서 정의되었으며, 토큰 기반 인증에서 널리 사용된다. 이 표준화된 접근 방식을 사용함으로써, 개발자들은 보편적으로 인정받는 방식을 따를 수 있으며, 다른 시스템과의 호환성을 유지할 수 있다.
명확한 의미 전달
: "Bearer"라는 단어는 토큰의 소지자가 해당 자원에 대한 액세스 권한을 가지고 있다는 것을 명확하게 전달한다. 이는 다른 단어들보다 이 목적에 더 적합하다.
결론적으로, "Bearer"라는 용어는 토큰 기반 인증에서 널리 사용되고 인정받는 표준 용어이기 때문에 사용되고 있다.
자주사용하는 설정값
SIMPLE_JWT = {
"ACCESS_TOKEN_LIFETIME": timedelta(minutes=60),
"REFRESH_TOKEN_LIFETIME": timedelta(days=14),
"SIGNING_KEY": "SECRET",
"ALGORITHM": "HS256",
"AUTH_HEADER_TYPES": ("Bearer",),
}
ACCESS_TOKEN vs REFRESH_TOKEN
액세스 토큰 (Access Token)
리프레시 토큰 (Refresh Token)
설계 팁
ACCESS_TOKEN과 REFRESH_TOKEN을 분리하는 이유
보안 강화를 위한 분리
: 액세스 토큰과 리프레시 토큰을 분리함으로써, 두 토큰이 각각 다른 목적으로 사용되고, 이에 따라 다른 보안 수준을 적용할 수 있다. 액세스 토큰은 짧은 유효 기간을 가지므로, 실제 리소스에 접근하는 데 사용되며, 만약 탈취되더라도 짧은 시간 동안만 유효하다. 반면, 리프레시 토큰은 오랜 기간 동안 유효하지만, 오직 새로운 액세스 토큰을 발급받는 데만 사용된다.
성능과 효율성 향상
: 액세스 토큰은 상대적으로 짧은 유효 기간을 가지므로, 자주 갱신해야 한다. 이는 서버에 부하를 줄이고, 사용자의 요청 처리 속도를 빠르게 하기 위한 선택이다. 짧은 유효 기간의 액세스 토큰을 사용함으로써, 각 요청에 대한 인증 과정을 빠르게 처리할 수 있다.
세션 유지의 편의성
: 리프레시 토큰은 사용자가 자주 로그인을 반복하는 것을 방지한다. 사용자가 서비스를 지속적으로 이용하는 동안에는, 리프레시 토큰을 사용해 자동으로 새로운 액세스 토큰을 발급받을 수 있어, 끊임없는 사용자 경험을 제공할 수 있다.
토큰 탈취에 대한 대응
: 액세스 토큰이 탈취되더라도, 그 피해는 짧은 유효 기간으로 인해 제한적이다. 반면, 리프레시 토큰이 탈취되면 보다 심각한 문제가 발생할 수 있으나, 이를 방지하기 위한 여러 보안 조치를 취할 수 있다.
(예: 안전한 저장, 사용 패턴 모니터링, 재발급 및 무효화 등)
결국, 액세스 토큰과 리프레시 토큰의 분리는 보안과 성능, 그리고 사용자 경험을 모두 고려한 설계 결정이다. 리프레시 토큰의 보안에 특별한 주의를 기울이면서, 이 두 가지 유형의 토큰을 효과적으로 사용하면, 보다 안전하고 효율적인 인증 시스템 구축을 할 수 있다.
REFRESH_TOKEN 탈취되면 보안 끝?
리프레시 토큰의 안전한 저장
: 리프레시 토큰은 매우 중요하므로, 클라이언트 측에서는 이를 안전하게 저장하고 관리해야 한다. 예를 들어, 웹 애플리케이션에서는 쿠키에 저장하는 대신, 보안이 강화된 저장소를 사용해야 한다.
리프레시 토큰의 재발급 및 무효화
: 리프레시 토큰을 사용할 때마다 새로운 리프레시 토큰을 발급하고, 이전 토큰을 무효화하는 방법을 고려해야 한다. 이렇게 하면, 토큰이 탈취되었다 하더라도, 공격자가 오랜 시간 동안 그 토큰을 사용할 수 없게 된다.
리프레시 토큰의 사용 패턴 모니터링
: 서버는 리프레시 토큰의 사용 패턴을 모니터링하여 비정상적인 행동을 감지해야 한다. 예를 들어, 짧은 시간 내에 다수의 액세스 토큰 발급 요청이 발생한다면 이는 의심스러운 행동으로 간주될 수 있다.
리프레시 토큰의 활성화 구역 제한
: 가능하다면, 리프레시 토큰의 사용을 특정 지역이나 IP 주소로 제한하는 것도 좋은 방법이다. 이를 통해 무단 접근을 효과적으로 방지할 수 있다.
두 단계 인증 (Two-Factor Authentication, 2FA)
: 보안을 더욱 강화하기 위해, 사용자 계정에 대한 두 단계 인증을 도입할 수 있다. 이렇게 하면, 공격자가 리프레시 토큰을 탈취했더라도 추가적인 인증 단계가 있어 무단 접근을 방지할 수 있다.
JWT 관련 뷰를 위한 URL을 설정한다.
users/urls.py 코드 추가
from rest_framework_simplejwt.views import (
TokenObtainPairView,
TokenRefreshView,
TokenVerifyView
)
urlpatterns = [
# simple JWT
path("login/simpleJWT", TokenObtainPairView.as_view()),
path("login/simpleJWT/refresh", TokenRefreshView.as_view()),
path("login/simpleJWT/verify", TokenVerifyView.as_view())
]
/login/simpleJWT)/login/simpleJWT/refresh)/login/simpleJWT/verify)위 함수들을 상속 받아 커스텀도 가능하다.
django-rest-framework-simplejwt 라이브러리는 TokenRefreshView와 TokenVerifyView를 이미 제공하므로 이들에 대한 별도의 view 함수를 작성할 필요는 없다. 그러나, 이러한 뷰의 작동 방식을 이해하고 사용자 정의를 하고 싶다면, 기본 뷰를 상속받아 확장할 수 있다.
다음은 TokenRefreshView와 TokenVerifyView를 확장하는 방법을 보여주는 예시다.
TokenRefreshView 확장
: users/views.py에 아래 코드를 추가한다.
from rest_framework_simplejwt.views import TokenRefreshView
from rest_framework.response import Response
class CustomTokenRefreshView(TokenRefreshView):
def post(self, request, *args, kwargs):
# 여기에서 커스텀 로직을 추가할 수 있습니다.
# 예를 들어, 추가 로그 작성, 토큰 갱신 전/후 처리 등을 구현할 수 있습니다.
# 부모 클래스의 post 메소드를 호출하여 기본 동작을 수행합니다.
response = super().post(request, *args, kwargs)
# 추가 응답 데이터나 로직을 여기에 추가할 수 있습니다.
# 예: response.data['custom_field'] = 'custom_value'
return response
TokenVerifyView 확장
: users/views.py에 아래 코드를 추가한다.
from rest_framework_simplejwt.views import TokenVerifyView
from rest_framework.response import Response
class CustomTokenVerifyView(TokenVerifyView):
def post(self, request, *args, kwargs):
# 여기에서 커스텀 로직을 추가할 수 있습니다.
# 예를 들어, 토큰 검증 로그 작성, 추가 검증 로직 등을 구현할 수 있습니다.
# 부모 클래스의 post 메소드를 호출하여 기본 동작을 수행합니다.
response = super().post(request, *args, kwargs)
# 추가 응답 데이터나 로직을 여기에 추가할 수 있습니다.
# 예: response.data['custom_message'] = 'Token is valid'
return response
이러한 커스텀 뷰를 사용하려면, Django의 URL 설정에서 기본 뷰 대신 이 커스텀 뷰를 사용해야 한다. (users/urls.py에 코드를 추가한다.)
from django.urls import path
from .views import CustomTokenRefreshView, CustomTokenVerifyView
urlpatterns = [
# ...
path('login/simpleJWT/refresh', CustomTokenRefreshView.as_view(), name='token_refresh'),
path('login/simpleJWT/verify', CustomTokenVerifyView.as_view(), name='token_verify'),
]
이 방법으로 django-rest-framework-simplejwt의 기본 동작에 추가적인 로직을 적용할 수 있다.
액세스 토큰 만료 감지
: 프론트엔드 애플리케이션은 일반적으로 액세스 토큰의 만료를 감지하는 로직을 구현한다. 이는 토큰의 유효기간 정보를 확인하거나, 서버로부터의 응답 상태 코드를 통해 이루어질 수 있다.
리프레시 토큰 사용
: 액세스 토큰이 만료되면, 프론트엔드는 저장된 리프레시 토큰을 사용하여 TokenRefreshView에 요청을 보내 새로운 액세스 토큰을 받는다.
새로운 액세스 토큰 사용
: 새로 발급받은 액세스 토큰을 이후의 요청에 사용하여 서비스를 지속적으로 이용한다.
즉, 프론트엔드 개발자는 액세스 토큰이 만료되었을 때, 리프레시 토큰만 서버에 보내 새 액세스 토큰을 받는다. 서버는 리프레시 토큰을 검증하고, 유효하다면 새로운 액세스 토큰을 발급한다.

Postman : 유저 데이터
(URL [GET] : http://127.0.0.1:8000/api/v1/users/login/jwt/info)
users/view
from config.authentication import JWTAuthentication
class UserDetailView(APIView):
# authentication_classes = [JWTAuthentication] ## 주석 처리 필요
permission_classes = [IsAuthenticated]
def get(self, request):
user = request.user
return Response({"id":user.id, "username":user.username})

{
"refresh": "your_refresh_token_here"
}
Postman
{
"token": "your_access_token_here"
}
토큰이 유효할 시 → { }

token이 유효하지 않을 시 → 아래 오류
{
"detail": "Token is invalid or expired",
"code": "token_not_valid"
}

아직도 인증 주는거에 대해서 완벽하게 이해하진 못했는데,,, 다양한 방법이 있다는 건 알겠다😂