React-Django 카카오로그인

김범기·2024년 6월 13일

django

목록 보기
10/12
post-thumbnail

개요

개인 프로젝트를 진행하면서 소셜로그인을 구현해야겠다는 생각으로 진행했다.
이전에 카카오 로그인을 시도하고, 구글 로그인을 시도하면서 DB를 싹 갈아엎은 후에 jwt 토큰을 이용하는 방식으로 시작했다.
초보 개발자(호소인)이므로 그냥 내가 나중에 보고 참고하기 위한 글이기에 제대로 안적혀 있을 수 있으니 참고하길 바람.

카카오 로그인

카카오가 확실히 문서가 직관적으로 되어있어서 그런지, 구글로그인에 비하면 금방코드를 적용할 수 있었고 실행되었다.

이전 구글로그인의 경험과 함께 아래 블로그를 참고해서 작성을 했다.

참고 블로그1

카카오 로그인 문서

이번에는 django에서만 진행한 것 없이 바로 react와 django를 연동해서 시작했다.

React Django에서 작업

React

카카오 로그인 버튼을 만들어주었다.
몰랐는데 카카오에서 제공하는 자체 규격 디자인이 있었다.

<div onClick={() => {navgiateToKakaoLogin()}}>
	<img src={process.env.PUBLIC_URL+"images/kakaoLoginBtn1.png"} alt="" />
</div>

이제 인가 코드를 받아야한다.
구글 로그인과 마찬가지로 redirect_uri가 필요한데 이것과 함께 "client_id", "response_type"을 작성해주었다.

const kakaoParams :KakaoParamsType = {
    "client_id" : kakaoClientId,
    "redirect_uri" :  kakaoRedirectUri,
    "response_type" : "code",
  }
  const kParams = new URLSearchParams(kakaoParams).toString()

  function navgiateToKakaoLogin(){
    window.location.href = `https://kauth.kakao.com/oauth/authorize?${kParams}`
  }

window.location.href = https://kauth.kakao.com/oauth/authorize?client_id=${REST_API_KEY}&redirect_uri=${REDIRECT_URI}&response_type=code;

kParams를 작성해서 대입하는 방법말고 직접넣어줘도 된다.

이렇게 하면 내가 redirect_uri로 넘어가는데, 난 그 부분에 컴포넌트로 만들어주고 그곳에서 code를 받고 넘겨서 토큰을 받아오는 작업을 진행했다.

export async function getKakaoLoginCode(code:string) {
  try {
    const response = await axios.post(`${base_url}accounts/kakao/login/`, {code})
    console.log("response===", response)
    return response
  } catch (e:any) {
    // 실패 시 처리
    console.error(e.response.status);
  }
}

여기서 accounts/kakao/login/은 backserver와의 연동이다.

django

django에서 urls.py를 다음과 같이 설정하고 views.py를 생성했다.

path('kakao/login/', views.kakao_login, name='kakao_login'),

kakao_login 코드는 이전 구글로그인에서처럼 다음처럼 구성했다.

  1. 코드를 받아서 카카오에서 토큰과 정보를 받아오기
  2. 해당 정보를 통해 user DB에 저장된 데이터가 있으면 가져오기
  3. 없으면, 데이터 입력하기
  4. 실패시, 실패...ㅠㅜ


토큰을 받아오는 URL은 요기,


사용자 정보 받아오는 URL은 요기.

여기서도 구글로그인에서 했던것처럼, allauth를 제대로 쓰지않고, simple-jwt를 이용해서 token을 만들고 사용했다.

구글로그인에서는 내가 못찾은건지 모르겠지만, profile_image_url을 확인못했는데, 카카오에는 있었기에, 이를 활용해주었다.

카카오에서 별도의 로그아웃도 있었는데, 이 부분은 이번 개인 프로젝트에서는 구현하지 않기로 했다.

아래는 전체코드

@api_view(["POST"])
def kakao_login(reqeust):
    client_id = os.environ.get("SOCIAL_AUTH_KAKAO_CLIENT_ID")
    redirect_uri = os.environ.get("SOCIAL_AUTH_KAKAO_REDIRECT_URI")
    # 시도
    try:
        reqeust_data = reqeust.data
        # request에서 code 꺼내기
        code = reqeust_data.get("code")
        # 카카오에서  토큰 받아오기
        kakao_token_response = requests.post(
            'https://kauth.kakao.com/oauth/token',
            headers={"Content-type": "application/x-www-form-urlencoded;charset=utf-8"},
            data={
                "grant_type": "authorization_code",
                "client_id": client_id,
                "redirect_uri": redirect_uri,
                "code": code
            }
        )
        access_token = kakao_token_response.json().get("access_token")  
        # 카카오에서 정보 받아오기
        kakao_user = requests.get(
            "https://kapi.kakao.com/v2/user/me",
            headers={
                "Authorization": f"Bearer {access_token}",
                "Content-type": "application/x-www-form-urlencoded;charset=utf-8",
            },
        )
        print("kakao_user:", kakao_user)
        kakao_user = kakao_user.json()
        
        kakao_account = kakao_user.get("kakao_account")
        # social = "kakao"
        social_id = kakao_user.get("id")
        email = kakao_account.get("email")
        nickname = kakao_account["profile"]["nickname"]
        profile_image_url = kakao_account["profile"]["profile_image_url"]
        print("kakao_account:", kakao_account)
        print("social_id", social_id)
        print("email", email)
        print("nickname", nickname)
        print("profile_image_url", profile_image_url)
        # user데이터가 있으면 token과 데이터 가져오기
        try:
            user = User.objects.get(email=email, social='kakao')
            refresh_token = RefreshToken.for_user(user) # jwt발급
            response_data =  {
                'refresh': str(refresh_token),
                'access': str(refresh_token.access_token),
                # 'email': email,
                'message': '가져오기 성공',
                'userInfo' : {
                    'nickname' : user.nickname,
                    'social' :user.social,
                    'email' : user.email,
                    'profile_image_url' : user.profile_image_url
                }
            }

            return Response(response_data, status=status.HTTP_200_OK)
        # user데이터가 없으면 생성하고 token 가져오기
        except User.DoesNotExist:
            # 닉네임이 없으면 닉네임을 설정
            if not nickname:
                nickname = f"{random.choice(nickname_a)}{random.choice(nickname_b)}{random.randint(1000, 9999)}"
            data = {
                'email' : email,
                'username' : email + str(random.randint(100000, 1000000)),
                'nickname' : nickname,
                'social' : 'kakao',
                'social_id' : str(social_id),
                'profile_image_url' : profile_image_url
            }
            try:
                # user 생성
                user = User.objects.create(
                    email=data.get("email"),
                    username=data.get("username"),
                    nickname=data.get("nickname"),
                    social=data.get("social"),
                    social_id=data.get("social_id"),
                    profile_image_url=data.get("profile_image_url")
                )
                user.set_unusable_password()    # 소셜로그인이니까 No password!
                user.save() # user 저장
                print('저장성공')
                # user 꺼내기                
                user = User.objects.get(username=data.username, social=data.social, email=email)
                refresh_token = RefreshToken.for_user(user) # 자체 jwt 발급

                response_data = {
                    'refresh': str(refresh_token),
                    'access': str(refresh_token.access_token),
                    'message': '저장성공',
                    'userInfo' : {
                        'nickname' : user.nickname,
                        'social' :user.social,
                        'email' : user.email,
                        'profile_image_url' : user.profile_image_url
                    }
                }

                return Response(response_data, status=status.HTTP_201_CREATED)
            except:
                response_data = {
                    'message': '저장실패'
                }
                return Response(status=status.HTTP_400_BAD_REQUEST)
    # 이게 안되네...
    except:
        response_data = {
            'message': '실패'
        }
        return Response(status=status.HTTP_400_BAD_REQUEST)

문제?!

수정을 하나 하긴 해야할 듯 하다.
분명 저장도 잘되는것을 확인했는데, 저장하고, 즉시 들고 오는것이 불가능한 건지 에러가발생했다.
db를지우고 다시시도도 해봐야겠다.
물론 이미 DB로 저장됬으므로 처음이 안되는거지 이후에는 잘 들고온다.

문제해결

user = User.objects.get(username=data.username, social=data.social, email=email)

이 부분이 잘 못 되어있었다.
수정후 꺼낼 때, 아래처럼 해야한다. data.get("") 또는 data[""]의 방식으로 해야되는데 이 부분을 잘 못 작성했다. 아래처럼 수정하니 문제는 해결되었다.

user = User.objects.get(username=data.get("username"), social=data.get("social"), email=email)

profile
반드시 결승점을 통과하는 개발자

0개의 댓글