사용자가 로그인해야만 쓸 수 있는 API의 경우, 사용자가 로그인을 했는지 안 했는지 사전에 체크해야 한다. 이 역할을 로그인 데코레이터가 한다. 로그인을 한 경우에는 해당 API를 실행시키고, 로그인을 하지 않았을 경우 로그인 유도를 해야 하다.
아래 케이스는 유효한 access_token이 없을 경우 로그인 페이지로 리다이렉트 하는 대신 JSON으로 {"message" : "INVALID_TOKEN"}
를 리턴해 주는 함수이다.
login_decorator
가 장식한 함수를 func
파라미터로 데코레이터 함수에 전달하고, 최종적으로 wrapper
함수를 리턴
wrapper
함수의 구성 로직request.headers
에 담겨 있는 'Authorization'의 value, 즉 access_token
을 auth_token(str타입)
에 담음
jwt.decode
에 첫번째 인자로 디코딩할 대상이 되는 auth_token
을, 두번째 인자는 'secret', 세번째로 algorithm 타입을 넣어서 dictionary 형태의 사용자 정보를 payload 변수에 담음
ex) eyJ0eXAiO...iJK -> {'user_id' : 1}
payload
의 user_id 값과 동일한 id를 가지는 User 객체를 request.user
변수에 담음
func
에 request를 인자로 전달하고 실행하도록 함
예외 처리
User.DoesNotExist
: 해당 user_id를 가지고 있는 User 객체가 없을 경우, {"message" : "INVALID_USER"}
리턴
jwt.exceptions.DecodeError
: access_token이 없거나 일치하지 않을 경우, {"message" : "INVALID_TOKEN"}
리턴
import json, jwt
from django.http import JsonResponse
from .models import User
def login_decorator(func):
def wrapper(self, request, *args, **kwargs):
try:
auth_token = request.headers.get('Authorization', None)
# print('auth_token', auth_token)
payload = jwt.decode(auth_token, 'secret', algorithm='HS256')
# print('payload', payload)
request.user = User.objects.get(id=payload['user_id'])
return func(self, request, *args, **kwargs)
except User.DoesNotExist:
return JsonResponse({"message" : "INVALID_USER"}, status=400)
except jwt.exceptions.DecodeError:
return JsonResponse({"message" : "INVALID_TOKEN"}, status=400)
return wrapper
CommentView
클래스는 코멘트를 작성하는 post
함수와 작성한 코멘트를 보여주는 get
함수로 구성되어져 있습니다. 이 함수들은 사용자가 로그인 되어 있을 때만 작동하는 함수들입니다.
따라서 이 2개 함수 앞에 로그인 데코레이터를 추가하여, 로그인이 되었을 경우에만 2개 함수가 실행되도록 세팅을 합니다.
@login_decorator
import json
from django.views import View
from django.http import JsonResponse
from user.views import login_decorator
from .models import Comment
class CommentView(View):
@login_decorator
def post(self, request):
data = json.loads(request.body)
user = User.objects.get(id=request.user.id)
# request.user로 전달된 id값과 일치하는 객체 생성
Comment(
author = user, # 객체를 담음 (엄밀하게는 user_id가 담김)
content = data['content']
).save()
return JsonResponse({'message' : 'SUCCESS'}, status=200)
@login_decorator
def get(self, request):
comment_data = Comment.objects.values()
return JsonResponse({'comments' : list(comment_data)}, status=200)
❯ http -v http://127.0.0.1:8000/feed/comment/ 'Authorization:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxMX0.8WeBhffU-wMdOhE4zdS3KJx2XfCYVpAoPNwSjaRovn4' content='어제는 흐리더니 오늘은 날씨가 참좋네'
POST /feed/comment/ HTTP/1.1
Accept: application/json, */*;q=0.5
Accept-Encoding: gzip, deflate
Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxMX0.8WeBhffU-wMdOhE4zdS3KJx2XfCYVpAoPNwSjaRovn4
Connection: keep-alive
Content-Length: 115
Content-Type: application/json
Host: 127.0.0.1:8000
User-Agent: HTTPie/2.1.0
{
"content": "어제는 흐리더니 오늘은 날씨가 참좋네"
}
HTTP/1.1 200 OK
Connection: close
Content-Type: application/json
Date: Wed, 20 May 2020 07:56:20 GMT
Server: WSGIServer/0.2 CPython/3.7.7
Vary: Origin
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
{
"message": "SUCCESS"
}
http -v http://127.0.0.1:8000/feed/comment/ 'Authorization:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxMX0.8WeBhffU-wMdOhE4zdS3KJx2XfCYVpAoPNwSjaRovn4'
GET /feed/comment/ HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxMX0.8WeBhffU-wMdOhE4zdS3KJx2XfCYVpAoPNwSjaRovn4
Connection: keep-alive
Host: 127.0.0.1:8000
User-Agent: HTTPie/2.1.0
HTTP/1.1 200 OK
Connection: close
Content-Type: application/json
Date: Wed, 20 May 2020 08:20:46 GMT
Server: WSGIServer/0.2 CPython/3.7.7
Vary: Origin
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
{
"comments": [
{
"author_id": 1,
"content": "‘코멘트를’",
"created_at": "2020-05-11T11:24:51.059Z",
"id": 1,
"updated_at": "2020-05-12T07:40:45.690Z"
},
{
"author_id": 1,
"content": "코멘트를남겨요.",
"created_at": "2020-05-11T11:25:09.152Z",
"id": 2,
"updated_at": "2020-05-12T07:40:34.498Z"
},
... 이하 생략