[Django] postman 공부 + 프론트
어제 멘토링 결과 더 이상 줄이긴 어려울 것 같다고 하셨다. 아마 생성하는데만 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 부터 걸어주어야할것같다.
이 문제는 내일해봐야할 것 같다....