NoSleepPlace 프로젝트 2 - 카카오 소셜 로그인 API

문승준·2021년 12월 9일
0

Wecode Project

목록 보기
6/8
post-thumbnail

카카오 로그인을 이용한 회원가입과 로그인 기능을 구현했다.

내가 구현한 백엔드 부분의 로직과 테스트 코드를 되돌아보려한다.

1. 전체 개념과 흐름

프론트엔드의 역할

  • Kakao developers 설정

    • 카카오 개발자 등록 후 앱을 등록하면 API KEY를 받을 수 있다. (REST API 키, JavaScript 키 등)

    • 서비스 앱에서 인가 코드를 받을 REDIRECT URI를 등록한다. (우리는 localhost:3000)

    • 플랫폼 메뉴에서 사이트 도메인을 설정한다. (배포 전 localhost:3000)

  • 사용자가 로그인 클릭시

    • 클릭 이벤트 핸들러에서 카카오 로그인 함수Kakao.Auth.authorize 로 간편 로그인을 요청한다.

    • 사용자가 정보 제공 및 기능 활용에 동의를 하면 인가 코드 응답이 RIDIRECT URI로 리다이렉트된다. (HTTP 302)

    • 인가 코드를 토큰 발급 API로 보내서 카카오 액세스 토큰을 받는다.

    • 카카오 액세스 토큰을 백엔드 서버로 보내준다.

백엔드의 역할

  1. 받은 카카오 액세스 토큰을 사용자 정보를 가져오는 카카오 API로 보내서 유효성 검사를 한다.

  2. 유효성이 검증되면 사용자 정보를 응답으로 받는다.

  3. 해당 정보가 DB에 없으면 create하고, DB에 존재하면 get을 한다.

  4. 해당 사용자의 id를 담아서 JWT를 활용해 토큰을 생성하고 응답으로 보낸다.

  • 간단 정리

    • 프론트는 사용자를 로그인시키고 인가코드를 받아서 그것을 활용해 다시 카카오한테 액세스 토큰을 받는다.

    • 백엔드는 그 액세스 토큰을 가지고 카카오톡 사용자 정보를 받아오고 우리 서비스 자체 액세스 토큰을 만들어서 보내준다.

    • 프론트엔드는 인가코드까지만 백엔드에 전달해 줄 수 있으며 역할 분담은 조정할 수 있다.

2. 백엔드 로직

카카오 액세스 토큰으로 사용자 정보 요청 하기

  • 요청을 보내야하는 target은 https://kapi.kakao.com/v2/user/me 이다.

  • Request의 header에 Authorization: Bearer {ACCESS_TOKEN} 을 담아 보낸다.

  • Python의 HTTP 라이브러리 중 Requests를 사용하면 요청을 보낼 수 있다.

  • 아래 코드는 기존 전체 로직에서 kakao API 부분만 Class로 만들어서 모듈화한 것이다.

import requests

from django.http import JsonResponse

class KakaoAPI:
    def __init__(self, access_token):
        self.access_token = access_token
        self.user_url     = 'https://kapi.kakao.com/v2/user/me'

    def get_kakao_user(self):
        headers  = {"Authorization": f"Bearer {self.access_token}"}
        response = requests.post(self.user_url, headers=headers, timeout=3)

        if not response.status_code == 200:
            return JsonResponse({'message':"INVALID_KAKAO_TOKEN"}, status=401)

        return response.json()
  • 배운점

    • Class로 따로 분리하면 다양한 소셜 로그인이 추가되었을때 유지 보수가 용이할 것이다.

    • requests 라이브러리는 timeout 옵션이 반드시 필요하다.
      타임아웃 시간 내에 서버의 응답이 없으면 예외를 발생시키는 역할을 한다. (바이트 값이 소켓에 접수되지 않을 때)

카카오 회원 정보로 회원가입 및 로그인 하기

  • 프론트엔드로 부터 카카오 액세스 토큰을 받아온다. (헤더에 담아 보내주기로 했다.)

  • 위 KakaoAPI의 get_kakao_user 메서드를 사용해 카카오 회원 정보를 받아온다.

  • 받아온 회원 정보의 JSON 구조에 따라 필요한 정보를 DB에 입력 혹은 비교한다. (get_or_create)

  • 사용자 동의 항목에 따라 값이 없으면 빈 문자열로 저장한다.

  • 아래 코드는 전체 로직 중 일부만 가져왔다.

# 카카오 액세스 토큰으로 유저 정보 받아오기

kakao_token   = request.headers.get('Authorization')
kakao_user    = KakaoAPI(kakao_token).get_kakao_user()
# DB에 동일한 kakao_id가 있으면 get하고 없으면 create한다.

user, created = User.objects.get_or_create(
                kakao_id = kakao_user['id'],
                defaults = {
                    'nickname'     : nickname,
                    'profile_image': profile_image,
                    'email'        : kakao_account.get('email', ''),
                    'age_range'    : kakao_account.get('age_range', '')
                }
            )
  • 배운점
    • get_or_create() 메서드는 defaults 값을 제외한 인자값으로 get()을 시도한다. 객체를 찾으면 반환하며, 그렇지 않으면 인자값과 **defaults 로 인스턴스를 만들고 반환한다.

3. 유닛 테스트 코드

  • 유닛 테스트를 처음 적용하다보니 로직을 작성하는 시간보다 테스트 코드 작성이 더 어렵고 시간이 많이 들었다.

  • django.test 모듈에서 TestCaseClient클래스를 import했다.

  • unittest.mock 모듈에서 MagicMockpatch를 import했다.

  • 카카오 API에서 보내주는 사용자 정보를 임의로 만들기위해 MockedResponse클래스를 작성했다.

  • 일부가 생략된 아래 테스트 코드를 되돌아보자.

from unittest.mock         import MagicMock, patch

class KakaoSignUpTest(TestCase):
    @patch("users.kakao.requests")
    def test_kakao_signup_new_user_success(self, mocked_requests):
        client = Client()

        class MockedResponse:
            def __init__(self, status_code):
                self.status_code = status_code

            def json(self):
                return {
                    "id" : "1204",
                    "kakao_account" : {
                        "profile" : {
                            "nickname" : "승준",
                            "thumbnail_image_url" : "http://k.kakaocdn.net/img_110x110.jpg"
                        },
                        "email_needs_agreement" : False,
                        "email" : "dddsfa@sample.com",
                        "age_range_needs_agreement" : False,
                        "age_range" : "20-30"
                    }
                }

        mocked_requests.post = MagicMock(return_value = MockedResponse(200))
        headers              = {"Authorization" : "fake_kakao_token"}
        response             = client.post("/users/account/kakao", **headers)
        self.assertEqual(response.status_code, 200)

        access_token = response.json()['access_token']
        payload      = jwt.decode(access_token, SECRET_KEY, algorithms="HS256")
        kakao_id     = User.objects.get(id=payload['id']).kakao_id

        self.assertEqual(kakao_id, "1204")
  • 배운점

    • @patch() : 외부 서비스에 의존하는 코드에 대한 테스트를 작성한데 사용된다.

      위 코드에서는 users.kakao.requests를 MagicMock 인스턴스로 대체하는 역할을 한다.


4. 느낀점

  • 카카오 소셜 API 사용법을 알아볼때 프론트엔드가 구현하는 부분부터 먼저 따라서 해보았다. 덕분에 전체적인 흐름을 알수있었고 그안에서 백엔드가 어떤 역할을 해야하는지 쉽게 파악이되었다. 구체적인 구현 방법을 알아보기 전에 공식문서를 읽어보며 큰 그림을 그려보고 시작하는 것이 큰 도움이 되었다.

  • Django에서 클래스를 많이 작성하고 사용했지만 필요한 경우 스스로 설계하고 만들어내는 역량이 필요할 것 같다. 그저 원래하던거니까 그대로 코드를 작성했던게 아닐까 반성이 된다.
    유지보수성, 확장성을 위해선 프로젝트 설계와 클래스 활용에 대한 더 깊은 이해와 공부를 필요로한다. OOP에 대해 공부하면서도 실제 코드에 적용할 수 있도록 노력해야겠다.

profile
개발자가 될 팔자

0개의 댓글