From 99bd6f21a4069e2bcec53988defc179e138714fe Mon Sep 17 00:00:00 2001 From: yjoonjang Date: Sun, 7 Apr 2024 04:46:54 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20=EC=B9=B4=EC=B9=B4=EC=98=A4=20=EC=9D=B8?= =?UTF-8?q?=EA=B0=80=EC=BD=94=EB=93=9C=20=EC=A0=84=EC=86=A1=20=EC=8B=9C=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EA=B0=80=EC=9E=85=20=ED=9B=84=20?= =?UTF-8?q?=EB=AA=A8=EB=93=A0=20=EC=A0=95=EB=B3=B4=20=EB=B0=98=ED=99=98?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- accounts/serializers.py | 2 +- accounts/urls.py | 3 +- accounts/views.py | 174 +++++++++++++++++++--------------------- 3 files changed, 83 insertions(+), 96 deletions(-) diff --git a/accounts/serializers.py b/accounts/serializers.py index f2aac45..ee844f5 100644 --- a/accounts/serializers.py +++ b/accounts/serializers.py @@ -27,7 +27,7 @@ class KakaoTokenSerializer(serializers.Serializer): class UserInfoUpdateSerializer(serializers.ModelSerializer): class Meta: model = User - fields = ("username", "position", "profile_image") + fields = ("id", "username", "position", "profile_image") class GetUserInfoSerializer(serializers.ModelSerializer): diff --git a/accounts/urls.py b/accounts/urls.py index 9dd5c7e..4e542ae 100644 --- a/accounts/urls.py +++ b/accounts/urls.py @@ -4,9 +4,8 @@ # TODO swagger에 뜨는 api 관리 urlpatterns = [ path("kakao/", views.kakao_login, name="kakao_login"), - path("kakao/login/", views.kakao_callback, name="kakao_callback"), path( - "kakao/login/finish/", + "kakao/login/", views.KakaoLoginView.as_view(), name="kakao_login_todjango", ), diff --git a/accounts/views.py b/accounts/views.py index eafd6c6..414c56a 100644 --- a/accounts/views.py +++ b/accounts/views.py @@ -10,15 +10,18 @@ OpenApiParameter, ) from django.http import JsonResponse +from rest_framework_simplejwt.tokens import RefreshToken import requests from django.shortcuts import redirect +from django.contrib.auth import get_user_model from json.decoder import JSONDecodeError from rest_framework.decorators import api_view, permission_classes from rest_framework.permissions import AllowAny, IsAuthenticated from rest_framework.response import Response from rest_framework.views import APIView from rest_framework import status +from rest_framework.authtoken.models import Token from allauth.socialaccount.providers.oauth2.client import OAuth2Client from dj_rest_auth.registration.views import SocialLoginView @@ -42,11 +45,12 @@ env.read_env(env_file) BASE_URL = env("BASE_URL") -KAKAO_CALLBACK_URI = BASE_URL + "accounts/kakao/login/" +KAKAO_CALLBACK_URI = BASE_URL + "accounts/kakao/callback/" # KAKAO_CALLBACK_URI = "http://api.resumai.kr/accounts/kakao/callback/" REST_API_KEY = env("KAKAO_REST_API_KEY") CLIENT_SECRET = env("KAKAO_CLIENT_SECRET_KEY") +User = get_user_model() @extend_schema(exclude=True) def kakao_login(request): @@ -55,105 +59,89 @@ def kakao_login(request): f"https://kauth.kakao.com/oauth/authorize?client_id={REST_API_KEY}&redirect_uri={KAKAO_CALLBACK_URI}&response_type=code" ) +class KakaoLoginView(SocialLoginView): -@extend_schema( - summary="카카오 로그인", - description="code를 GET 요청으로 보내면 access token, code를 반환합니다.", - parameters=[ - OpenApiParameter( - name="code", type=str, description="발급받은 카카오의 code 입니다." - ), - ], - examples=[ - OpenApiExample( - response_only=True, - summary="Response Body Example입니다.", - name="success_example", - value={ - "access_token": "string", - "code": "string", + @extend_schema( + summary="카카오 로그인 마무리", + description="code (인가 코드)를 post 요청으로 보내면 access token, 유저 정보를 반환합니다. **(id_token은 불필요합니다.)**", + request={ + "application/json": { + "type": "object", + "properties": { + "code": {"type": "string"}, + }, }, - ), - ], -) -@permission_classes([AllowAny]) -@api_view(["GET"]) -def kakao_callback(request): - code = request.GET.get("code") - logger.fatal(code) - - # Access Token Request - token_req = requests.get( - f"https://kauth.kakao.com/oauth/token?grant_type=authorization_code&client_id={REST_API_KEY}&client_secret={CLIENT_SECRET}&redirect_uri={KAKAO_CALLBACK_URI}&code={code}" + }, + examples=[ + OpenApiExample( + response_only=True, + summary="Response Body Example입니다.", + name="success_example", + value={ + "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b", + "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0", + "user_info": { + "id": 6, + "email": "yjoonjang@naver.com", + "username": "장영준", + "profile_image": "https://k.kakaocdn.net/dn/cI6qGf/btsCovDyklV/ydaQojxohw6VnLxtcdKwuk/img_640x640.jpg", + "created": False + } + }, + ), + ], ) + def post(self, request, *args, **kwargs): + code = request.data.get("code") + print(code) - token_req_json = token_req.json() - logger.fatal(token_req_json) + if not code: + return Response({"error": "Code is required"}, status=status.HTTP_400_BAD_REQUEST) - error = token_req_json.get("error") - if error is not None: - raise JSONDecodeError(error) + # 카카오 인가코드를 사용해 access_token 획득 + token_res = requests.get(f"https://kauth.kakao.com/oauth/token?grant_type=authorization_code&client_id={REST_API_KEY}&client_secret={CLIENT_SECRET}&redirect_uri={KAKAO_CALLBACK_URI}&code={code}") + + if token_res.status_code != 200: + return Response({"error": "Failed to obtain access token"}, status=status.HTTP_400_BAD_REQUEST) + + token_json = token_res.json() + access_token = token_json.get("access_token") + + # 카카오 access_token으로부터 사용자 정보 획득 + headers = { + "Authorization": f"Bearer {access_token}" + } + profile_res = requests.get("https://kapi.kakao.com/v2/user/me", headers=headers) + + if profile_res.status_code != 200: + return Response({"error": "Failed to obtain user information"}, status=status.HTTP_400_BAD_REQUEST) + + profile_json = profile_res.json() + + kakao_oid = profile_json.get("id") + nickname = profile_json.get("properties")["nickname"] + profile_image = profile_json.get("properties")["profile_image"] + email = profile_json.get("kakao_account")["email"] + + user, created = User.objects.get_or_create(email=email, defaults={"username": f"{nickname}", "kakao_oid": kakao_oid, "profile_image": f"{profile_image}"}) + + # 사용자에 대한 토큰 생성 + refresh = RefreshToken.for_user(user) + data = { + "access_token": str(refresh.access_token), + "refresh_token": str(refresh), + "user_info": { + "id": user.id, + "email": user.email, + "username": user.username, + "profile_image": user.profile_image, + "is_created": created + } + } + + return Response(data, status=status.HTTP_200_OK) - access_token = token_req_json.get("access_token") - logger.fatal(access_token) - # Email Request - profile_request = requests.get( - "https://kapi.kakao.com/v2/user/me", - headers={"Authorization": f"Bearer {access_token}"}, - ) - profile_data = profile_request.json() - - kakao_oid = profile_data.get("id") - kakao_account = profile_data.get("kakao_account") - username = kakao_account["profile"]["nickname"] - profile_image_url = kakao_account["profile"]["profile_image_url"] - email = kakao_account.get("email") - - data = {"access_token": access_token, "code": code} - # TODO 유저 프로필 이미지 저장하도록 - return JsonResponse(data) - - -@extend_schema( - summary="카카오 로그인 마무리", - description="access token, code를 post 요청으로 보내면 access token, 유저 정보를 반환합니다. **(id_token은 불필요합니다.)**", - parameters=[ - OpenApiParameter( - name="access_token", - type=str, - description="발급받은 카카오의 access_token 입니다.", - ), - OpenApiParameter( - name="code", type=str, description="발급받은 카카오의 code 입니다." - ), - ], - request={ - "application/json": { - "type": "object", - "properties": { - "access_token": {"type": "string"}, - "code": {"type": "string"}, - }, - }, - }, - examples=[ - OpenApiExample( - response_only=True, - summary="Response Body Example입니다.", - name="success_example", - value={ - "access": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlI", - "refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBl", - "user": {"pk": 6, "email": "yjoonjang@naver.com"}, - }, - ), - ], -) -class KakaoLoginView(SocialLoginView): - adapter_class = kakao_view.KakaoOAuth2Adapter - client_class = OAuth2Client - callback_url = KAKAO_CALLBACK_URI class UpdateUserInfoView(APIView):