jam 블로그

깔끔한 파이썬 탄탄한 백엔드 -7- 본문

IT Book Study/깔끔한 파이썬 탄탄한 백엔드

깔끔한 파이썬 탄탄한 백엔드 -7-

kid1412 2020. 9. 3. 22:47
728x90

7. 인증

인증은 private api와 같은 사용자를 제한해야하는 api에서 필요하며, public api와 같은 경우 사용 횟수 제한이나 남용 방지등을 위해 쓰입니다.

인증 흐름

인증을 생성 및 사용 방법은 대략적으로 아래와 같습니다.

  1. 회원 가입을 진행할때 api에 id/pw를 api에 보냅니다.
  2. 회원 가입 api에서 id/pw를 체크하고 별 문제 없으면 DB에 저장합니다. 이때 id는 그대로 저장하며 pw는 암호화해서 저장합니다.
  3. 회원 가입이 완료 되면 로그인 합니다. 이때 들어온 id와 pw를 DB에 저장된 값과 비교합니다. (pw는 암호화한 후 암호화로 저장된 DB와 비교합니다.)
  4. 로그인이 OK가 되면 이때 access token을 만들어서 front-end에 전송합니다.
  5. Front-end에서는 이제 API를 사용할 때 access-token을 같이 보내어 사용 가능한 사용자라는 것을 확인 시켜줍니다. access-token이 만료되지만 않으면 재로그인을 하지 않고 계속 token으로 통신이 가능합니다.

비밀번호 암호화

암호화에는 두가지 방식이 있습니다.

일반적으로 비밀번호를 암호화 할때는 단방향 암호화 방식을 사용합니다. 다만 md5나 sha1 같은 경우 rainbow table과 같은 hash table로 깨지기 쉽기 때문에 깨기 어렵게 하는 hash 알고리즘을 사용합니다.

여기서 사용할 것은 bcrypt 알고리즘입니다.

  • salting : 비밀번호에 랜덤 값을 더해서 해시화 (다만 salt에 쓰는 키값은 서버나 conf 파일 같은 곳에 저장되는 경우가 많기 때문에 서버 자체의 보안에 조심해야합니다.)
  • 키 스트레칭(Key stretching) : 단방향 해시 값을 계산후 그 해시값을 또 해시하는 방법으로 여러번 반복 합니다.

참고 : https://namu.wiki/w/암호 알고리즘

JWT

access token을 만드는 방법은 다양하지만 여기서는 많이 쓰이는 JWT를 사용합니다.

  • 구조

    • header : 토큰 타입과 사용되는 해시 알고리즘을 지정합니다.

    • payload : 실제로 서버 간에 전송하고자 하는 데이터 부분. 다만 base64로 암호화하는거라 민감한 데이터는 넣지 말아야 합니다.

    • signature : BASE64URL 코드화된 header와 payload 그리고 JWT secret를 헤더에 지정된 암호 알고리즘으로 암호화하여 전송

      참고 : https://medium.com/sjk5766/jwt-json-web-token-소개-49e211c65b45

decorator 함수

우리는 http header에 access token이 있는지 특정 api에서 검사를 할 decorator를 만들어야 합니다. 다른 파일들처럼 decorator를 따로 작성합니다.

# decorator.py
import jwt
from api import *
from functools import wraps
from flask import request, Response, current_app, g

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

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

            user_id = payload['user_id']
            g.user_id = user_id
            g.user = get_user(user_id) if user_id else None
        else:
            return Response(status=401)
        return f(*args, **kwargs)
    return decorated_function
  • Http header에 Authorization에 access token이 있는지 확인합니다.
  • 없으면 401을 반환 합니다.
  • 있으면 JWT로 decode하여 user_id값을 global로 설정합니다.

Authorization이 필요한 API에 decorator를 설정 합니다.

# app.py
@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
  • tweet, follow, unfollow에는 auth가 필요하기에 decorator를 설정합니다.
Comments