ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • TIL.64 (Token)_Authentication
    TIL/Django 2020. 12. 11. 23:16
    728x90

    Westagram Project를 하며 회원가입, 로그인, 게시물, 댓글 등의 기능을 구현해보았다.

    오늘은 로그인 당시 부여한 Access_token을 이용한 회원가입/로그인 인증 구현을 마쳐놓은 상태이다.

    이제 인증을 마쳤으니, 발행한 토큰을 이용해 

    유저가 요청하는 request를 실행할 수 있는 권한이 있는 유저인가를 확인하는 절차(인가)를 구현해보자

     

    먼저 인가를 구현하기 위한 방법으로 크게(?) 2가지가 있다고 생각한다.

    1. Django REST Framework 에서 지원하는 Authorization 4가지를 이용하는 방법

    www.django-rest-framework.org/api-guide/authentication/

     

    Authentication - Django REST framework

     

    www.django-rest-framework.org

     

    2. DRF 인가 방법을 사용하는 것이 아닌,  별도의 토큰 검증 함수를 만들어 이를 데코레이터로 활용하는 방법이다.

    이 방법도 크게 2가지로 나뉘며 

    2-1 : 데코레이터 함수 wrapper 함수를 이용하는 방법

    2-2 : __call__ 함수를 이용하는 방법

    2-2 방법을 사용하여 구현하도록 하겠다.


    대략적인 Flow는 아래와 같다.

    회원가입, 로그인 OK -> 로그인 요청 접수 -> 로그인 성공시 access_token을 생성하여 응답과 함께 프론트서버 (-> 클라이언트)에게 응답

    이 토큰을 저장할 장소로 브라우저의 쿠기 또는 브라우저 로컬, 세션 스토리지등에 저장 ->

    로그인된 유저가 게시물을 등록하고 싶다는 요청 ->  발행했던 토큰을 검증 ->

    문제없을 경우 게시물 등록 권한허가 or 발행한 토큰이 불일치 할 경우 게시물 등록 제한


    *참고(JWT 는 어디에 저장하는것이 좋을까에 대한 고찰)

    lazyhoneyant.tistory.com/7

     

    JWT 토큰은 어디에 저장하는게 좋을까?

    이전에 JWT 토큰에 대한 글을 쓴 적이 있는데 그것의 연장선 상에 있는 이야기이다. JWT 를 구현하는데에 있어서 토큰을 어디에 저장해야 하는가는 한번 생각해 볼만한 문제이다. 토큰 인증이란

    lazyhoneyant.tistory.com


    먼저 App 파일 내의 utils.py 파일을 새로 생성한다

    해당 파일을 토큰 검증 함수로 데코레이터에 사용하겠다.

    import json, jwt
    from user.models import User
    from my_settings import SECRET_KEY, ALGORITHM
    from django.http import JsonResponse
    
    class LoginConfirm:
        def __init__(self, original_function):
            self.original_function = original_function
    
        def __call__(self, request, *args, **kwargs):
            print('============ 1111111 ==========')
            token = request.headers.get("Authorization", None)
            print(token)
            try:
                if token:
                    print('========= 33333 =========')
                    token_payload = jwt.decode(token, SECRET_KEY, ALGORITHM)
                    print('======= 444444 ========')
                    user = User.objects.get(id=token_payload['user-id'])
                    print('========= 5555555 ========')
                    request.user = user
                    print('========== 666666 =======')
                    return self.original_function(self, request, *args, **kwargs)
                return JsonResponse({'MESSAGE' : 'NEED_LOGIN'}, status=400)
    
            except jwt.ExpiredSignatureError:
                return JsonResponse({'MESSAGE' : 'EXPIRED_TOKEN'}, status=401)
    
            except jwt.DecodeError:
                return JsonResponse({'MESSAGE' : 'INVALID_USER'}, status=401)
    
            except User.DoesNotExist:
                return JsonResponse({'MESSAGE' : 'INVALID_USER'}, status=401)

     

    __call__

    : 클래스를 함수처럼 호출시킬 수 있는 함수, 데코레이터 함수에서의 wrapper함수의 역할을 한다

     

    token = request.headers.get("Authorization", None)

    : 전송된 HTTP 요청에서 "Authorization" 헤더 값을 읽은 후 access token을 얻음

     

    token_payload = jwt.decode(token, SECRET_KEY, ALGORITHM)

    : jwt를 암호화할때 사용한 KEY, algorithm 을 사용하여 token을 복호화 해준 후 token_payload에 할당

    발행된 토큰을 복호화하여 user-id 값을 가져오는데 이를 딕셔너리 자료구조형으로 가져온다.

    따라서 {'user-id' : 13} 이라는 값으로 복호화된다 (게시물을 등록하려는 유저는 id 13번째에 해당되는 유저이다)


    여기서 token_payload와 user 를 print해보면 위의 설명이 이해가 된다.

    print(token_payload)
    # {'user-id': 13}
    print(user)
    # User object (13)
    print(request.user)
    # User object (13)

    user = User.objects.get(id=token_payload['user-id'])

    : 복호화한 jwt의 사용자의 id를 변수에 할당 후 SigninView에서 토큰 발행시 사용하였던 ['user-id']를 입력해준다.

    이 코드의 의미는 복호화한 사용자의 id가 토큰 발행시 사용하였던 id와 같은지를 비교하는 코드이다.

    위의 복호화한 정보를 이용하여 User 테이블의 해당하는 id값이 있다면 그아래 코드가 실행되는 것이다.


    user/views.py 에서 토큰을 발행 당시 아래와 같은 코드를 이용해 토큰을 발행해주었다.

    access_token  = jwt.encode({'user-id' : User_get.id}, SECRET_KEY, ALGORITHM)
                return JsonResponse({'MESSAGE' : 'SUCCESS', 'access_token' : access_token.decode('utf-8')}, status=201)

     

    request.user = user

    위 코드의 의미는 위에서 할당해온 User objest(13)을 어떤 별도의 테이블 컬럼으로 자동으로 생성해주는? 기능을 가지고있다.

     

    # 로그인이 정상적으로 된 사용자가 토큰 인가를 거쳐 게시물을 작성하는 상황을 예로 들어보자.

    @LoginConfirm 에서 토큰을 검증하고 

    이제 권한이 부여되었으니 게시물을 작성할 수 있다.

    이러한 토큰을 보내는 요청은 게시물을 등록할때와 같이 헤더에 포함되어 들어오게 되는데 

    아래와 같은 요청으로 이루어진다.

    1. 로그인 정상적으로 완료

    2. 게시물 이름 겹침없음 상태이다.

    그런데 왜 아래와 같이 에러가 발생하는 걸까? (토큰인증은 제대로 작동하였으며, 게시물 등록 로직은 문제가 없다)

    (게시물 제목 : AABBBB , image_url : 123.jpg , 게시물내용 : 1111111, Token Request)

    아래 형식을 지켜야 정상적으로 요청이 들어온다

    "Authorization:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyLWlkIjoxM30.9lVx8olgktiSkjHoqjOXPXT6tBmTLJq5wVZQ42ecxnQ"

    http -v POST 127.0.0.1:8000/board/ board_name=AABBBB image=123.jpg contents=11111 "Authorization:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyLWlkIjoxM30.9lVx8olgktiSkjHoqjOXPXT6tBmTLJq5wVZQ42ecxnQ"
    
    # "Authorization: 토큰"

     

    이유는 인증을 거치면서 아래코드의 data['user']라는 정보가 없어졌기 때문이다.

     


    토큰이 생성되지 않은 상태를 생각해보면

    단순 게시물 등록 로직 구현시 어떤 user가 게시물을 등록하는지 해당유저의 id 값을 이용해 정보를 전달했었다.

    하지만, 토큰 인증을 거치면서 우리는 이미 어떤 user가 게시물을 올리는지 알고 있지만

    이전 로직에서는 data['name']에 대한 값이 입력이 안되었다 판단하여 KeyError를 반환하는 것이다.


    자, 그럼 위에서 어려운 개념이었던 request.user = user 의 의미는 무엇일까

    데코레이터는 하위 함수가 실행되기 전에 먼저 실행되는 함수를 지정하느 방법으로

    게시물 등록 POST 함수가 실행되기전 @LoginConfirm이 먼저 실행되며 토큰을 검증하는데

    이때 토큰을 검증하면서  위에서 할당해온 User objest(13)을 어떤 별도의 테이블 컬럼으로 자동으로 생성해주는? 역할을 하는게 아닐까

    추측 된다 (자세히 알아보고 수정하자)

     

    따라서 토큰인증을 마무리 하면 User object(13)에 해당하는 user라는 테이블의 컬럼을 생성하고

    다음 게시물 등록 POST 함수로 넘어가서 POST함수에서는 토큰 검증 과정에서 생성된 테이블을 이용하여 접근을 해야 한다.

     

    이렇게 생각하는 이유는 요청당시 토큰만 보내줄뿐 어떤 유저인지는 전달을 해주지는 않지만!

    토큰에는 해당 유저에 대한 DB 정보가 들어있기 때문이다.

     

    이전 로직에서는 토큰의 내용을 읽어들이지 못하므로 이전 찾고자 하는 유저 값이 요청으로 들어오지 않은 것으로 인식한다.

    따라서 토큰 검증을 거치는 모든 함수들은 토큰 검증후 생성되는 컬럼값에 접근하여 원하는 정보를 가져와야 할것이다.


    위에서 설명한 바와 같이 POST 함수의 코드를 검증 후

    생성된 컬럼값에서 가져오는 방법으로 접근하면 문제없이 통신이 이루어지는것을 볼수 있다.

    http -v POST 127.0.0.1:8000/board/ board_name=AABBBB image=123.jpg contents=11111 "Authorization:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyLWlkIjoxM30.9lVx8olgktiSkjHoqjOXPXT6tBmTLJq5wVZQ42ecxnQ"
    
    # "Authorization: 토큰"

     

     

    참고 : wave1994.tistory.com/66?category=872868

     

    파이썬(Django) :: 인증 데코레이터(Decorator) 클래스

    # Why 인증 데코레이터  HTTP 프로토콜은 각각의 통신이 독립적이기 때문에 이전에 사용자가 인증을 했는지 알 수 없으며, 새로운 페이지로 넘어 갈떄마다 인증을 해줘야는 문제점이 있다. 이를

    wave1994.tistory.com

     

    728x90
Designed by Tistory.