TIL. 최종프로젝트(7) simpleJWT for_user

2023년 06월 12일 by barryjung

    TIL. 최종프로젝트(7) simpleJWT for_user 목차

[오늘 한일]

  • 랭킹조회 기능 작성
  • 소셜로그인 연계 토큰처리 커스텀 클래스 작성
  • 퀴즈 결과처리 기능 구상

 

[오늘 배운점]

<랭킹조회 쿼리셋 분기>

랭킹조회는 지난 프로젝트때 익힌 대로,

generic.ListAPIView로 작성하여, 페이지네이션과 시리얼라이저를 적용했고,

 

쿼리 파라미터로써 구분값을 입력받아서

경험치순 랭킹, 배틀포인트순 랭킹을 구분하여 제공한다.

 

view는 하나로 합치면서 파라미터를 이용해 프론트와 연결성과 확장성이 유리하다. 

 

 


<소셜 로그인 커스텀 토큰 클래스>

우리 프로젝트는 소셜로그인을 all auth라이브러리로 구현했다.

이 all auth는 각 제공자에 대해 로그인 처리 후 토큰을 넘겨받아,

해당 토큰을 취급하는 것까지 지원한다.

 

반면 우리 프로젝트는 simpleJWT를 토큰 로그인에 사용한다.

이럴 경우 all auth의 마지막 단계에서 simple JWT에 연계하여 토큰을 생성하여 제공하면 된다.

(인터넷으로 알아보면서 개념이 정리됬다.)

 

all auth에서 로그인 승인까지 처리되고 마지막 단계에서,

각 제공자에게 정보제공 요청을 보낼 전용 access token을 받게 된다.

해당 토큰과 특정 URL로 요청을 보내면 사용자 정보를 받아온다. (email등)

all auth의 마지막 승인에서는 사용자의 생성이 이뤄진다.

마지막 승인이 (200 ok)여야지 저장을 동작한다.

 

그럼 받아온 email을 이용해 사용자 오브젝트를 찾는다.

그리고 사용자 오브젝트를 이용해서 토큰을 얻어내는 메소드를 사용한다.

 

※ 소셜 계정은 User DB에 이메일과 비밀번호를 저장하지만,

이 비밀번호는 각각의 방식으로 암호화되어있는 데이터이다.

그래서 이메일과 비밀번호로 view를 통과시키는건 적절하지 않고

비밀번호를 취급하는 것도 부적절할 것이다.

 

from rest_framework_simplejwt.tokens import AccessToken, RefreshToken
data= AccessToken.for_user(유저 인스턴스)
data= RefreshToken.for_user(유저 인스턴스)

simplejwt에는 이렇게 AccessToken, RefreshToken이라는 모듈이 있는데,

이 모듈이 가진 for_user라는 메소드를 사용하면

유저 인스턴스를 이용해서 토큰을 전역규칙에 맞게 생성하여 반환할수 있다.

토요일에 이부분까지 확인했었다.

액세스토큰과 리프레쉬토큰이 잘 제공된다.

 

그런데 문제는 이 토큰을 jwt로 열어봤을때 있었다.

우리가 토큰에 실어주고 싶은 username과 email이 포함되지 않았다.

우리는 tokenobtainpairView를 오버라이드하고,

TokenObtainPairSerializer를 오버라이드 하여 커스텀 뷰와 시리얼라이저를 만들었고,

시리얼라이저에서 get token메소드를 커스텀해 토큰에 추가정보를 실게했다.

 

그런데 AccessToken, RefreshToken 모듈은

view가 아니고, 모듈을 열어봐도 serializer가 부여되는 구석은 없었다.

 

이문제에 대해 메모를 해뒀었고 오늘 이 문제를 당면하게 되었다.

chatgpt, 인터넷을 이용해 방법을 모색해봤다.

 

settings에 디폴트 설정을 줘서 해결하는 방법과

AccessToken, RefreshToken 모듈을 커스텀하는 방법이 보였다.

 

먼저 디폴트 설정을 주는 방법은 settings에 설정하라는 건데,

#settins.py
REST_FRAMEWORK = {}

이 부분에다가 default token serializer를 주는 방법이다.

하지만 생각했던대로, AccessToken, RefreshToken 모듈은 view가 아니고, 

serializer적용도 안되기 때문에 이방법은 효과가 없었다.

 

AccessToken, RefreshToken 모듈을 커스텀하는걸 도전해봤다.

모듈 파일을 직접 열어가면서 현재 모듈에 맞게 최적화하여 오버라이드 할수 있게 노력했다.

 

from rest_framework_simplejwt.tokens import Token, AccessToken, RefreshToken


class CustomToken(Token):
    @classmethod
    def for_user(cls, user):
        token = super().for_user(user)
        token["email"] = user.email
        token["username"] = user.username

        return token


class CustomAccessToken(CustomToken, AccessToken):
    pass


class CustomRefreshToken(CustomToken, RefreshToken):
    pass

이렇게 작성하여 제대로 동작시킬수 있었다.

 

이번 프로젝트를 하며 모듈 클래스에 대한 커스텀을 여러차례 시도하게 되었다.

그러면서 노하우를 알게 된건데,

상위 클래스의 메소드를 오버라이드 할때는 그 리턴값을 주목해본다.

리턴 값에 대해 가공을 하면 되는 문제라고 한다면,

 

상위 메소드 실행에 리턴값을 얻어 변수에 담아주고,

그 변수를 가공한다. (가공 하기전에 print로 값의 모습을 체크해볼수 있다)

그리고 필요한 동작만 한다음 해당 변수를 그대로 리턴하는 것이다.

 

이렇게하면 상위 메소드의 코드를 일일이 가져오지 않아도 된다.

특히 모듈 클래스의 경우 모듈 파일내에서 다른 처리가 이뤄질 여지가 많다.

그래서 적절한 부분만 오버라이드 하는게 필요하다.

 

 

한편, 내가 커스텀한 클래스가 내가 필요한 클래스의 상위 클래스일수 있다.

이 케이스도 그렇다. 이부분때문에 꽤 고민했다.

이럴때는 당연히 하위 클래스도 커스텀하여 선언하면서

내가 만든 커스텀 클래스를 상속하도록 해야 한다.

 

그럼 원래 모듈클래스와 연결성이 없으니 난감할수 있고,

코드를 다 옮겨 적기에는 모듈 파일내 처리 등이 따라 오지 못해 결국 동작이 불가하다.

 

해법은 간단하다.
위 작성한것처럼 상속할 클래스, 원래 클래스를 다중 상속하면 된다.

이런 방법으로 해결하지 못하는 경우도 있을수 있겠지만,

오늘의 경우에는 원래 클래스가 필요한 속성의 나열이였기 때문에,

상속 클래스로 for_user가 동작하며 필요한 속성은 원래 클래스에서 받아오는 것으로

스마트하게 처리가 되었다.

 

결국 다중 상속한 클래스중 for_user메소드는 어디에 있고,

특정 속성은 어디에 있는지가 관건이고 알아서 잘 참조해다가 사용하는것 같다.

 

결론적으로 실제 for_user메소드를 동작시키는 하위 커스텀 클래스들은 내용이 pass이다.

필요한 내용이 상속클래스에 다 있기 때문이다.

이렇게 해서 커스텀 토큰 처리 클래스를 만들수 있었다.