소프트웨어 마에스트로/BackEnd(Django)

[Django] postman 공부 + 프론트

alpakaka 2024. 10. 25. 21:56

어제 멘토링 결과 더 이상 줄이긴 어려울 것 같다고 하셨다. 아마 생성하는데만 2~3분 정도 걸리는거로 추측된다고 하셨다. 

그래서 정말 마지막으로 하나만 테스트해보고 다른 일들을 할 예정이다.

 

마지막으로 할일은 40-mini -> 3.5 turbo 로 바꿔보려고 한다. azure 를 사용해보라는 말도 있긴 하던데... 일단 터보버전 해보고서 꽤나 괜찮으면 테스트해보면 될 것 같다.

일단 이제는 3.8~4.0(s) 사이가 평균으로 나오는 속도이다.

 

속도는 확실히 빠르다

3초 중반대까지 줄일 수 있어보인다. 그러나 문제가 있는데..

 

 

확실히 원하는 결과가 나오지 않는다. 쓸데없는 language 와 같은 속성이 생기기도 하고..

 

타임아웃도 걸리기는하는데 22초는 너무 긴것같다... 좀 더 줄이는 방안을 찾아봐야할것같다.

문제를 발견했다. 이상하게 한 1분뒤에 리퀘스트를 보내면 계속 10초 이내로 답변이 오지 않는다. 중간에 연결이 끊어지는 건가? 

일정 시간 이후부터는 계속 400에러 + 너무 오래 걸리는 문제가 발생하고 있다.. 얼른 해결해봐야할 듯 싶다.

 

그런데 일단 다른 급한 문제부터 해결해보면 좋을 것 같다. 아직 저 문제는 dev에 올라가지 않은 상황이라 일단 다음에 해결해도 괜찮을 것 같다. 예상은 httpx 의 커넥션 풀링 관련으로 에러가 발생하고 있는 것 같은데 이 부분을 깊게 하는 건 나중에 해봐도 좋을 듯 싶다.

 

 


우선 소셜 로그인을 드디어 완료했다.

계속 에러가 뜨길래 팀짱님께 물어봤는데 그 app_id 를 잘못 넣고 있었던 것이었던 것이다....

 

class GoogleLogin(APIView):
    """
    request : token, device_token, type(0 : android, 1 : ios)
    """

    authentication_classes = []
    permission_classes = [AllowAny]

    def post(self, request):
        try:
            device_type, token, device_token = self.validate_request(request)
            idinfo = self.verify_token(device_type, token)
            if "accounts.google.com" in idinfo["iss"]:
                email = idinfo["email"]
                user, is_new = User.get_or_create_user(email)
                refresh = self.handle_device_token(user, device_token)
                return Response(
                    {
                        "refresh": str(refresh),
                        "access": str(refresh.access_token),
                        "is_new": is_new,
                        "email": email,
                    },
                    status=status.HTTP_200_OK,
                )
        except Exception as e:
            sentry_sdk.capture_exception(e)
            return Response(
                {"error": str(e)}, status=status.HTTP_400_BAD_REQUEST
            )

    def validate_request(self, request):
        device_type = request.data.get("type", None)
        token = request.data.get("token")
        device_token = request.data.get("device_token", None)
        if not token or device_type is None:
            raise LoginException()
        return device_type, token, device_token

    def verify_token(self, device_type, token):
        if device_type == DEVICE_TYPE_ANDROID:  # Android
            return id_token.verify_oauth2_token(
                token,
                google_requests.Request(),
                audience=GOOGLE_ANDROID_CLIENT_ID,
            )
        elif device_type == DEVICE_TYPE_IOS:  # iOS
            return id_token.verify_oauth2_token(
                token, google_requests.Request(), audience=GOOGLE_IOS_CLIENT_ID
            )
        else:
            raise LoginException("Invalid device type")

    def handle_device_token(self, user, device_token):
        if device_token:
            FCMDevice.objects.get_or_create(
                user=user, registration_id=device_token
            )
            return CustomRefreshToken.for_user(user, device_token)
        else:
            return CustomRefreshToken.for_user_without_device(user)


class AppleLogin(APIView):
    """
    request : token, device_token, type(0 : android, 1 : ios)
    """

    APPLE_APP_ID = settings.SECRETS.get("APPLE_APP_ID")
    APPLE_PUBLIC_KEYS_URL = "https://appleid.apple.com/auth/keys"

    authentication_classes = []
    permission_classes = [AllowAny]

    def post(self, request):
        try:
            device_type, token, device_token = self.validate_request(request)
            # verify apple token and get email
            email = self.verify_token(token)
            user, is_new = User.get_or_create_user(email)
            refresh = self.handle_device_token(user, device_token)
            return Response(
                {
                    "refresh": str(refresh),
                    "access": str(refresh.access_token),
                    "is_new": is_new,
                    "email": email,
                },
                status=status.HTTP_200_OK,
            )
        except Exception as e:
            sentry_sdk.capture_exception(e)
            return Response(
                {"error": str(e)}, status=status.HTTP_400_BAD_REQUEST
            )

    def verify_token(self, identity_token):
        """
        Finish the auth process once the access_token was retrieved
        Get the email from ID token received from apple
        """
        # Verify the token
        try:
            # JWT의 헤더에서 kid 추출
            unverified_header = jwt.get_unverified_header(identity_token)
            kid = unverified_header["kid"]

            # kid에 맞는 Apple 공개 키 가져오기
            public_key = self.get_apple_public_key(kid)

            # 토큰 디코드 및 검증
            decoded_token = jwt.decode(
                identity_token,
                public_key,
                algorithms=["RS256"],
                audience=self.APPLE_APP_ID,
                issuer="https://appleid.apple.com",
            )
            email = decoded_token.get("email", None)
            return email
        except jwt.ExpiredSignatureError as e:
            sentry_sdk.capture_exception(e)
            raise LoginException("Token has expired")
        except jwt.InvalidTokenError as e:
            sentry_sdk.capture_exception(e)
            raise LoginException("Invalid token")
        except Exception as e:
            sentry_sdk.capture_exception(e)
            raise LoginException(f"An unexpected error occurred: {e}")

    def validate_request(self, request):
        device_type = request.data.get("type", None)
        token = request.data.get("token")
        device_token = request.data.get("device_token", None)
        if not token or device_type is None:
            raise LoginException()
        return device_type, token, device_token

    def handle_device_token(self, user, device_token):
        if device_token:
            FCMDevice.objects.get_or_create(
                user=user, registration_id=device_token
            )
            return CustomRefreshToken.for_user(user, device_token)
        else:
            return CustomRefreshToken.for_user_without_device(user)

    def get_apple_public_key(self, kid):
        # SSL 검증 비활성화하고 Apple 공개 키를 가져옴
        response = requests.get(self.APPLE_PUBLIC_KEYS_URL, verify=False)
        keys = response.json().get("keys", [])

        # kid에 맞는 키 찾기
        for key in keys:
            if key["kid"] == kid:
                return RSAAlgorithm.from_jwk(key)
        raise ValueError("Matching key not found")

1차 완성본

 

import jwt
import requests
import sentry_sdk
from django.conf import settings
from django.contrib.auth import get_user_model
from fcm_django.models import FCMDevice
from google.auth.transport import requests as google_requests
from google.oauth2 import id_token
from rest_framework import status
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView

from accounts.exceptions import LoginException
from accounts.serializers import UserSerializer
from accounts.tokens import CustomRefreshToken

User = get_user_model()


JWT_SECRET_KEY = settings.SECRETS.get("JWT_SECRET_KEY")
GOOGLE_ANDROID_CLIENT_ID = settings.SECRETS.get("GCID")
GOOGLE_IOS_CLIENT_ID = settings.SECRETS.get("GOOGLE_IOS_CLIENT_ID")
DEVICE_TYPE_ANDROID = 0
DEVICE_TYPE_IOS = 1


class BaseLogin(APIView):
    authentication_classes = []
    permission_classes = [AllowAny]

    def post(self, request):
        try:
            device_type, token, device_token = self.validate_request(request)
            email = self.verify_token(device_type, token)
            user, is_new = User.get_or_create_user(email)
            refresh = self.handle_device_token(user, device_token)
            return Response(
                {
                    "refresh": str(refresh),
                    "access": str(refresh.access_token),
                    "is_new": is_new,
                    "email": email,
                },
                status=status.HTTP_200_OK,
            )
        except Exception as e:
            sentry_sdk.capture_exception(e)
            return Response(
                {"error": str(e)}, status=status.HTTP_400_BAD_REQUEST
            )

    def validate_request(self, request):
        device_type = request.data.get("type", None)
        token = request.data.get("token")
        device_token = request.data.get("device_token", None)
        if not token or device_type is None:
            raise LoginException()
        return device_type, token, device_token

    def handle_device_token(self, user, device_token):
        if device_token:
            FCMDevice.objects.get_or_create(
                user=user, registration_id=device_token
            )
            return CustomRefreshToken.for_user(user, device_token)
        else:
            return CustomRefreshToken.for_user_without_device(user)


class GoogleLogin(BaseLogin):
    def verify_token(self, device_type, token):
        if device_type == DEVICE_TYPE_ANDROID:  # Android
            return id_token.verify_oauth2_token(
                token,
                google_requests.Request(),
                audience=GOOGLE_ANDROID_CLIENT_ID,
            )
        elif device_type == DEVICE_TYPE_IOS:  # iOS
            return id_token.verify_oauth2_token(
                token, google_requests.Request(), audience=GOOGLE_IOS_CLIENT_ID
            )
        else:
            raise LoginException("Invalid device type")


class AppleLogin(BaseLogin):
    APPLE_APP_ID = settings.SECRETS.get("APPLE_APP_ID")
    APPLE_PUBLIC_KEYS_URL = "https://appleid.apple.com/auth/keys"

    def verify_token(self, device_type, identity_token):
        try:
            unverified_header = jwt.get_unverified_header(identity_token)
            kid = unverified_header["kid"]
            public_key = self.get_apple_public_key(kid)
            decoded_token = jwt.decode(
                identity_token,
                public_key,
                algorithms=["RS256"],
                audience=self.APPLE_APP_ID,
                issuer="https://appleid.apple.com",
            )
            email = decoded_token.get("email", None)
            return email
        except jwt.ExpiredSignatureError as e:
            sentry_sdk.capture_exception(e)
            raise LoginException("Token has expired")
        except jwt.InvalidTokenError as e:
            sentry_sdk.capture_exception(e)
            raise LoginException("Invalid token")
        except Exception as e:
            sentry_sdk.capture_exception(e)
            raise LoginException(f"An unexpected error occurred: {e}")

    def get_apple_public_key(self, kid):
        response = requests.get(self.APPLE_PUBLIC_KEYS_URL, verify=False)
        keys = response.json().get("keys", [])
        for key in keys:
            if key["kid"] == kid:
                return jwt.algorithms.RSAAlgorithm.from_jwk(key)
        raise ValueError("Matching key not found")

리팩토링 진행한 버전

BaseLogin 을 만들고 로직이 다른 verify 만 다른 로직으로 수정하였다.

150 -> 120 정도로 줄여보았다. 근데 일단 보낼때 response 바디 변경할 때 편할 듯 보인다.

 

계속 워닝이 뜨는데 

import urllib3
# Disable InsecureRequestWarning for this specific request
        urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

이걸로 해결했다.

 

 

일단 이렇게 소셜 로그인은 완료되었다 ^^b

 


그래서 일단 header 를 매번 받아오거나 allowany 로 바꾸는 게 귀찮기 때문에 이를 좀 개선해보고자 한다.

postman 의 pre-request script 기능을 통해서 해결할 수 있다고 하니 한번 찾아보려한다.

https://blog.postman.com/how-to-access-google-apis-using-oauth-in-postman/

 

How to Access Google APIs Using OAuth 2.0 in Postman | Postman Blog

In this tutorial, you'll learn how to use Postman to access a Google API using OAuth 2.0.

blog.postman.com

였는데 뭔가 뭔가 점점 찾을 수록 너무도 많은 시간이 걸릴 것 같아 포기했다.

나에겐 해야할 일이 아직 많다.

 


이제 httpx 관련 문제를 해결해보려고한다.

왜 1분뒤에 요청하면 timeout 이 걸릴 정도로 문제가 발생하는가...(timeout을 10분으로 해도 걸린다)

일단 관련 오류들을 이미 겪은 사람들이 있지 않을까?

 

처음엔 httpx 풀링 관련으로 설정해서 문제가 발생하는 줄 알았는데, 해당 설정을 삭제하고 하니 무한 로딩에 걸렸다.

일단 급하게 Timeout 부터 걸어주어야할것같다.

이 문제는 내일해봐야할 것 같다....