drf-jwt
원래 drf로 회원가입을 만들때 jwt가 가장 많이 사용된다고 한다. 그러나 Release History 를 보면 2017년이 마지막인걸 볼 수 있고, 개발이 다 끝나고 나서 약간의 문제점은 처음 username, password 를 통해 인증 받을 때, 토큰 (access_token) 하나만 보내준다. 그리고 나서 access_token 갱신 할 때, refresh_token 을 통해서 access_token 갱신을 해 줘야되는데 여기서 refresh_token 을 가지고 있지 않아서 access_token 갱신이 어려웠다. 그리고 결정적으로 jwt를 만든사람도 simple jwt사용하라고 하길래 바로 simple-jwt에 대해 알아보기 시작했다.
Simpe-JWT는 DRF을 위해 JSON Web Token authentication을 제공한다. 라이브러리 사용 방법은 djangorestframework-jwt 와 비슷하고 처음 인증할때, access_token, refresh_token 둘 다 보내준다. 그래서 djangorestframework-simplejwt로 개발하게 되었다.
우선 simple-jwt를 설치해주고 세팅해주어야 한다.
pip install djangorestframework-simplejwt
# settings.py
# 유저도 settings에서 꼭 설정해주어야 함
AUTH_USER_MODEL = 'product.User'
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'product',
'rest_framework',
# simple-jwt 추가해주기
'rest_framework_simplejwt',
]
...
REST_FRAMEWORK = {
...
'DEFAULT_AUTHENTICATION_CLASSES': (
...
'rest_framework_simplejwt.authentication.JWTAuthentication',
)
...
}
...
# 추가적인 JWT_AUTH 설정
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=30),
'REFRESH_TOKEN_LIFETIME': timedelta(days=7),
'ROTATE_REFRESH_TOKENS': False,
'BLACKLIST_AFTER_ROTATION': False,
'UPDATE_LAST_LOGIN': False,
'ALGORITHM': 'HS256',
'SIGNING_KEY': SECRET_KEY,
'VERIFYING_KEY': None,
'AUDIENCE': None,
'ISSUER': None,
'JWK_URL': None,
'LEEWAY': 0,
'AUTH_HEADER_TYPES': ('Bearer',),
'AUTH_HEADER_NAME': 'HTTP_AUTHORIZATION',
'USER_ID_FIELD': 'id',
'USER_ID_CLAIM': 'user_id',
'USER_AUTHENTICATION_RULE': 'rest_framework_simplejwt.authentication.default_user_authentication_rule',
'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',),
'TOKEN_TYPE_CLAIM': 'token_type',
'TOKEN_USER_CLASS': 'rest_framework_simplejwt.models.TokenUser',
'JTI_CLAIM': 'jti',
'SLIDING_TOKEN_REFRESH_EXP_CLAIM': 'refresh_exp',
'SLIDING_TOKEN_LIFETIME': timedelta(minutes=5),
'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=1),
}
위 코드에서 SIMPLE_JWT = {}
라고 되어 있는 부분은 다 쓸 필요는 없고 필요한 부분만 커스텀해서 작성해주면 된다.
Django에 내장된 유저 모델은 프로젝트와 맞지 않은 부분이 많아서 커스텀해주기로 했다.
class UserManager(BaseUserManager):
use_in_migrations = True
def create_user(self, login_id, email, password, **kwargs):
if not email:
raise ValueError('Users must have an email address')
user = self.model(
login_id=login_id,
email=email,
)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, login_id=None, email=None, password=None, **extra_fields):
superuser = self.create_user(
login_id=login_id,
email=email,
password=password,
)
superuser.is_staff = True
superuser.is_superuser = True
superuser.is_active = True
superuser.save(using=self._db)
return superuser
class User(AbstractBaseUser, PermissionsMixin):
login_id = models.CharField(max_length=30, unique=True, null=False, blank=False)
email = models.EmailField(max_length=30, unique=True, null=False, blank=False)
is_superuser = models.BooleanField(default=False)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
objects = UserManager()
USERNAME_FIELD = 'login_id'
REQUIRED_FIELDS = ['email']
class Meta:
db_table = 'user'
회원가입할때부터 JWT token을 발급해주기로 했다.
# serialzier.py
User = get_user_model()
class RegisterSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = '__all__'
def create(self, validated_data):
login_id = validated_data.get('login_id')
email = validated_data.get('email')
password = validated_data.get('password')
user = User(
login_id=login_id,
email=email
)
user.set_password(password)
user.save()
return user
# views.py
# 회원가입
class RegisterAPIView(APIView):
def post(self, request):
serializer = RegisterSerializer(data=request.data)
if serializer.is_valid():
user = serializer.save()
# jwt token 접근해주기
token = TokenObtainPairSerializer.get_token(user)
refresh_token = str(token)
access_token = str(token.access_token)
res = Response(
{
"user": serializer.data,
"message": "register successs",
"token": {
"access": access_token,
"refresh": refresh_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(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
# urls.py
urlpatterns = [
path("", include(router.urls)),
path("register/", RegisterAPIView.as_view()), #회원가입하기
]
포스트맨으로 테스트 해주면 아래처럼 생성된 유저하고 토큰들이 발급된다.
코드가 복잡해져서 이렇게 사용하는게 맞는지 모르겠는데 추후에 조금 더 수정할것이다.
# serialzier.py
User = get_user_model()
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = '__all__'
# views.py
# 로그인
class AuthView(APIView):
def post(self, request):
user = authenticate(
login_id=request.data.get("login_id"), password=request.data.get("password")
)
if user is not None:
serializer = UserSerializer(user)
token = TokenObtainPairSerializer.get_token(user)
refresh_token = str(token)
access_token = str(token.access_token)
res = Response(
{
"user": serializer.data,
"message": "login success",
"token": {
"access": access_token,
"refresh": refresh_token,
},
},
status=status.HTTP_200_OK,
)
return res
else:
return Response(status=status.HTTP_400_BAD_REQUEST)
# urls.py
urlpatterns = [
path("", include(router.urls)),
path("register/", RegisterAPIView.as_view()), #회원가입하기
path("auth/", AuthView.as_view()), #로그인하기
]
포스트맨으로 로그인 테스트를 해보았다.
아직 로그아웃을 구현하지 못했는데 다음에는 로그아웃도 구현하는것도 추후에 할 예정이다
access_token은 5분만 지나면 만료되기 때문에 refresh_token을 가지고 access_token을 재발급해야 된다. 이는 따로 코드를 구현하지 않고 simplejwt에 내장된 기능으로 구현했다.
# urls.py
from rest_framework_simplejwt.views import TokenRefreshView
urlpatterns = [
path("", include(router.urls)),
path("register/", RegisterAPIView.as_view()), #회원가입하기
path("auth/", AuthView.as_view()), #로그인하기
path('auth/refresh/', TokenRefreshView.as_view()),#토큰 재발급하기
]
이렇게 설정을 해주고 포스트맨에 refresh_token과 함께 전송을 하면 아래와 같이 access_token이 다시 발급된다.
감사합니다.