ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 기본 장고에서 JWT를 활용한 로그인 구현
    Django 2022. 3. 27. 14:23

    오늘은 기본 장고에서 기존 csrf 세션 방식이 아닌 JWT를 활용하여 로그인을 구현해보려고 한다.

     

    일단 간단하게 구현 방식을 설명하자면

     

    1. 사용자가 로그인을 하게되면 서버에서 사용자를 확인 후 Access Token 발급

    2. 발급된 토큰을 cookie에 저장

    3. 사용자가 데이터 요청시 Access Token을 보냄

    4. 서버에서 Access Token을 검증 후 응답

     

     

     

     

     

    1. package install

    PyJWT 와 django-dotenv를 insall 해준다.

     

     

    2. 프로젝트 폴더에 .env 파일을 생성한다.

    SECRET_KEY= (secret_key)
    JWT_ALGORITHM="HS256"

    secret_key는 프로젝트를 생성하면 settings.py에 발급되는SECRET_KEY이다.

    또한 csrf 토큰은 사용하지 않을 것이기 때문에 settings.py / MIDDLEWARE 의 csrf관련 코드를 주석처리 하자.

     

     

    3. users앱에 utils폴더를 만든 후 jwt.py를 생성한다.(vscode를 사용하면 __init__.py도 생성해야함)

    import jwt
    from django.conf import settings
    
    JWT_ALGORITHM = getattr(settings, 'JWT_ALGORITHM', None)
    SECRET_KEY = getattr(settings, 'SECRET_KEY', None)
    
    
    def encode_jwt(data):
        return jwt.encode(data, SECRET_KEY, algorithm=JWT_ALGORITHM)
    
    
    def decode_jwt(access_token):
        return jwt.decode(
            access_token,
            SECRET_KEY,
            algorithms=[JWT_ALGORITHM],
            issuer="Bookin",
            options={"verify_aud": False},
        )

    encode_jwt : jwt를 암호화하는 함수

    decode_jwt : 암호화된 토큰을 decode하는 함수

    issuer : 발급자 ( 원하는 이름으로 사용한다. )

     

     

    4. 토큰 발급, 로그인 데코레이터 코드

    토큰을 사용한 인증/인가는 프로젝트 전반적인 모든 곳에서 사용한다고 생각했기 때문에 core앱을 만들고 utils.py에 코드를 작성하였음

     

    core/utils.py

    from django.shortcuts import render
    import jwt
    from users.utils.jwt import encode_jwt, decode_jwt
    from users.models import UserModel
    from datetime import datetime, timedelta
    
    
    def generate_access_token(email):
        iat = datetime.now()
        exp = iat + timedelta(days=5)
    
        data = {
            "iat": iat.timestamp(),
            "exp": exp.timestamp(),
            "aud": email,
            "iss": "Bookin",
        }
    
        return encode_jwt(data)
    
    
    def login_authenticate(request, email, page):
        data = {}
        data["access_token"] = generate_access_token(email)
        data["email"] = email
        user = UserModel.objects.get(email=data["email"])
    
        response = render(request, page)
        response.set_cookie('access_token', data['access_token'])
    
        return response
    
    
    def login_decorator(func):
        def wrapper(request, *args, **kwargs):
            try:
                access_token = request.COOKIES['access_token']
                payload = decode_jwt(access_token)
                user = UserModel.objects.get(email=payload["aud"])
                request.user = user
            except jwt.exceptions.DecodeError:
                print('INVALID ERROR')
                request.user
            except KeyError:
                print('KEY ERROR')
                request.user
    
            return func(request, *args, **kwargs)
    
        return wrapper
    
    
    def authentication(request):
        email = request.user
        user = UserModel.objects.get(email=email)
        return user

    1. generate_access_token 함수는 토큰을 발급하는 함수이다.

     

    iat : 발행일

    exp : 만료일 (5일)

    aud : email로 한 이유는 email을 unique field로 지정해놨기 떄문에 email로 작성했다.

    iss : 발급자로 위 jwt.py에 작성한 issuer 와 같은 이름으로 해야 한다.

     

    2. login_authenticate 함수는 generate_access_token함수를 사용하여 토큰을 발급하고 인증받는 함수이다.

    set_cookie() 함수를 사용하여 발급된 토큰을 cookie에 저장한다.

     

    3. login_decorator 함수는 로그인 인증을 받아야하는 기능을 간편하게 사용하기위해 decorator로 함수를 만들었다.

    토큰 인증을 받은 사용자만 기능을 사용할 수 있다.

    아래 sign_in_view 함수를 보면 함수위에 @login_decorator를 추가해주는 방식으로 사용할 수 있다.

    로그인 인증이 필요한 기능에 @login_decorator를 붙여주자.

     

    4. authentication 함수는 View에서 GET으로 요청이 들어왔을 때 인증받은 사용자를 구분하기위해 작성하였다.

     

     

    5. View 작성

    @login_decorator
    def sign_in_view(request):
        if request.method == 'POST':
            email = request.POST.get('email', '')
            password = request.POST.get('password', '')
            me = auth.authenticate(request, email=email, password=password)
            page = 'bookstore/main.html'
            if me is not None:
                return login_authenticate(request, email, page)
            else:
                return render(request, 'users/signin.html', {'error': '비밀번호를 확인해주세여'})
        if request.method == 'GET':
            try:
                user = authentication(request)
                if user:
                    return render(request, 'bookstore/main.html')
            except Exception:
                return render(request, 'users/signin.html')
    
    
    @login_decorator
    def user_logout(rqeuest):
        reset = ''
        response = redirect("/sign-in")
        response.set_cookie('access_token', reset)
        return response

    logout을 하면 쿠키에 들어있는 access_token은 비어진다.

     

     

     

     

    위 코드들은 유효성 검사와, 보안 사항이 조금 미흡한 코드이다. 기본장고에서 JWT가 어떻게 생성되고 사용될 수 있는지 참고만 하였으면 좋겠다. 

    원래는 아래 그림과 같은 과정으로 코드 구현을 해야한다.

    refresh_token 또한 발급이 되면 해커가 access_token을 중간에 탈취하더라도 만료 시간이 짧아 보안상 유리하다.

    refresh_token을 발급할 때 같은 로그인 한 user의 DB에도 refresh_token이 저장이되고

    access_token이 만료되면 쿠키에 있는refresh_token과 DB에 있는 토큰이 같은지 확인하고 새로운 access_token을 발급해준다.

     

     

Designed by Tistory.