슈코딩

[Django, JavaScript] 프론트엔드, 백엔드 분리 카카오 로그인 구현 본문

개발일지/Project

[Django, JavaScript] 프론트엔드, 백엔드 분리 카카오 로그인 구현

Roshu 2022. 7. 20. 23:47

 

이번에 매번 도전해왔지만 끝내 성공하지 못했던 소셜로그인을 드디어 처음으로 성공을 했다. 구글링을 통해서 나오는 자료들은 대부분 프론트와 백엔드가 분리되어있지 않은 상태에서의 코드들이거나 함수 베이스의 View라서 현재 진행 하는 프로젝트와는 어울리지 않는 코드들 뿐이었다. 하지만 그동안 배운지식과 나름(?) class 기반으로 작성한 코드를 발견해서 

그 코드를 custom해서 프로젝트에 적용 시킬 수 있게 만들었다.

 

우선은 JavaScript부터 보면

function kakaoLogin() {
    window.Kakao.Auth.login({
        scope: 'profile_nickname, account_email',
        success: function (authObj) {
            window.Kakao.API.request({
                url: '/v2/user/me',
                success: res => {
                    kakaoAccount = res.kakao_account;
                    kakaoUserData = {
                        'email': kakaoAccount['email'], 
                        'nickname': kakaoAccount['profile']['nickname']
                    }
                    kakaoLoginApi(kakaoUserData)
                }
            });
        }
    });
}

기본적으로 kakao 에서 제공해주는 API를 활용해서 로그인하는 팝업창을 띄운다. 그리고 return받는 정보에 

email과 nickname을 필수동의 (비즈앱 활성화)를 받고 kakaoLoginApi()라는 api함수 안에 인자로 넣어준다.

 

async function kakaoLoginApi(kakaoUserData) {

    const response = await fetch(`${backEndBaseUrl}/users/api/kakao/`, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'X-CSRFToken': csrftoken,
        },
        body: JSON.stringify(kakaoUserData),
    }
    )
    response_json = await response.json()

    if (response.status == 200) {
        setLocalStorageItems()
        alert(response_json['msg'])
        window.location.reload()

    } else if (response.status == 201) {
        setLocalStorageItems()
        alert("원활한 서비스 이용을 위해 주소를 입력해주세요.")
        addressModalView();
    }
}

받은인자를 json data 로 fetch api를 사용해서 백엔드로 request하면 이제 class에서 정보를 받아서 대조하고, UserModel과 SocalAccount Model에 저장을 해준다.

 

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework_simplejwt.tokens import RefreshToken
from allauth.socialaccount.models import SocialAccount
from user.models import User as UserModel

class KakaoLoginView(APIView):

    def post(self, request):
        email = request.data.get("email")
        nickname = request.data.get("nickname")

        try:
            # 기존에 가입된 유저와 쿼리해서 존재하면서, socialaccount에도 존재하면 로그인
            user = UserModel.objects.get(email=email)
            social_user = SocialAccount.objects.filter(user=user).first()
            #로그인
            if social_user:
                refresh = RefreshToken.for_user(user)
                
                return Response({'refresh': str(refresh), 'access': str(refresh.access_token), "msg" : "로그인 성공"}, status=status.HTTP_200_OK)
            
            # 동일한 이메일의 유저가 있지만, social계정이 아닐때 
            if social_user is None:
                return Response({"error": "email exists but not social user"}, status=status.HTTP_400_BAD_REQUEST)
            
            # 소셜계정이 카카오가 아닌 다른 소셜계정으로 가입했을때
            if social_user.provider != "kakao":
                return Response({"error": "no matching social type"}, status=status.HTTP_400_BAD_REQUEST)
    
        except UserModel.DoesNotExist:
            # 기존에 가입된 유저가 없으면 새로 가입
            new_user = UserModel.objects.create(
                nickname=nickname,
                email=email,
            )
            #소셜account에도 생성
            SocialAccount.objects.create(
                user_id=new_user.id,
            )

            refresh = RefreshToken.for_user(new_user)
                
            return Response({'refresh': str(refresh), 'access': str(refresh.access_token), "msg" : "회원가입 성공"}, status=status.HTTP_201_CREATED)

 

팀원과 같이 고민하며 새벽까지 짠 코드인데, 정말 구글링을 많이했지만 가장 알아보기 쉽게 구현한게 아닌가 생각이 들면서도 이렇게 간단하게 해버려서 뭔가 빠트린게 없나 싶지만, 원하는 JWT토큰도 RefreshToken을 활용해서 발급하고 

프론트에서 payload도 따로 분리해서 localstorage에 저장하고 해서 일반 유저랑 다를게 없는 상태를 만들어냈다. 

 

가장 힘들었던 부분은 유저를 어떤 방식으로 저장을 해야하는지, JWT토큰은 어떻게 발급을 해야할지, 그리고 social 유저인걸 어떻게 구분을 할지 였는데 그 해답은 저장은 받아온 정보만 가지고 UserModel에 저장하면 됐었고, JWT는 RefreshToken이라는 simple-jwt 라이브러리의 class를 활용할 수 있었고, social유저인걸 구분하는것은 allauth의 socialaccount 모델에 따로 저장을해서 쿼리를 통해서 소셜유저를 구분 할 수 있었다. 막상 구현해보기 전에는 정말 복잡하다 느껴지고 어렵게만 느껴졌는데 다 구현해보니 생각보다 코드가 단순해서 놀랐다. 원하던 class base view로도 만들고 

프론트와 백이 분리된 상태에서도 잘 작동을 하니 만족스럽게 구현을 한 것 같다. 

Comments