오늘도 개발

[2차 프로젝트 내일의 집] 카카오 api로 SNS 로그인/회원가입 구현하기 본문

TIL & 프로젝트 회고

[2차 프로젝트 내일의 집] 카카오 api로 SNS 로그인/회원가입 구현하기

Sueeeeeee 2022. 8. 3. 11:31

2차 프로젝트는 오늘의 집 클론코딩이다.

카카오 로그인 구현을 맡아 진행했다.

 

로그인에 필요한 것 

sue라는 카카오 유저가 카카오 로그인으로 내일의 집을 이용하려 한다고 가정한다.

 

1) 인가 코드

카카오에 있는 sue의 정보에 내일의 집이 접근해도 된다고 sue에게 허락 받았다는 증거.

내일의 집 프론트가 카카오에게 인가 코드를 요청하면, 카카오가 내일의 집 백엔드에게 인가 코드를 응답한다.

 

2) 카카오 토큰

sue가 카카오에 로그인했다는 증거.

내일의 집 백엔드가 카카오에게 인가 코드를 보여주면서 요청하면, 카카오가 내일의 집 백엔드에게 카카오 토큰을 응답한다.

내일의 집 백엔드는 카카오 토큰을 사용하여 카카오에 sue의 이메일을 달라고 요청하는 등 다양한 일을 할 수 있다.

 

3) jwt 토큰

sue가 내일의 집에 로그인했다는 증거. 

sue가 로그인을 완료하면 내일의 집 백엔드가 발급하는 토큰이다. 

 

0. 준비

1) [내 애플리케이션 등록  => REST API 키 발급됨.

2) [내 애플리케이션 > 앱 설정 > 플랫폼]에서 플랫폼 등록(Web)

3) [내 애플리케이션 > 제품 설정 > 카카오 로그인]에서

   - 활성화 설정 ON

   - redirect URI 등록(인가 코드 받을 백엔드 URI)

 

1. 인가 코드 발급  

프로젝트에서 사용한 방식

1) 유저가 로그인 버튼 클릭

2) 내일의 집 프론트에서 카카오 서버로 인가 코드 요청

- 카카오 인가 코드 발급 api 엔드포인트 (상세정보 click)

https://kauth.kakao.com/oauth/authorize

3) 카카오가 유저에게 카카오톡 로그인 화면 띄움.

로그인 후 유저가 카카오에게 '내일의 집은 내 정보를 써도 된다'고 알려줌.   

출처 : kakao developers - 카카오 로그인 REST API

4) 카카오가 미리 등록된 redirect URI로 인가 코드를 응답함. 내일의 집 백엔드가 응답 받음.

(내 애플리케이션 > 앱 설정)

 

프론트 연결 전 테스트 해본 방식

프론트인 척 하고 포스트맨으로 카카오에 인가 코드를 요청했더니 3번 과정을 진행할 수 없었다.

응답으로 html 코드가 오는데 카카오톡 로그인 화면의 html 코드인 것 같았다.

이 상태로는 카카오에 로그인 할 수도 없고 동의 항목을 누를 수도 없었다.

 

kakao developers의 dev talk 질문 답변을 보니 카카오에서 이렇게 만들었기 때문에 안 되는 게 당연했다.

그래서 요청을 포스트맨으로 보내지 않고 웹브라우저 주소창으로 보냈더니

로그인, 사용자 동의를 완료하고 redirect URI로 인가 코드를 보낼 수 있었다.  

 

다음 url을 웹브라우저 주소창에 입력하면 인가 코드가 잘 오는지 테스트할 수 있다.

client_id에는 REST API 키를, redirect_uri에는 응답을 보낼 uri를 넣으면 된다.

https://kauth.kakao.com/oauth/authorize?client_id={client_id}&redirect_uri={redirect_uri}&response_type=code

 

2. 인가 코드로 카카오 토큰 발급 

1) 백엔드가 인가 코드를 받음

code = request.GET.get('code', None)

2) 백엔드가 카카오에 토큰 발급 요청

- 요청 body에 인가 코드를 넣어 보낸다.

- 카카오 토큰 발급 api 엔드포인트 (상세정보 click)

https://kauth.kakao.com/oauth/token

- django에서 REST api를 호출할 때는 requests 모듈을 사용하면 된다.

# 요청 바디에 넣을 내용
data = {
    'grant_type' : 'authorization_code',
    'client_id' : settings.KAKAO_REST_API_KEY,
    'redirect_uri' : 'http://localhost:8000/users/login',
    'code' : code
}

# 1) data를 넣어 requests.post(..)로 요청
# 2) requests.post(..) 요청으로 받은 응답을 token_response에 저장
# 3) 응답을 json 형식으로 변환
token_response = requests.post("https://kauth.kakao.com/oauth/token", data=data).json()
access_token = token_response.get('access_token')

 

3. 카카오 토큰으로 카카오 유저 정보 가져오기

1) 백엔드가 카카오에 유저 정보 요청

- 유저 정보 가져오는 api 엔드포인트(상세정보 click)

https://kapi.kakao.com/v2/user/me

- 요청 header에 카카오에서 발급한 access_token을 넣어 보낸다. 

headers = {
    'Authorization': f'Bearer {access_token}'
}

# 1) headers에 header정보를 넣어 requests.post(..)로 요청
# 2) requests.post(..) 요청으로 받은 응답을 user_data_response에 저장
# 3) 응답을 json 형식으로 변환
user_data_response = requests.post("https://kapi.kakao.com/v2/user/me", headers=headers).json()

- 응답은 이런 식으로 온다.

# 모든 항목에 동의하기 누른 경우
{
    # 카카오에서 고유한 유저 아이디
    'id': 123, 
    'connected_at': '2022-08-03T05:29:09Z', 
    'properties': {
        'nickname': 'sue', 
        'profile_image': ...,
        'thumbnail_image': ...
        }, 
    'kakao_account': {
        'profile_nickname_needs_agreement': False, 
        'profile_image_needs_agreement': False, 
        'profile': {
            'nickname': 'sue', 
            'thumbnail_image_url': '...', 
            'profile_image_url': '...', 
            'is_default_image': False
            }, 
        'has_email': True, 
        'email_needs_agreement': False, 
        'is_email_valid': True, 
        'is_email_verified': True, 
        'email': '...'
        }
    }
    
# 선택 항목 동의 누르지 않은 경우
{
    'id': 123, 
    'connected_at': '2022-08-06T04:40:24Z', 
    'kakao_account': {
        'profile_nickname_needs_agreement': False, 
        'profile_image_needs_agreement': False, 
        'has_email': True, 
        'email_needs_agreement': True
        }
}

 

4.  jwt 발급

- 내일의 집 백엔드가 카카오 토큰 열어보고, 토큰의 유저 정보가 내일의 집 DB에 있으면 바로 프론트에게 jwt 응답

- 내일의 집 DB에 없으면 DB에 추가 후 프론트에 jwt 응답

 

5.  총정리

class LoginView(View):
    def get(self, request):
        try:
            # 1. 프론트에서 보내준 인가코드 받기 
            code = request.META.get('HTTP_AUTHORIZATION')
            # 인가 코드 없으면 오류 반환
            if not code:
                return JsonResponse({"message" : "INVALID_AUTHORIZATION_CODE"}, status=401)
			
            # 액세스 토큰 요청 바디에 넣을 데이터 준비
            data = {
                'grant_type' : 'authorization_code',
                'client_id' : settings.KAKAO_REST_API_KEY,
                'redirect_uri' : settings.REDIRECT_URI,
                'code' : code
            }
            # 2. 카카오에 액세스 토큰 요청
            token_response = requests.post("https://kauth.kakao.com/oauth/token", data=data).json()
            access_token   = token_response.get('access_token')
          	
            # 액세스 토큰 없으면 오류 반환
            if not access_token:
                return JsonResponse({"message" : "INVALID_ACCESS_TOKEN"}, status=401)
			
            # 3. 유저 정보 요청 헤더에 넣을 데이터 준비
            headers = {
                'Authorization': f'Bearer {access_token}'
            }
            # 카카오에 유저 정보 요청
            user_data_response = requests.post("https://kapi.kakao.com/v2/user/me", headers=headers).json()
            kakao_id = user_data_response.get('id')
            
            # 유저 정보 없으면 오류 반환
            if not kakao_id:
                return JsonResponse({"message" : "INVALID_KAKAO_ID"}, status=401)
			
            # 유저 정보 가져오기
            kakao_account = user_data_response['kakao_account']
            email         = kakao_account.get('email')
            nickname      = None 
            profile_image = None
            profile       = kakao_account.get('profile')

            if profile:
                nickname      = profile.get('nickname')
                profile_image = profile.get('profile_image_url')
            
            # DB 확인해보고 존재하는 유저면 jwt 발급, 아니면 회원가입 후 jwt 발급
            user, is_created = User.objects.get_or_create(
                kakao_id = kakao_id,
                defaults = {
                    "email"        : email,
                    "nickname"     : nickname,
                    "profile_image": profile_image
                }
            )
            nhouse_token = jwt.encode({"user_id" : user.id}, settings.SECRET_KEY, settings.ALGORITHM)

            if not is_created:
                if not user.email == email:
                    user.email = email 
                    user.save()
                if not user.profile_image == profile_image:
                    user.profile_image = profile_image
                    user.save()
                if not user.nickname == nickname:
                    user.nickname = nickname
                return JsonResponse({"message" : "LOGIN_SUCCESS", "access_token" : nhouse_token}, status=200)

            return JsonResponse({"message" : "SIGNUP_SUCCESS", "access_token" : nhouse_token}, status=201)  

        except KeyError:
            return JsonResponse({"message":"KEY_ERROR"}, status=400)

 

 

참고

Kakao Developers - 카카오 로그인