오늘도 개발
[2차 프로젝트 내일의 집] 카카오 api로 SNS 로그인/회원가입 구현하기 본문
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) 카카오가 유저에게 카카오톡 로그인 화면 띄움.
로그인 후 유저가 카카오에게 '내일의 집은 내 정보를 써도 된다'고 알려줌.

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)
참고
'TIL & 프로젝트 회고' 카테고리의 다른 글
| [2차 프로젝트 내일의 집] SNS 로그인/회원가입 api에 Unit Test 해보기 (0) | 2022.08.05 |
|---|---|
| [2차 프로젝트 내일의 집] get_or_create 사용하기 (0) | 2022.08.03 |
| [1차 프로젝트 록차] 프로젝트 최종 회고 (0) | 2022.07.29 |
| [1차 프로젝트 록차] API 명세서 작성해보기 (0) | 2022.07.27 |
| [1차 프로젝트 록차] 1차 스프린트 회고 (0) | 2022.07.27 |