[Re:Django] 5. Decorator 구현 및 엔드포인트에 적용하기

Magit·2020년 5월 4일
1

Django

목록 보기
5/13
post-custom-banner

Decorator 란?

Decorator란 어떤 함수를 다른 함수가 실행되기 전에 자동으로 먼저 실행될 수 있도록 설정해주는 문법이다. 이름 그대로 함수를 다른 함수에 장식처럼 올려놓는다. 공통적으로 먼저 실행되어야하는 코드가 있을 때 사용한다. Decorator 로 사용하는 함수는 함수의 이름앞에 @ 를 붙인 다음, 적용하고자 하는 함수 정의부분 위에 지정한다.

decorator 개념에 대한 참조 사이트
DoonDoon님 블로그
SCHOOL OF WEB
코딩 도장

인증 Decorator 구현하기

보통 Decorator는 인증 절차가 필요할 때 많이 쓰인다. 예를 들어, 로그인이 되있다는 전제하에 사용가능한 기능들이 있다면 우선 로그인 여부를 확인해야된다.

로그인에 성공하면 JWT 토큰을 발행하게되고, 프론트엔드는 로컬 스토리지에 토큰을 저장할 것이다. 그리고 특정 기능들을 사용하려고 할 때마다 토큰을 HTTP request의 Authorization 헤더 부분에 포함해서 보낼것이고, 백엔드는 토큰을 복호화해서 사용자의 id를 읽고, 로그인 여부를 확인하게 된다.

그럼 이런 인증 절차를 Decorator 함수에 어떤식으로 작성하면 좋을지 정리해보자.

  1. 인증 엔드포인트를 통해 생성된 JWT access_token을 프론트엔드는 HTTP요청에서 Authorization헤더에 포함해서 보내게 된다.

  2. decorator함수는 Authorization 헤더에서 값을 읽어서 JWT access_token 이 있는지 확인한다.

  3. access_token 이 있으면 그 값을 복호화하여서 사용자의 id를 읽어들이고 해당 사용자의 로그인 여부를 확인한다.

예를 들어 Profile 의 endpoint 로 로그인 한 유저가 접근하는 페이지가 있다고 생각해보자.

class Profile(View):
	@login_decorator
    def get(self, request):	[1]
    	profile = User.objects.filter(id=request.user.id).values()
        return JsonResponse({'profile' : profile[0]}, status = 200)

[1] : get 메소드가 실행되기 전에 login_decorator 라는 데코레이터가 먼저 실행된다.


그럼 데코레이터를 만들어보자.

import jwt, json

from django.http import JsonResponse
from .models import User

from project_garam.settings import SECRET_KEY, ALGORITHM	[2]

[1] : 토큰을 디코팅할 때 사용할 시크릿키와 알고리즘. 보안을 위해서 따로 빼뒀다.


def login_decorator(func):	[1]
    def wrapper(self, request, *args, **kwargs):	[2]

[1] : login_decorator 라는 데코레이터를 만들고 데코레이터가 적용된 메소드를 func로 불러온다. (여기서는 accounts.views에 있는 Profile)
[2] : parameter을 받아올 wrapper 함수를 만들어준다. 데코레이터도 클래스 밑에서 생성되는 함수이기에 self, http request를 받는 request, 쿼리스트링이나 url parameter을 받아오기 위한 *arg, *kwargs 를 넣어준다.(args는 tuple형태의 parameter를, **kwargs는 dictionary 형태의 parameter를 받을 수 있다.)


        if 'Authorization' not in request.headers:
            return JsonResponse({"message": "INVALID_CLIENT_TOKEN"}, status=401)	[1]

[1] : token 은 HTTP request의 헤더인 Authorization 의 value 값으로 담겨서 온다. 그렇기에 만약 request의 헤더에 Authorization 이 없다면 토큰 자체가 담겨있지 않다는 의미이므로, 에러코드 401을 반환한다.


        token = request.headers["Authorization"]	[1]

        try:
            data = jwt.decode(token, SECRET_KEY, ALGORITHM)	[2]
            user = User.objects.get(id=data['id'])	[3]
            request.user = user	[4]

        except jwt.DecodeError:
            return JsonResponse({"message": "INVALID_TOKEN"}, status=401)	[5]
        except User.DoesNotExist:
            return JsonResponse({"message": "UNKNOWN_USER"}, status=401)	[6]

        return func(self, request, *args, **kwargs)	[7]
    return wrapper	[8]

[1] : token 은 HTTP request의 헤더인 Authorization의 value 값으로 들어올테니, 딕셔너리 문법에 따라 value 값을 token 이라는 변수에 저장한다.
[2] : datapayload 즉, 토큰을 디코딩해서 나오게 될 사용자에 대한 정보를저장할 변수이다. 토큰 발행 시와 동일한 사용자라면 동일한 payload가 반환된다. 디코딩시 SECRET_KEYALGORITHM은 토큰 발행 시 넣었던 정보와 같아야한다.
[3] : DB의 USER 테이블에서 토큰을 디코딩해서 얻은 사용자 정보 와 매칭되는 사용자 정보를 user 라는 변수에 저장한다.
[4] : request객체에 user 객체를 넣어준다. 여기서 request객체는 headers, body, startline등으로 이루어져 있다. 그리고 객체의 특성상 객체를 추가하는 것이 가능하다는 것을 명심하자. 이렇게 request객체에 추가된 user객체는 그대로 데코레이터를 빠져나오는 순간 사라지지않고 views.py에 있는 Profile클래스의 get매서드에 전해진다.
[5] : 없는 토큰 값이 들어왔을 경우 DecodeError를 처리한다.
[6] : User 테이블에 매칭되는 값이 없을 경우 DoesNotExist를 처리한다.
[7]~[8] : 조건이 모두 맞아 떨어지면 시작될 때 받은 parameter들을 전부 다 리턴해주고 마지막으로 wrapper 함수를 리턴한다. 리턴값에 포함된 request는 user 객체를 가진채로 Profile로 넘겨진다.



엔드포인트에 적용하기

위에서 예시로 들었던 코드로 연습을 해보자.

# 데코레이터 이후 코드
def get(self, request):	[1]
    	profile = User.objects.filter(id=request.user.id).values()	[2]
        return JsonResponse({'profile' : profile[0]}, status = 200)	[3]

[1] : 데코레이터를 무사히 통과되었다면 user 객체를 갖고 있는 request가 get 메소드로 넘어올것이다.
[2] : request의 user 객체의 id 값을 필터링 해서 User 테이블에서 갖고온다. 그리고 .values() 로 key, value 쌍 딕셔너리를 담은 리스트 queryset을 갖고와서 profile 변수에 담는다.
[3] : profile에 담긴 첫번째 딕셔너리 데이터를 Json으로 리턴한다.

댓글에 적용해보기

댓글은 사용자 아이디와 댓글 내용이 담기는 기본적인 기능으로 구성되있다. 기존에 작성된 코드들에 GETPOST를 실행하기 전 로그인 토큰을 확인하도록 데코레이터를 추가해보자.

from .utils import login_decorator                       [1]


class CommentView(View):
    @login_decorator                                         [2]
    def get(self, request):
        return JsonResponse({'comment_list':list( Comment.objects.values())},status=200)					   [3]

    @login_decorator                                         [4]
    def post(self, request):
        data = json.loads(request.body)

        Comment(
            user_id = request.user.id,                      [5]
            comment = data['comment'],
        ).save()											[6]

        return HttpResponse(status=200)

[1] : 데코레이터를 만들어진 곳에서 import 해와야 사용할 수 있다.
[2] : get 메소드가 실행되기 전에 로그인 인증 여부를 확인한다.
[3] : 쿼리로 갖고오게되는 값들은 쿼리셋으로 담기므로 json return이 불가능하기에, list 처리해준다. 그리고 해당하는 id를 갖고있는 사용자의 comment를 json 으로 리턴한다.
[4] : post 메소드가 실행되기 전에 로그인 인증 여부를 확인한다. 토큰이 통과된 상태여서 user 객체가 request에 추가되어 넘어온 상황이다.
[5]~[6] : 토큰을 디버깅해서 얻은 유저 아이디 정보를 활용한다. 유저가 입력한 comment와 request.user 객체에서 id 값을 뽑아 user_id 에 저장한다.(Comment 테이블이 User 테이블을 정참조하므로 id로 표현됨)

참고 블로그
TIL - Logging Decorator
Django - 데코레이터로 로그인 토큰 확인하기
Flask - 인증절차를 다른 엔드포인트에 적용하기 with decorator

profile
이제 막 배우기 시작한 개발자입니다.
post-custom-banner

0개의 댓글