/ JWT

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의 실습은 굉장히 비슷한 흐름이다. 하지만 나는 이해하고 사용하는데 굉장히 많은 시간이 걸렸다…^^