우선 로그인 뷰는 FBV로 만들어 볼게요.
그 이유는 그렇게 복합적이고 많은 기능을 포함하지 않거든요.
users/views.py
from django.contrib.auth import authenticate
# 생략
@api_view["POST"]
def login(request):
username = request.data.get('username')
password = request.data.get('password')
if not username or not password:
return Response(status=status.HTTP_400_BAD_REQUEST)
user = authenticate(username=username, password=password)
authenticate(username=username, password=password)
로그인을 해주는 장고 메서드입니다.
auth모듈에서 가져와 메서드를 임포트 해줄게요.
users/urls.py
from django.urls import path
from . import views
app_name = "users"
urlpatterns = [
path("", views.UsersView.as_view()),
path("token", views.login), # 새롭게 추가
path('me/', views.MeView.as_view()),
path('me/favs/', views.FavsView.as_view()),
path('<int:pk>/', views.user_detail),
]
여기까지 어느 정도 작성을 마쳤다면, 이제 JWT(Json Web Token)로 들어가야해요.
아래와 같이 생겼어요.
실제 만들어 보고(encode) 만들었던걸 복호화(암호 해동? Decoded!)도 해보조~!
관련 링크 : https://pyjwt.readthedocs.io/en/latest/usage.html
pip install PyJWT
users/views.py
import jwt # 새로추가
from django.contrib.auth import authenticate
from django.conf import settings # 새로추가
# 생략
@api_view["POST"]
def login(request):
username = request.data.get('username')
password = request.data.get('password')
if not username or not password:
return Response(status=status.HTTP_400_BAD_REQUEST)
user = authenticate(username=username, password=password)
if user is not None:
encoded_jwt = jwt.encode({'pk':user.pk}, settings.SECRET_KEY , algorithm='HS256')
return Response(data={'token': encoded_jwt})
else:
return Response(status=staut.HTTP_401_UNAUTHORIZED)
바디에 정보를 담아서 POST요청을 날리면 token정보를 다시 확인 할 수 있어요.
JWT는 사용하기 쉽기에 인기가 많조. 반대로 사용하기 쉽다는건 복호화 하기도 쉽겠네?라는 지적을 듣기도 쉽습니다. 그래서! 반드시 토큰에는 민감한 정보(비밀번호, 이메일, 이름)를 절대! 넣어서는 안되요. 단순한 테이블의 PK값만 넣어줘서 식별할 수 있는 값만 넣어줘도 충분합니다.
다시한번, 누구나! decoded
할 수 있어요. 그런데 서버는 우선 토큰을 받으면 해당 정보를 까서 무엇인지 확인 할 수 있어요. 그런데 이 서버는 받은 토큰에 어떤 변경사항이 하나라도 있었는지 판단해요.
그래서 실제 우리는 토큰에 어떤정보가 있었는 것보다 중요한건 누가 token을 건들지 않았는지?!
확인하는 것이 더 중요해요!
이제 복호화 과정을 거치고 쭉쭉 거치고 우리 API가 JWT를 알아 먹게 해보조.
링크 - https://www.django-rest-framework.org/api-guide/authentication/
drf에서는 기본적으로 2가지 authentication을 하는데요
베이직이랑! 세션!
세션의 경우는 이럴때 사용되는 예가 있습니다.
바로 browsable API를 우리가 사용할 때가 대표적이죠.
사용하기 전에 일단 settings.py에 설정 작업을 해줘야합니다.
# 생략
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
]
}
만약 "rest_framework.authentication.SessionAuthentication",
한줄을 지워버리면 어떻게 될까요?
로그인하라고 팝업창이 뜨네요.
필요한 "rest_framework.authentication.SessionAuthentication",
은 놔두고 베이직은 지워버릴게요.
# 생략
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE':10,
"DEFAULT_AUTHENTICATION_CLASSES": [
"rest_framework.authentication.SessionAuthentication",
],
}
Header안에 Authorization키를 잡아주고 값은 X-JWT 토큰값
(중간에 공백 존재!)라고 입력할게요.
인증이 유효하지 않다고 나오종? DEFAULT_AUTHENTICATION_CLASSES에 JWT에 연관된 녀석을 넣어주지 않아서 그래요.
직접 JWT 인증을 위한 클래스를 커스터마이징을 해볼게요.
config
디렉토리 안에 authentication.py 파일을 만들게요. 그리고 위에서 봤던 Example 소스 코드를 일단 그대로 복붙하고 아래와 같이 소스 코드를 변경할게요.
import jwt
from django.conf import settings
from rest_framework import authentication
from rest_framework.response import Response
from users.models import User
class JWTAuthentication(authentication.BaseAuthentication): # DRF의 Token방식과 JWT의 차이 DB저장 유무(JWT는 저장하지 않음)
def authenticate(self, request): # 서버 측면에서 더 효율적임 2만명이 로그인을 해도 디비에서 저장하는 것은 아무것도 없음
try: # 리프레쉬 토큰이 현재 구현 되지 않은 부분이 있음
token = request.META.get("HTTP_AUTHORIZATION") # 헤더에 관한 정보는 MEATA속성에 있음
if token is None:
return None
xjwt, jwt_token = token.split(" ") #관습적으로 많이 사용함
decoded = jwt.decode(jwt_token, settings.SECRET_KEY, algorithms=["HS256"])
pk = decoded.get("pk")
user = User.objects.get(pk=pk)
return (user,None) # tuple, list로 안넘기면 오류남 cannot unpack non-iterable User object
except ValueError:
return None
except jwt.exceptions.DecodeError:
raise exceptions.AuthenticationFailed(detail="JWT Format Invalid")
# return Response(status=status.HTTP_401_UNAUTHORIZED)
except User.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
그리고 나서
"config.authentication.JWTAuthentication",
이걸 넣어줄게요.
# 생략
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE':10,
"DEFAULT_AUTHENTICATION_CLASSES": [
"config.authentication.JWTAuthentication",
"rest_framework.authentication.SessionAuthentication",
],
}
아래와 같이 endpoint로 이동해서 원하는 정보를 쫘악~ 볼수 있는거조.