Flask - 인증절차를 다른 엔드포인트에 적용하기 with decorator

황인용·2020년 2월 1일
1

Flask

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

앞서 인증인가 절차로 회원가입&로그인 엔드포인트 구현하기 with bcrypt, JWT에서 인증 엔드포인트를 통해 access_token을 응답 받았다.

Minitter 많은 API중 인증절차가 필요한 부분들이 있다.
바로 tweet과 follow, unfollow API들이다.
기존에는 어떤 아이디가 tweet를 올리는지, follow 하는지를 id값을 지정하여 전송하였다.
하지만 먼저 어떤 아이디인지를 확인하는 절차를 사전에 거친다면 id값을 지정할 필요없이 바로 tweet이나 follow, unfollow 절차를 밟을 수 있을 것이다. 또한 이 절차는 자주 사용되며 재사용성이 크다.
따라서 이 부분은 파이썬의 decorator를 활용한다.
decorator를 사용 시 우리가 앞에서 로그인하고 응답받은 access_token을 활용하여 인증절차가 필요한 엔드포인트에서 사용하고자 한다.

  • tweet, follow, unfollow 등 사전에 사용자의 인증절차가 필요하다. 그리고 이 절차는 자주 사용되고 먼저 절차를 밟아야 다음 절차를 밟을 수 있다.
  • 이 부분을 파이썬의 decorator를 사용하여 구현한다.
  • decorator에서는 로그인에서 응답받은 access_token를 활용하여 구현한다.

Decorator

  • Decorator는 어떠한 함수를 다른 함수가 실행되기 전에 자동으로 먼저 실행될 수 있도록 해주는 문법
  • 이름 그대로 함수를 다른 함수에 장식처럼 첨부하는 것
  • 주로 공통적으로 항상 먼저 실행되어야 하는 코드가 있을 떄 사용한다.
  • decorator사용하는 함수는 함수의 이름앞에 @를 붙여서 적용하고자 하는 함수 정의부분 웨에 지정한다.
  • 자세한 내용은 Python - Nested Function and Decorator
  • 파이썬에서는 functools 모듈의 wraps decorator함수를 활용할 수 있다.

decorator 함수 예제

from functools import wraps						# 1)

def test_decorator():							# 2)
  @wraps(f)										# 3)
  def decoratored_function(*args, **kwargs):	# 4)
    print("Decorator Function")					# 5)
    return f(*args, **kwargs)					# 6)
  
  return decoratored_function					# 6)

@test_decorator									# 7)
def func():										# 8)
  print("Calling func function!")

1) functools 모듈의 wraps decorator 함수를 import한다.

2) decorator 함수를 정의한다.
decorator함수는 함수를 인자로 받아서 함수를 리턴하는 함수이다. 여기서 f라고 이름 지어진 함수를 인자로 받는다. 그리고 이 f함수는 해당 decorator함수가 적용되는 함수가 된다.

3) wraps decorator 함수를 적용한다. 꼭 적용해야할 함수는 아니지만, 적용하면 부차적으로 생기는 이슈들을 해결해 준다. 자세한 내용은 파이썬 공식문서 참고...

4) 실질적인 decorator 함수이다. decorator 함수를 리턴해 줘야 함으로 nested 함수로 지정해준다.
함수의 인자가 *args, `
kwargs*args는 tuple형태의 인자를, kwargs`는 dictionary형태의 인자를 받을 수 있다.

5) f함수를 실행시켜 리턴해준다.
즉, 해당 decorator함수가 실행되고 난 후에 decorator가 적용된 함수를 호출해 주는 것이다.

6) decorator 함수를 리턴한다.

7) 2)에서 만든 decorator함수를 8)의 함수에 적용해 준다.

8) 2)에서 만든 decorator함수가 적용되는 함수다.

decorator 함수 예제 테스트

위 와 같이 작성된 파일을 파이썬 shell에서 불러오면 다음과 같은 결과를 확인 할 수 있다.

>>> func()
Decorator Function
Calling func function

인증 decorator 함수

이제 인증 절차를 위한 decorator함수를 만들어보자.

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

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

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

import jwt

from functools  import wraps
from flask 		import request, Response, g 

def login_required(f):      									# 1)
    @wraps(f)                   								# 2)
    def decorated_function(*args, **kwargs):					
        access_token = request.headers.get('Authorization') 	# 3)
        if access_token is not None:  							# 4)
            try:
                payload = jwt.decode(access_token, current_app.config['JWT_SECRET_KEY'], 'HS256') 				   # 5)
            except jwt.InvalidTokenError:
                 payload = None     							# 6)

            if payload is None: return Response(status=401)  	# 7)

            user_id   = payload['user_id']  					# 8)
            g.user_id = user_id
            g.user    = get_user(user_id) if user_id else None
        else:
            return Response(status = 401)  						# 9)

        return f(*args, **kwargs)
    return decorated_function

1) login_required decorator 함수를 지정한다. 이제 login_required decorator 함수가 적용된 함수는 해당 사용자가 로그인을 이미 한 상태에서만 실행될 수 있다.

2) functools 라이브러리의 wraps decorator 함수를 적용해서 decorator 함수를 구현한다.

3) 전송된 HTTP 요청에서 Authorization헤더 값을 읽어 들여 access_token을 얻는다.

4) authorization 헤더가 전송되었다면 access_token을 복호화하여 payload JSON을 읽어 들이도록 한다.
만일 access_token이 None 이라면 authorization 헤더가 전송되지 않았다는 뜻으로 인증허가를 하지 않아도 된다.

5) access_token을 복호화 하여 payload JSON을 읽어 들인다. jwt 모듈의 decode 함수는 JWT access_token을 복호화할 뿐만 아니라 토큰이 해당 백엔드 API서버에서 생선된 token인지를 확인하는 절차를 밟는다.
그래서 'JWT_SECRET_KEY'에서 필드 값을 읽어 들여 secret key를 읽고 'HS256'으로 동일하게 복호화해서 결과가 같은지를 확인한다.

6) 만약 5)에서 exception이 일어나면, JWT 복호화에 문제가 있는 것으로 Unauthorized 401 응답을 보낸다.

7) payloadNone이라면 이 또한 문제가 있다는 뜻으로 Unauthorized 401 응답을 보낸다.

8) JWT에서 복호화한 payload JSON에서 user_id를 읽어 들인다.
그리고 해당 사용자 id를 사용해서 데이터베이스에서 사용자 정보도 읽어 들이도록 한다.
get_user_info 함수는 사용자 id를 바탕으로 데이터베이스에서 사용자 정보를 읽어 들이는 함수이다.

인증 decorator 적용하기

인증 decorator login_required 함수를 tweet, follow, unfollow 엔드포인트에 적용한다.

    @app.route('/tweet', methods=['POST'])
    @login_required
    def tweet():
        user_tweet       = request.json
        user_tweet['id'] = g.user_id
        tweet            = user_tweet['tweet']

        if len(tweet) > 300:
            return '300자를 초과했습니다', 400

        insert_tweet(user_tweet)

        return '', 200

    @app.route('/follow', methods=['POST'])
    @login_required
    def follow():
        payload       = request.json
        payload['id'] = g.user_id

        insert_follow(payload) 

        return '', 200

    @app.route('/unfollow', methods=['POST'])
    @login_required
    def unfollow():
        payload       = request.json
        payload['id'] = g.user_id

        insert_unfollow(payload)

        return '', 200
...
...
  • route decorator를 먼저 적용해야한다.
  • 엔드포인트로 method 호출이 이루어져야만 다음 절차를 실행할 수 있다.
  • 따라서 각 엔드포인트에는 routelogin_required decorator를 순서에 맞게 적용한다.

인증 decorator 적용 테스트

tweet with access_token

$ http -v POST localhost:5000/tweet tweet="My first Decorator Login" "Authorization:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxMSwiZXhwIjoxNTgwNjQ4ODE0fQ.tP7kVVyObey0_yexaCjmhwZ_y7xc_HOL1MtwnusuKRE"
POST /tweet HTTP/1.1
Accept: application/json, */*
Accept-Encoding: gzip, deflate
Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxMSwiZXhwIjoxNTgwNjQ4ODE0fQ.tP7kVVyObey0_yexaCjmhwZ_y7xc_HOL1MtwnusuKRE
Connection: keep-alive
Content-Length: 37
Content-Type: application/json
Host: localhost:5000
User-Agent: HTTPie/0.9.8

{
    "tweet": "My first Decorator Login"
}

HTTP/1.0 200 OK
Content-Length: 0
Content-Type: text/html; charset=utf-8
Date: Sat, 01 Feb 2020 13:08:12 GMT
Server: Werkzeug/0.16.1 Python/3.7.6

정리

  • HTTP 통신은 stateless이다. 따라서 이 전 통신은 어떠한 절차를 밟는 통신인지 알수없다. 그러므로 HTTP 통신에 인증 여부 정보를 보내야되는데, 이때 사용하는 방법이 access_token이다.

  • access_token은 JWT(JSON Web Token)을 사용해서 생성될 수 있다.

  • JWT 생성할 때 보안상 조심해야할 부분은 사용자의 개인정보는 포함시키지 말아야 한다.

  • 인증 절차를 엔드포인트들에 적용시키기 위해 decorator 함수를 사용할 수 있다.

profile
dev_pang의 pang.log
post-custom-banner

0개의 댓글