JWT 토큰을 이용한 로그인 실습
필요한 패키지 설치
pip install PyJWT == 1.7.1 # 다른 버전은 에러가 났었다
pip install bcrypt # 회원가입시 유저의 패스워드 암호화 위해 설치
JWT 발급
-
로그인시 유저가 보낸 id와 pwd를 받는다(body에 데이터가 담겨져 들어온다)
-
id와 pwd가 정확한 값인지 확인한다. 이때 pwd는 bcrypt를 이용해 암호화 한 값이 DB에 저장되었기 때문에
bcrypt.checkpw
로 입력받은 pwd와 유저의 DB에 저장된 pwd가 같은지 확인한다 -
이후 jwt로 토큰을 생성한다. 나는 유저의 id를 payload에 담아 id로 게시글의 수정이나 삭제를 진행할 예정이다
return JsonResponse({'token':token}, status=200)
로 브라우저측에 token을 전송한다
class SignInView(View):
def post(self, request):
data = json.loads(request.body)
try:
if User.objects.filter(userid=data['userid']).exists():
user = User.objects.get(userid=data['userid'])
if bcrypt.checkpw(data['password'].encode('utf-8'), user.password.encode('utf-8')):
token = jwt.encode({'user_id':user.id}, SECRET_KEY, algorithm='SHA256').decode('utf-8')
return JsonResponse({'token':token}, status=200)
return JsonResponse({'message':'error'}, status=401)
except KeyError:
return JsonResponse({'message': 'Invalid Keys'}, status=400)
bcrypt란
bcrypt는 DB에 유저의 패스워드를 저장할때 암호화 해주는 패스워드 해싱 라이브러리이다. DB에 password가 저장될때 암호화 해서 저장하는것이 법적으로 정해져 있다고 한다.
bcrypt.hashpw()로 비밀번호를 저장한다. 이때 컴퓨터가 바로 읽을 수 있는 값으로 변경하기 위해서 hashpw 함수에 넣기전에 비밀번호를 인코딩하여 byte로 바꾸어 주어야 한다.
인코딩한 비밀번호를 해싱하는 방법이다 : hashed_pwd = bcrypt.hashpw(password.encode(‘utf-8’), bcrypt.gensalt()).decode(‘utf-8’) 이때 뒤에 decode(‘utf-8)을 붙이는 이유는 DB에 들어갈때에는 str값으로 받아 매칭하기 때문에 만들어진 해시 함수를 디코딩하여 DB에 넣어준다
게시글 수정시 인가된 유저인지를 확인하기
check_login이라는 데코레이터를 만들어 글을 생성할때 인가된 유저인지 jwt를 통해 확인한다. 이후 해당 글을 삭제할 권한을 판단한다
- 브라우저에서 헤더에 함께 담아온 토큰을
request.headers.get
으로 받아온다. - 받아온 토큰을 디코딩하여 유저의 정보인 id를 받아온다. 이때 토큰이 없을 경우 error 메시지를 전송한다
여기까지가 jwt로 유저를 확인하는 과정이다. 글의 작성자가 현재 로그인 한 유저의 글인지 아닌지 확인하기 위해서 디코딩하여 받아온 유저가 글의 작성자가 맞는지 다른지 확인하여 글 수정의 권한을 결정한다
import jwt
from django.http import JsonResponse
from config.settings import SECRET_KEY
from user.models import User
def login_check(func):
def wrapper(self, request, *args, **kwargs):
try:
access_token = request.headers.get('Authorization', None)
payload = jwt.decode(access_token, SECRET_KEY, algorithms='HS256')
user_id = User.objects.get(id = payload['user_id'])
request.user = user_id
except jwt.exceptions.DecodeError:
return JsonResponse({'message':'Token Error'}, status=400)
except:
return JsonResponse({'message': 'Error'}, status=400)
return func(self, request, *args, **kwargs)
return wrapper
나는 아래와 같이 유저에게 권한을 주었다. request.user가 데코레이터에서 전달받은 유저의 id이다.
if Post.objects.get(id = kwargs['id']).writer.userid != User.objects.get(id = request.user.id).userid:
return JsonResponse({'message':'수정 권한 없음'}, status=400)
기본적인 jwt의 실습은 굉장히 비슷한 흐름이다. 하지만 나는 이해하고 사용하는데 굉장히 많은 시간이 걸렸다…^^