-
[Django] DRF jwt 인증방식을 이용한 로그인, 회원가입 구현하기Django 개발 2022. 3. 28. 01:35
JWT(Json Web Token)란?
JWT란 모바일이나 웹의 사용자 인증을 위해 사용하는 암호화된 토큰을 의미합니다. JWT는 클라이언트에서 Http 리퀘스트 헤더에 JSON 토큰을 넣어 보내면 서버는 헤더에 포함되어 있는 JWT 정보를 통해 인증합니다.
Django Built-in Token과 JWT 차이점
DRF's builtin Token Authentication
1. One Token for all sessions
2. No time stamp on the token
DRF JWT Token Authentication
1. One Token per session
2. Expiry timestamp on each token위에 첨부한 스택오버플로우에서 가져온 Built-in Token과 JWT 차이점에 관한 답변입니다.
- Built-in Token은 한 개의 토큰으로 모든 세션을 관리하며, 시간을 기록하지 않기 때문에 한 번 발급되면 만료되지 않는다. 만약 중간에 토큰이 해킹당할 경우 보안에 심각한 문제를 야기할 수 있다.
- JWT는 한 세션당 한 개의 토큰이 발급되며 영구적이지 않고 만료되기 때문에 access token이 만료될 경우 refresh token을 통해 새로운 access token을 발급받는다.
1. 환경 세팅
djagno의 djangorestframework-jwt는 2019년 7월 8일 이후로 더 이상 업데이트 되지 않으니 djangorestframework-simplejwt 라이브러리를 사용하시길 권장합니다.
(1) pip를 사용하여 설치하기
pip install djangorestframework-simplejwt
(2) docker를 사용하여 설치하기
requirements.txt파일에 simplejwt를 추가해주세요.
djangorestframework-simplejwt==5.0.0
settings.py
INSTALLED_APPS = [ ... 'rest_framework_simplejwt', 'rest_framework_simplejwt.token_blacklist', ... ] REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework_simplejwt.authentication.JWTAuthentication', ), } REST_USE_JWT = True SIMPLE_JWT = { 'ACCESS_TOKEN_LIFETIME': timedelta(hours=2), 'REFRESH_TOKEN_LIFETIME': timedelta(days=7), 'ROTATE_REFRESH_TOKENS': False, 'BLACKLIST_AFTER_ROTATION': True, 'TOKEN_USER_CLASS': 'user.User', // 자신의 User 모델 연결 }
token_blacklist는 필요없는 token이나 해킹된 token을 서버에서 사용할 수 없도록 관리하여 보안을 높일 수 있도록 도와주는 앱으로 사용하지 않으신다면 추가하지 않으셔도 됩니다.
ACCESS_TOKEN_LIFETIME
access token이 유효한 기간을 지정하는 datetime.timedelta 객체
REFRESH_TOKEN_LIFETIME
refresh token이 유효한 기간을 지정하는 datetime.timedelta 객체
ROTATE_REFRESH_TOKENS
True로 설정할 경우, refresh token을 보내면 새로운 access token과 refresh token이 반환된다.
BLACKLIST_AFTER_ROTATION
True로 설정될 경우, 기존에 있던 refresh token은 blacklist가된다.
↓ 더 자세한 정보가 궁금하시다면
urls.py
from django.urls import path from rest_framework_simplejwt.views import TokenObtainPairView from rest_framework_simplejwt.views import TokenRefreshView from rest_framework_simplejwt.views import TokenVerifyView urlpatterns = [ # 로그인/회원가입 path('login/', views.JWTLoginView.as_view()), path('signup/', views.JWTSignupView.as_view()), # 토큰 path('token/', TokenObtainPairView.as_view(), name='token_obtain_pair'), path('token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), path('token/verify/', TokenVerifyView.as_view(), name='token_verify'), ... ]
2. JWT 인증방식을 이용한 회원가입 구현
serializers.py
class UserJWTSignupSerializer(serializers.ModelSerializer): id = serializers.CharField( required=True, write_only=True, max_length=20 ) password = serializers.CharField( required=True, write_only=True, style={'input_type': 'password'} ) birth = serializers.DateField( required=True, write_only=True, ) subscription_date = serializers.DateField( required=False, write_only=True, ) class Meta(object): model = User fields = ['id', 'password', 'birth', 'subscription_date'] def save(self, request): user = super().save() user.id = self.validated_data['id'] user.birth = self.validated_data['birth'] user.subscription_date = self.validated_data['subscription_date'] user.set_password(self.validated_data['password']) user.save() return user def validate(self, data): id = data.get('id', None) if User.objects.filter(id=id).exists(): raise serializers.ValidationError("user already exists") data['subscription_date'] = date.today() return data
회원가입 serializer에서는 required=True로 설정된 id, password, birth 값을 request에서 보내주면 validate 함수에서 user가 존재하는지 체크한 후에 오늘 날짜를 subscription_date에 저장하여 return합니다.
그러면 save 함수가 해당 유저 데이터를 저장합니다. django_rest_auth 라이브러리를 공부할 때 봤던 내용이지만 커스텀 serializer를 사용할 경우 save 함수를 무조건 정의해줘야합니다.
views.py
class JWTSignupView(APIView): ... serializer_class = JWTSignupSerializer def post(self, request): serializer = self.serializer_class(data=request.data) if serializer.is_valid(raise_exception=False): user = serializer.save(request) token = RefreshToken.for_user(user) refresh = str(token) access = str(token.access_token) return JsonResponse({'user': user, 'access': access, 'refresh': refresh}) else: ...
위에서 커스텀하게 만들어준 JWTSignupSerializer를 연결하고 오류가 발생하지 않았다면 serializer.save(request)를 해서 user를 return 받는다. 그 후 token을 발급하고 JsonResponse를 통해 프론트에 해당 데이터를 보내준다.
Request 예시
{ "id": "abc", "password": "123456", "birth": "2022-01-01", }
Response 예시
{ "user": { ... }, "access": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNjQ1NjY4MjA1LCJpYXQiOjE2NDU2NjEwMDUsImp0aSI6IjkyYzE1OTRkYzYyYjRmNWFiZWE2ZDE3NGZjMWE4ZjM4IiwidXNlcl9pZCI6MjV9.ePTbpUjWRcuraASj3Ya_1aAbYQp8dODftPU7PhWZsQY", "refresh": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImV4cCI6MTY0NjI2NTgwNSwiaWF0IjoxNjQ1NjYxMDA1LCJqdGkiOiI1MzUwY2Q2YTAwODE0OGM4YjEzZDk3YmI2NmRhM2I5ZCIsInVzZXJfaWQiOjI1fQ.wqMwLU3c3Tn3WTZW3JTR5dBZaWvjg-4BloYE9ipqwzA", }
3. 로그인 views, serializers 코드
serializer.py
class JWTLoginSerializer(serializers.ModelSerializer): id = serializers.CharField( required=True, write_only=True, ) password = serializers.CharField( required=True, write_only=True, style={'input_type': 'password'} ) class Meta(object): model = User fields = ['phone', 'password'] def validate(self, data): id = data.get('id', None) password = data.get('password', None) if User.objects.filter(id=id).exists(): user = User.objects.get(id=id) if not user.check_password(password): raise serializers.ValidationError("wrong password") else: raise serializers.ValidationError("user account not exist") token = RefreshToken.for_user(user) refresh = str(token) access = str(token.access_token) data = { 'user': user, 'refresh': refresh, 'access': access, } return data
로그인 serializer에서는 아이디와 비밀번호를 입력받아 아이디가 매칭되는 유저가 있는지 확인하고 없다면 오류를 발생시킵니다. 유저가 존재한다면 비밀번호를 체크하고 일치하지 않을 경우 오류를 발생시킵니다.
만약 유저가 존재하고 아이디와 비밀번호가 일치한다면 토큰을 발급하고 해당 데이터를 return 합니다.
views.py
class JWTLoginView(APIView): ... serializer_class = JWTLoginSerializer def post(self, request): serializer = self.serializer_class(data=request.data) if serializer.is_valid(raise_exception=False): user = serializer.validated_data['user'] access = serializer.validated_data['access'] refresh = serializer.validated_data['refresh'] return JsonResponse({ 'user': user 'access': access, 'refresh': refresh) }) else: ...
회원가입 뷰와 마찬가지로 JWTLoginSerializer를 연결하고, serializer.validated_date['key값']을 통해 원하는 데이터를 가져옵니다. 그리고 JsonResponse를 통해 프론트에 해당 데이터를 보내줍니다.
Request 예시
{ "id": "abc", "password": "123456" }
Response 예시
{ "user": { ... }, "access": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNjQ1NjcxNzE0LCJpYXQiOjE2NDU2NjQ1MTQsImp0aSI6ImYxNWQzMTQ5YmI3ODQ0NGY5MGI5OGUzM2Y4NmIwZTM2IiwidXNlcl9pZCI6MX0.l2FtnDkFVbRF7hrikPZ3cglD4jIjKM3a06PqbBmJKRU", "refresh": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImV4cCI6MTY0NjI2OTMxNCwiaWF0IjoxNjQ1NjY0NTE0LCJqdGkiOiIyZjAzNGE2ZTRlZWE0ZWUzYmIyMzNhODBkNTVmZjlkZCIsInVzZXJfaWQiOjF9.gfezGuNjqn6odrfy6wzOXXMCtePryMJ9Ax6E5ur554E", }
참고 문서
↓ Simple JWT 공식 문서
반응형'Django 개발' 카테고리의 다른 글
[Pycharm]디버깅 No such file or directory 오류 해결 (0) 2022.05.20 [Docker] No such file or directory: 'docker' 오류 해결 (0) 2022.04.25 [Django] postman 로그인 API CSRF token missing 오류 해결 (1) 2022.02.21 [Django] Django serialize를 사용하여 queryset을 json으로 변경하기 (0) 2021.09.02 [Django] Ajax serialize를 사용하여 form 데이터 전송하기 (0) 2021.09.01