인증과 인가에 대한 전체적인 개념과 흐름은 이 포스팅에서 확인해주세요!
단방향 salting, stretching 방식으로 암호화를 하기 때문에 복호화가 거의 불가능합니다.
다음 문서를 참조하여 작성하였습니다.
프로젝트에 bcrypt를 설치하는 방법은 다음과 같습니다.
터미널에 다음 명령어를 입력해주세요:
pip install bcrypt
bcrypt의 kdf에 대한 설명입니다.
key = bcrypt.kdf(
... password=b'password', >> 바이트 값으로 변환
... salt=b'salt', >> 솔트값
... desired_key_bytes=32, >> 32바이트
... rounds=100) cost 기본값 12 >> 2의 제곱
이 부분에 대해서는 심화 학습 후 재정리가 필요한 부분인 것 같습니다.
회원가입을 할 때 받은 비밀번호를 암호화하기 위해 많이 사용하는데요.
회원가입을 하는 앱의 views.py
에서 다음과 같이 사용할 수 있습니다:
# bcrypt를 import합니다.
import bcrypt
# view 클래스 내부에서 password를 암호화하는 방식은 다음과 같습니다.
# password 데이터를 요청 body에서 가져옵니다.
password = data["password"]
# 비밀번호를 인코딩합니다. (문자열 > 바이트 변환)
encoded_pw = password.encode("utf-8")
# 비밀번호를 해싱합니다. (slating + stretching)
secret_pw = bcrypt.hashpw(encoded_password, bcrypt.gensalt())
# DB에 저장하기 위해 해싱된 바이트를 문자열로 다시 변환합니다.
decoded_password = secret_password.decode("utf-8")
바이트로 변환된 비밀번호에 임의로 새로운 바이트 값을 더해 기존 패스워드를 찾지 못하게 합니다.
이러한 과정을 salting 소금을 친다
라고 부릅니다.
사용 방법은 위에도 있지만 한번 더 짚고 넘어가겠습니다.
# 다음 함수를 통해 salt값을 생성할 수 있습니다.
bcrypt.gensalt()
# 아래 구문을 통해 salt값이 어떻게 생겼는지 확인해볼 수 있습니다.
salt = bcrypt.gensalt()
print(salt)
소금값 = bcrypt.gensalt()
해시값 = bcrypt.hashpw(바이트, 소금값)
>> 아직 바이트!!!
DB에는 해시값.decode("utf-8")
을 저장해줍니다.
위의 사용 구문에서 hashpw()
라는 함수가 사용되었는데요.
말 그대로 비밀번호를 해시하겠다!
라는 함수입니다.
사용 방법은 다음과 같습니다:
# bcrypt.hashpw(인코딩 비밀번호, 소금값)
# bcrpyt.hashpw(password.encod("utf-8"), bcrypt.gensalt())
이번 함수는 비밀번호를 확인할 때 사용합니다.
비밀번호를 확인하는 단계는 로그인을 하거나 마이페이지에 접근 등 특정 인가 절차가 필요할 때 사용되는데요.
사용 방법은 다음과 같습니다.
# POST 요청으로 body에 비밀번호가 있다고 가정하겠습니다.
data = json.loads(request.body)
user_password = data["password"]
user_account = data["account]
# 사용자가 요청하면서 보낸 비밀번호 인코딩
encoded_password = user_password.encode("utf-8")
# 저장되어있는 사용자의 기존 비밀번호 인코딩
saved_password = User.objects.get(account = user_account).password.encode("utf-8")
# 두 변수를 비교합니다.
bcrypt.checkpw(encoded_password, saved_password)
장고 프로젝트의 SECRET_KEY를 활용하여 payload에 담긴 내용을 복호화할 수 있습니다.
통신은 stateless
하기 때문에 유저가 로그인을 하여도 다음 요청에서도 로그인을 하는 불편함을 겪을 것입니다.
서버는 이 토큰을 로그인 한 유저에게 발급하고, 유저는 로그인을 한번만 하게 되면 그 이후의 요청에 서버에게 받았던 토큰 정보를 담아 요청을 하게 됩니다.
서버는 그 토큰의 권한을 다시 확인하게 되는 것이죠.
프로젝트에 pyjwt를 설치하는 방법은 다음과 같습니다.
터미널에 다음 명령어를 입력해주세요:
pip install pyjwt
# jwt를 import합니다.
import jwt
# access_token 변수에 저장
access_token = jwt.encode({"user_id" : user.id, SECRET_KEY, algorithm=ALGORITHM}
토큰에 저장되는 유저 정보는 인코딩 되는 부분이기 때문에 데이터베이스에 저장되는 패스워드나 주민등록번호 등 주요 정보를 담으면 안됩니다.
모든 데이터베이스의 데이터가 갖고있는 id
값은 단지 숫자형 데이터이기 때문에 payload
에 id
값을 활용합니다.
위의 코드는 로그인 요청 시 받은 user
데이터의 id 값을 payload에 담았습니다.
로그인하는 유저의 id=5
라면 5번 유저의 정보를 payload에 담는 것입니다.
발행한 토큰은 유저가 다시 로그인 할 필요 없이 다른 서비스를 이용할 수 있는 출입증과 같은 요소로 활용됩니다.
# jwt를 import합니다.
import jwp
# 요청 시 헤더의 {"Authorization": "token정보"} 토큰 값을 가져옵니다.
access_token = request.headers.get("Authorization", None)
# payload라는 변수에 토큰을 복호화하여 로그인 한 user 객체 정보를 담습니다.
# 토큰을 발행하면서 {"user_id": user.id} 라는 유저 정보를 사용하였습니다.
payload = jwt.decode(access_token, settings.SECRET_KEY, settings.ALGORITHM)
# payload에서 user_id 값을 통해 데이터베이스 상에서 일치하는 유저 정보를 불러옵니다.
user = User.objects.get(id = payload["user_id"])
# 요청한 유저 정보를 가리킵니다.
request.user = user
decorator
웹서비스에서 로그인이 선행되어야 하는 서비스는 다양할 것입니다.
장바구니, 게시글 작성, 댓글 작성, 좋아요 누르기 등등...
해당 기능을 구현하기 위해 view 로직을 작성해야 할 텐데요.
여러 app에 토큰의 권한을 확인하는 코드를 작성하는 것은 매우 비효율적일 것입니다.
해결방법은 위의 코드를 모듈로 따로 작성하여 필요한 기능에 decorator로 사용하는 방법이 있습니다.
데코레이터 함수를 작성하면서 예외처리를 통해 특정 에러를 반환해야 합니다.
자주 사용하는 에러에 대해 알아보겠습니다.
InvalidTokenError
직역) 토큰 디코드 실패 때 발생하는 기본
에러
토큰 자체가 유효하지 않은 경우에 발생하는 에러입니다.
DecodeError
직역) 유효성 검사에 실패하여 토큰을 디코딩할 수 없을 때 발생하는 에러
올바르게 구조화된 토큰의 경우 Header.Payload.Signature로 이루어져 있습니다.
하지만 이 세 부분 중 특정 부분이 잘못되어 디코딩 할 수 없는 경우 발생합니다.
InvalidSignatureError
직역) 토큰의 Signature 부분이 발행하였던 토큰과 일치하지 않을 때 발생합니다.
토큰의 구조를 다시 한번 살펴보면,
Header
는 SECRET_KEY와 ALGORITHM에 대한 정보를,
Payload
는 유저와 토큰에 대한 정보를 가지고 있습니다.
이 둘의 정보를 합쳐서 다시 암호화를 한 부분이 Signature
부분입니다.
ExpiredSignatureError
직역) 토큰의 만료되었을 때 발생하는 에러
payload에는 토큰의 만료기간인 exp
정보를 포함하는데요
이 기간이 만료되었을 때 발행하는 에러입니다.
정보
가 일치하지 않다.구조
가 일치하지 않다.토큰의 작은 부분부터 큰 부분, 그 이후 없는 경우의 예외 처리를 순차적으로 해주어야 합니다.