📜 제목으로 보기

데코레이터를 작성하는 순서

기본 데코레이터 만들기

01 기능을 입힐 base함수부터 작성한다. 이왕이면 print가 아니라 return하는 결과물이 있는 함수를 작성하여, 반환값에 대한 장식도 가능하게 한다.

a55c4f3f-b77a-47ec-b9cf-b148bb0dd562

image-20220720175248054

02 base함수를 (호출부없는)함수객체를 인자로 받는 클로져 함수를 사용-생성한다. 여기까지는 지역상태(메서드내부 인자 등)를 기억하는 함수객체를 return한 뒤, 외부에서 실제 인자를 받는 클로져가 된다.

  • 데코레이터는 장식할 함수를 일급객체로서 간주하여, ()호출부 없이 함수객체만 인자로 받으며 -> 장식 후 -> 다시 함수객체로 반환하는 클로져 형태를 띈다.
  • 대문자로 변경하는 장식을 할 것이므로, 데코레이터 메서드 명을 uppercase로 만든다.

64d3b405-bc6b-4779-bdb8-f22808ccec5b

image-20220720175542359

image-20220720175553030

03 클로져 상태에서, base함수를 호출한 결과값을 장식해야한다면? 반환은 값이 되므로 함수객체를 반환하지 않게 되어, 클로져도 아니고 데코레이터도 아니게 된다. 그냥 값이 반환된 상태라서 외부에서 ()호출없이 끝난다.

47058064-f7f1-402e-8240-f1abb50a0be6

image-20220720180253854

image-20220720181046173

image-20220720181054087

04 base결과물 장식 후, 클로져 상태(함수객체 반환)를 유지하기 위한 inner메서드 wrapper로 감싼 뒤, return wrapper함수객체 -> 외부에서 호출될 함수로서, 함부러 파라미터를 내부context라고 파라미터로 만들면 안된다. -> base함수의 인자에 맞춰야한다.

  • wrapper는 inner메서드이므로, context객체들을 그대로 사용하여 paramter로 뺄 필요가 없다.(외부호출시 사용될 메서드 객체기 때문에 파라미터를 함부러줘선 안된다.)

    ff64c879-a8d9-4342-9d7d-cd912b567b2c

    image-20220720183920956

    image-20220720184020793

05 완성된 데코는, base함수에 @달아서 사용해주기

ef532c24-00c5-499b-a7ab-478ffa120295

image-20220720184425813

image-20220720184434604

06 다중데코레이터는, 외부에서 한번더 감싸서 만들고, 달 때는 아래->위 순서대로 단다.

image-20220720185131350

image-20220720185150566

image-20220720185256023

image-20220720185237058

함수객체 name에 base함수가 찍히도록 디버깅되는 데코레이터로 바꾸기 by wrapper메서드에 @functools.wraps(func) 데코 달아주기

  • 데코레이터를 입힌 base함수를, 함수객체.__name__을 찍어보면, 중간의 wrapper로 나온다. image-20220720185516643
  • 데코레이터의 wrapper함수에 @functools.wraps(func) 데코레이터를 달아줘야, base함수가 찍힌다. image-20220720185603276

  • 만약, 2개의 데코레이터를 중첩적용하고 있고, 첫번째 데코만 처리해줬다면? -> wrapper로 나온다.

    image-20220720185658874

    image-20220720185732640

    image-20220720185741410

    • 2번째 데코레이터도 달아줘야한다.

      image-20220720185802853

      image-20220720185809409

base함수호출시 필요한 특정 인자를 외부에서 조달하려면, 외부에 return되서 ()가 붙는 함수객체 wrapper에 *args, **kwargs로 파라미터를 정의하여 어떤 인자든 들어오게 한다.

  • 데코레이터 함수 자체에 인자가 아니다(func만 받는다.)

  • 마지막엔 return wrapper로 wrapper의 함수객체가 반환되므로 wrapper에 *args\**kw args를 받도록 하면, 어떤 인자든 다 들어올 수 있다

    • 들어온 인자를 그대로 사용하려면 *args를 base에서 그대로 사용한다.
    • 만약 콤마로 연결된 인자들이 들어왔는데, 반복문 등에 사용된다면 *를 빼서 패킹된 상태로 돌린다.
  1. 현재 base함수를 파라미터를 통해 문자열 인자 받도록 변경한다.

    • 그래도 바로 사용못한다. 왜냐면, 데코레이터에 감싸진 이상 wrapper가 반환되기 때문에
    • wrapper는 인자를 안받는데, 왜 받고 있냐 물어본다.

    a76ed85b-936a-4c51-a3ae-7f50490548ad

  2. 외부에서 호출되는 wrapper함수에 base함수호출시 필요한 인자를 주되 *args, *\*kwargs로 주어 어떤 인자든 받을 수 있게 한다.

    • args : 튜플로 패킹된다.

    • *args: 받은 인자 그대로를 언패킹해서 사용한다.

      • 파라미터속 *args : 콤마 = 튜플로 넘어온 인자를 언패킹한 상태로 가지고 있다. 언제든지 *를 떼서 컬렉션형태로 사용할 수 있다.

      58811d23-66eb-496d-8ceb-45ed76222443

      image-20220720193114348

      image-20220720193139906

역순으로 jwt 인증 decorator 작성

01 Route마다 매번 [token 인증 -> 실패시 예외]의 검증하는 로직을 route에 반복 기술하는 대신 데코레이터로 만들기

  • 참고 내 블로그

    기본 데코레이터 만들기
    01 기능을 입힐 base함수부터 작성한다. 이왕이면 print가 아니라 return하는 결과물이 있는 함수를 작성하여, 반환값에 대한 장식도 가능하게 한다.
    02 base함수를 (호출부없는)함수객체를 인자로 받는 클로져 함수를 사용-생성한다. 여기까지는 지역상태(메서드내부 인자 등)를 기억하는 함수객체를 return한 뒤, 외부에서 실제 인자를 받는 클로져가 된다.
    03 클로져 상태에서, base함수를 호출한 결과값을 장식해야한다면? 반환은 값이 되므로 함수객체를 반환하지 않게 되어, 클로져도 아니고 데코레이터도 아니게 된다. 그냥 값이 반환된 상태라서 외부에서 ()호출없이 끝난다.
    04 base결과물 장식 후, 클로져 상태(함수객체 반환)를 유지하기 위한 inner메서드 wrapper로 감싼 뒤, return wrapper함수객체 -> 외부에서 호출될 함수로서, 함부러 파라미터를 내부context라고 파라미터로 만들면 안된다. -> base함수의 인자에 맞춰야한다.
    05 완성된 데코는, base함수에 @달아서 사용해주기
    06 다중데코레이터는, 외부에서 한번더 감싸서 만들고, 달 때는 아래->위 순서대로 단다.
    함수객체 name에 base함수가 찍히도록 디버깅되는 데코레이터로 바꾸기 by wrapper메서드에 @functools.wraps(func) 데코 달아주기
    base함수호출시 필요한 특정 인자를 외부에서 조달하려면, 외부에 return되서 ()가 붙는 함수객체 wrapper에 *args, **kwargs로 파라미터를 정의하여 어떤 인자든 들어오게 한다.
    

001 src > auth_jwt > token_verifier.py 생성

image-20221010211709522

02 token인증(실패시 예외)을 데코레이터 만드는 방법(바깥에서부터)

  • route의 메서드가, 데코레이터의 decorator -> warpper -> base_func 중 basefunc을 차지하게 됨.

001 decorator는 ()호출 전의 base_func객체를 인자로 받아서 내부에서 호출하고, return은 func객체를 호출하고 실제 deco하는 inner method wrapper를 호출하지 않은체 반환하므로 [input:func(base_func) -> output func(wrapper)]이다.

  • 참고

def token_verify(function: callable) -> callable:

002 inner method wrapper메서드는 [결과값을 deco당하거나 or 호출전후로 작업을 하고 싶은 base_func()]의 decorating 과정에서 -> 외부 인자가요구하는 순간, wrapper method도 인자를 *args, **kwargs로 받는다. inner method인 wrapper는 decorator 맨 마지막에 호출하지 않은 체 반환되는 closure다

  • 참고

def token_verify(function: callable) -> callable:

    def decorated(*args, **kwargs):
        ## pre decoration

        # result = function()

        ## post decoration
        # post_decorated_result = result + @
        # return decorated_result
        
    return decorated

003 GET route에서 token인증을 위한 decode + 예외처리 했던 로직을 inner method warpper(여기선 decorated) 내부로 가져와서, [base_func (여기서는 route함수) 호출전 (1)TOKEN인증(==실패시 예외) + (2) TOKEN REFRESH 작업]을 항상 진행하게 해준다.

  • get route

    image-20221010214759256

  • 이동

    image-20221010214905612

004 필요한 것들을 import하고, 수정해나간다

  • auth_jwt > token_verifier.py에
    • flaks관련 모듈이 import된다
    • jwt가 import된다
    • 싱글톤 token_creator도 import된다
      • 인증 및 예외발생 route에는 없던 refresh를 해주기 위해
import jwt
from flask import request, jsonify


def token_verify(function: callable) -> callable:

    def decorated(*args, **kwargs):
        raw_token = request.headers.get("Authorization")
        uid = request.headers.get("uid")

        # if not raw_token:
        if not raw_token or not uid:
            return jsonify({
                "error": "Bad Request"
            }), 400

        try:
            token = raw_token.split()[1]
            token_information = jwt.decode(token, key='1234', algorithms='HS256')
            token_uid = token_information["uid"]

        except jwt.InvalidSignatureError:
            return jsonify({
                "error": "Invalid Token"
            }), 498

        except jwt.ExpiredSignatureError:
            return jsonify({
                "error": "Token expried"
            }), 401
            
        except KeyError as e:
            return jsonify({
                "error": "Invalid Token2"
            }), 401

        if int(token_uid) != int(uid):
            return jsonify({
                "error": "User not permission"
            }), 400

    return decorated

005 base_func인 function(route함수)는 wrapper(decorated)에서 [token인증 및 예외처리] 끝내고, 자신만 호출되면 되나? 인증안되면 예외발생하고, 인증되면 route 자신의 역할만 하면 끝??

  • 시나리오

    • route함수가, 인증 및 예외처리만 할 경우 -> deco내wrapper내 token처리 끝나고 base_func()호출시 특별한 인자 없이 base_func(\*args, \**kwargs)로 return하고 끝낸다

      image-20221010220636213

      image-20221010220852840

    • route함수가, 인증 및 예외처리하고 REFRESH한 token이 새로 발급되어서, 이것을 인자로 받아갈 때

      image-20221010220940515

      image-20221010221134178

006 route함수가 decorator로 token인증을 거치는 경우, 인증성공시의 refresh된 token을 route함수 인자로 받아야하며, decorator작성시, base_func(route_func)은 *args**kargs 앞에 가장 첫 인자로 next_token을 추가해야한다

image-20221011014624097

import jwt
from flask import request, jsonify
from .token_handler import token_creator


def token_verify(function: callable) -> callable:

    def decorated(*args, **kwargs):
        raw_token = request.headers.get("Authorization")
        uid = request.headers.get("uid")

        # if not raw_token:
        if not raw_token or not uid:
            return jsonify({
                "error": "Bad Request"
            }), 400

        try:
            token = raw_token.split()[1]
            token_information = jwt.decode(token, key='1234', algorithms='HS256')
            token_uid = token_information["uid"]

        except jwt.InvalidSignatureError:
            return jsonify({
                "error": "Invalid Token"
            }), 498

        except jwt.ExpiredSignatureError:
            return jsonify({
                "error": "Token expried"
            }), 401

        except KeyError as e:
            return jsonify({
                "error": "Invalid Token2"
            }), 401

        if int(token_uid) != int(uid):
            return jsonify({
                "error": "User not permission"
            }), 400

        next_token = token_creator.refresh(token)
        # route function
        # return function(*args, **kwargs)
        return function(next_token, *args, **kwargs)

    return decorated

007 inner method wrapper메서드에 @functools.wraps( base_func )을 달아주면, 디버깅시 함수name이 같이 찍힌다

from functools import wraps
#...
def token_verify(function: callable) -> callable:
    
    @wraps(function)
    def decorated(*args, **kwargs):

008 auth_jwt init에 올리기

from .token_handler import token_creator
from .token_verifier import token_verify

03 token 인증필요 route(GET)에서 jwt 인증 decorator사용하기

001 jwt 인증 데코레이터는 base_func이 인자로 next_token을 받는 것을 가정했으니 -> route함수는 token을 인자로 받아야한다 -> return에도 받은 next_token을 반환해보자.

  • api_route.py
#...
from src.auth_jwt import token_creator, token_verifier
#...

@api_routes_bp.route("/secret", methods=["GET"])
@token_verifier
def secret_route(token):

    return jsonify({
        'data': token
    }), 200

002 POSTMAN에서 토큰생성 (POST) -> 토큰 인증(GET) 해보기

token 생성은 생성요청자 uid를 넣어줘야하는데, 여기선 하드코딩으로 넣어준다

image-20221011015852728

image-20221011015644925

token 인증요청자는 token생성한uid와 동일해야하는데, (10Headers에 하드코딩으로 POST에 썼던 uid를 하드코딩해서 넣어주고 요청한다. 또한, (2) Authrization탭에 POST시 발급받은 token을 똑같이 직접 넣어줘야한다.

image-20221011015731021

image-20221011015706030

003 인증요청자가 발급자와 다른uid를 가지고 있다면? @token_verify가 인증실패 -> 예외처리를 잘해준다. (400, Bad Request인데, -> 400, User not permission)

image-20221011020126251

image-20221011020306881

image-20221011021008053

uid를 입력안해도 (or 토큰이 없어도)예외처리를 잘해준다.(400, Bad Request인데 -> 400에 No Authorized)

image-20221011020237204

image-20221011020246890

image-20221011020826150

token 맨끝에 1~2글자 지워도 잘 에러 내준다.(498, Invalid Token, -> 401 Invalid Token)

image-20221011020415029

image-20221011020629922

image-20221011020939299