Flask로 REST API 구현하기 - 3. JWT로 사용자 인증하기

oneofakindscene·2021년 8월 16일
0

flask

목록 보기
4/4

패키지 설치

pip install bcrypt
pip install PyJWT

비크립트(bcrypt) 사용법

  • 비크립트란? : bcypt는 블로피시 암호에 기반을 둔 암호 해시 함수
  • bcrypt의 사용법은 두 가지로 나뉩니다. 암호화암호 일치 확인

암호화 방법

(인코딩 과정)

  • bcrypt.hashpw(para1, para2)를 이용 하여, 인코딩을 실시
    • para1 : 첫번째 파라미터로는 bytes-string이 필요 str 객체내의 encode() 메소드를 이용하여, UTF-8 방식으로 인코딩을 해준 값을 넣어 wnsek.
    • para2 : bcrypt.gensalt()를 삽입하여,
      • gensalt() : 소금생성기 => gensalt() 메서드로 소금을 생성하면, b'바이트값드으으을' 가 리턴된다. 리턴된 값은 bytes 형으로 출력
  • 정리하자면, str 객체 => bytes로 인코드 => salt를 이용하여 암호화 => (최종) bytes-string 객체를 얻게됨

(디코딩 과정)

  • 인코딩 과정을 통해서 얻게된 bytes-string 객체를 => UTF-8 방식으로 디코딩하여 => 다시 str 객체를 얻게됨

예시코드

import bcrypt
password = "password"
encrypted_password = bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt())  # str 객체, bytes로 인코드, salt를 이용하여 암호화

print(encrypted_password)  # bytes-string
print(encrypted_password.decode("utf-8"))  # str 객체 

# 출력
b'$2b$12$6XEOimJ6msiHK7w/r7ayoO5W14cOVPLl8BPvmjhPJTWuo5RGRR.W6'
$2b$12$6XEOimJ6msiHK7w/r7ayoO5W14cOVPLl8BPvmjhPJTWuo5RGRR.W6

암호 일치 확인 방법

  • bcrypt.checkpw(para1, para2) 함수를 사용하여 암호 일치여부 확인함
    • para1 : "문자열".encode("utf-8") <- utf-8로 인코드한 값
    • para2 : bcrypt.hashpw()로 나온 bytes-string 객체를 넣어준다
import bcrypt
encrypted_password = bcrypt.hashpw("password".encode("utf-8"), bcrypt.gensalt())
print(bcrypt.checkpw("password".encode("utf-8"), encrypted_password))
print(bcrypt.checkpw("pessword".encode("utf-8"), encrypted_password))

# 출력
True
False

PyJWT

  • JWT는 Jason Web Token을 뜻함
  • PyJWT는 Python으로 JWT를 생성하는 데에 도움을 주는 모듈입니다. 이의 사용법은 암호화(인코딩)와, 복호화(디코딩)로 나뉩니다.

(암호화)

  • jwt.encode()로 우선 jwt 인코딩을 실시합니다.
    • 첫 번째 파라미터로는 인코딩 하고자 하는 dict 객체
      • 아래 예제 코드에서 변수 json에 들어있는 dict값
    • 두 번째 파라미터로는 시크릿 키
      • 아래 예제 코드에서 secret
    • 세 번째 파라미터로는 알고리즘 방식을 삽입 합니다.
      • 아래 예제 코드에서 algorithm="HS256"

(복호화)

  • jwt.decode()는 jwt.encode()로 인코딩한 JWT의 디코딩을 실시합니다.
    • 첫 번째 파라미터로는 디코딩 하고자 하는 bytes-string 객체
      • 아래 예제 코드에서 변수 encoded에 들어있는 bytes-string값
    • 두 번째 파라미터시크릿 키(단, 이는 jwt.encode() 에 넣은 시크릿 코드와 일치 하여야 합니다)
      • 아래 예제 코드에서 secret
    • 세 번째 파라미터로알고리즘 방식을 삽입 합니다.
      • 아래 예제 코드에서 algorithm="HS256"
  • (참고) 알고리즘에서 기본값은 256이지만, 512로 하면 토큰이 더 길어진다.
import jwt

json = {
    "id": "justkode",
    "password": "password"
}
encoded = jwt.encode(json, "secret", algorithm="HS256")  # byte-string
decoded = jwt.decode(encoded, "secret", algorithm="HS256")  # dict

print(encoded)
print(decoded)

# 출력
b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6Imp1c3Rrb2RlIiwicGFzc3dvcmQiOiJwYXNzd29yZCJ9.TKGlCElSgGthalfeTlbN_giphG9AC5y5HwCbz93N0cs'
{'id': 'justkode', 'password': 'password'}

전체 실습 코드

  • 실습에서 bcrypt는 password를 암호화하는데 쓰임
  • 실습에서 pyjwt는 name을 암호화하는데 쓰임

    app.py

from flask import Flask
from flask_restx import Resource, Api
from auth import Auth

app = Flask(__name__)
api = Api(
    app,
    version='0.1',
    title="Scene's API Server",
    description="Scene's Todo API Server!",
    terms_url="/",
    contact="oneofakindscene@gmail.com",
    license="MIT"
)

api.add_namespace(Auth, '/auth') # namespace에서 url 들어갈 부분 머로 들어가는지 확인해야함

if __name__ == "__main__":
    app.run(debug=True, host='0.0.0.0', port=80)

auth.py

# jwt.exceptions.DecodeError 에러가 났을경우 PyJWT 버전을 낮춰줘야한다. pip install 'PyJWT<2.0.0'
import jwt
import bcrypt
from flask import request
from flask_restx import Resource, Api, Namespace, fields

users = {}

Auth = Namespace(
    name="Auth",
    description="사용자 인증을 위한 API",
)

user_fields = Auth.model('User', {  # Model 객체 생성
    'name': fields.String(description='a User Name', required=True, example="Scene")
})

user_fields_auth = Auth.inherit('User Auth', user_fields, {
    'password': fields.String(description='Password', required=True, example="password")
})

jwt_fields = Auth.model('JWT', {
    'Authorization': fields.String(description='Authorization which you must inclued in header', required=True, example="eyJ0e~~~~~~~~~")
})

@Auth.route('/register')
class AuthRegister(Resource):
    @Auth.expect(user_fields_auth)
    @Auth.doc(responses={200: 'Success'})
    @Auth.doc(responses={500: 'Register Failed'})
    def post(self): # post : 계정 등록하는 역할을 하는
        name = request.json['name']
        password = request.json['password']
        if name in users: # 이미 등록되어있으니 등록 안되고 Failed return
            return {
                "message": "Register Failed"
            }, 500
        else:
            users[name] = bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt())  # nmae을 키로하고 value에 비밀번호 저장
            return {
                'Authorization': jwt.encode({'name': name}, "secret", algorithm="HS256").decode("UTF-8")  # str으로 반환하여 return
            }, 200

@Auth.route('/login')
class AuthLogin(Resource):
    @Auth.expect(user_fields_auth)
    @Auth.doc(responses={200: 'Success'})
    @Auth.doc(responses={404: 'User Not Found'})
    @Auth.doc(responses={500: 'Auth Failed'})
    def post(self): # post : id, pw 정보 일치하는지 여부 확인 <- 여기선 왜 post를 썻을까???
        name = request.json['name']
        password = request.json['password']
        if name not in users:
            return {
                "message": "User Not Found"
            }, 404
        elif not bcrypt.checkpw(password.encode('utf-8'), users[name]):  # 비밀번호 일치 확인
            return {
                "message": "Auth Failed"
            }, 500
        else:
            return {
                'Authorization': jwt.encode({'name': name}, "secret", algorithm="HS256").decode("UTF-8") # str으로 반환하여 return
            }, 200

@Auth.route('/get')
class AuthGet(Resource):
    @Auth.doc(responses={200: 'Success'})
    @Auth.doc(responses={404: 'Login Failed'})
    def get(self):
        header = request.headers.get('Authorization')  # Authorization 헤더로 담음
        if header == None:
            return {"message": "Please Login"}, 404
        data = jwt.decode(header, "secret", algorithm="HS256")
        return data, 200

스샷

  • register : POST
    • 처음 등록했을 때
    • 같은 걸로 두번째 등록하려하면 에러가 발생함
  • login : POST
    • 정상적으로 로그인 됐을 경우
    • name or password가 틀렸을 경우
  • get : GET
    • Headers에 login에서 확인했던 Authorization 담아서 보냈을 때

References

profile
oneofakindscene

0개의 댓글