(장고) 라이브러리 없이 카카오 로그인 직접 구현하기
장고에서 소셜 계정 로그인을 구현할 때, 많은 개발자는 django-allauth 같은 라이브러리를 사용한다. 이 방식은 확실히 편리하고, 웹 브라우저 환경에서는 대부분의 소셜 로그인을 잘 처리할 수 있다.
하지만 모바일 환경에서는 다른 문제가 발생한다. 웹 브라우저에서 카카오 로그인을 시도하면 카카오 로그인 페이지로 리다이렉트된다. 이렇게 되면 사용자는 잘 사용하지 않는 카카오 계정과 비밀번호를 입력해야 한다.
반면, 카카오 앱을 통하면 아이디와 비밀번호를 입력할 필요 없이 바로 인증이 된다. 이를 위해 카카오의 Kakao.Auth.authorize 메서드를 활용해야 한다.
메서드를 활용해 모바일과 웹을 모두 유연하게 지원하는 카카오 로그인을 구현하려면 외부 라이브러리 없이 직접 구현해야 한다고 판단했다.
여기서는 Django 프로젝트에서 카카오 로그인 기능을 처음부터 끝까지 직접 구현하며, 모바일에서도 최적의 사용자 경험을 제공하는 방법을 설명한다. 하나씩 정리해 보자.
카카오 개발자 사이트 설정
가장 먼저, 카카오 개발자 사이트에서 카카오 로그인 기능을 켜고 url을 알맞게 설정한다.
1. 카카오 개발자 사이트에 로그인한다.
2. 내 애플리케이션에 새 애플리케이션을 등록하거나 기존 앱을 선택한다.
3. 왼쪽에서 카카오 로그인 메뉴를 선택하여 활성화를 On으로 설정한다.
4. Redirect URI 설정에서 다음 URI를 추가한다:
http://127.0.0.1:8000/oauth
http://127.0.0.1:8000/accounts/kakao/login/callback
https://your_site.com/oauth
https://your_site.com/accounts/kakao/login/callback
여기에 등록된 uri는 로그인 요청 시 작성하는 uri와 반드시 똑같아야 한다. '/' 하나라도 차이가 난다면 로그인 요청 시 에러가 발생한다.
참고로 여기서는 서브 도메인을 입력하지 않지만, view에서 redirect uri를 입력할 땐 서브 도메인도 포함해야 인증 코드를 받을 수 있다. 자세한 내용은 아래에서 다시 언급하겠다.
5. 다시 왼쪽 메뉴에서 앱 키 페이지로 이동한 다음, REST API 키를 복사하고 Django settings.py에 다음과 같이 추가한다:
# settings.py
KAKAO_REST_API_KEY = 'your_kakao_rest_api_key'
key는 view에서 직접 입력해도 되지만, settings.py에 따로 입력하면 다른 소셜의 key들을 쉽게 관리할 수 있고 다른 프로젝트에 적용하기 쉽다. 그리고 이 값들을 서버의 환경변수에 저장하면 보안을 좀 더 강화할 수 있다.
카카오 로그인 버튼 추가
로그인 템플릿에서 카카오 로그인 버튼을 추가한다:
<p class="text-center text-secondary my-3">또는</p> <div class="d-flex justify-content-center mb-5"> <div class="btn btn-sm w-100 p-0" style="background-color:#FEE500; border-radius: 5px"> <button class="p-0" type="submit" style="border: 0; background: transparent" onclick="loginWithKakao()"> <img src="{% static 'img/kakao_login_button.png' %}" height="38px" /> </button> </div> </div>
로그인 버튼은 직접 디자인했다. 이미지 소스나 이와 관련된 가이드는 카카오 디자인 가이드 페이지를 참고한다.
JavaScript SDK 초기화 및 로그인 처리
JavaScript SDK를 초기화하고, 카카오 로그인 처리를 위한 코드를 추가한다:
{% block script %}
<script src="https://t1.kakaocdn.net/kakao_js_sdk/2.7.2/kakao.min.js" integrity="wejflehav83jnj3f" crossorigin="anonymous"></script>
<script>
if (!Kakao.isInitialized()) {
Kakao.init('your_kakao_javascript_key'); // SDK를 초기화하기 (인자에는 사용하려는 앱의 JavaScript 키 입력)
}
</script>
<script>
const redirectUri = '{% if debug %}http://127.0.0.1:8000/accounts/kakao/login/callback{% else %}https://www.your_site.com/accounts/kakao/login/callback{% endif %}';
function loginWithKakao() {
Kakao.Auth.authorize({
redirectUri: redirectUri,
throughTalk: true,
});
}
</script>
{% endblock %}
이 코드를 간략하게 살펴보자면 우선 자바스크립트 SDK를 이 페이지에서 가져온다. 카카오 버전이 다를 수 있으니 반드시 위 코드가 아닌 카카오 개발자 사이트에 있는 SDK 태그를 사용한다.
그 다음 카카오 JavaScript SDK를 초기화한다. 초기화하지 않는 경우, 인가 코드 반복 요청 현상이 발생할 수도 있는데 이 경우 코드를 받을 수 없어 로그인이 되지 않는다. 로그인을 시도할 땐 초기화하는 게 좋다.
그 다음 카카오 로그인 인증 함수다. 사용자가 카카오 로그인 버튼을 클릭했을 때 이 함수가 실행된다. redirect uri, throughTalk 등 여러 옵션을 설정할 수 있는데 자세한 내용은 이 페이지를 참고한다.
커스텀 백엔드
콜백 함수를 만들기 전에 커스텀 백엔드부터 작성해야 한다. 소셜 계정의 경우, 비밀번호가 없어 다른 방식으로 사용자를 인증해야 한다.
accounts 폴더 안에 backends.py 파일을 생성한다. 그리고 아래 코드를 작성한다.
from django.contrib.auth.backends import BaseBackend
from .models import CustomUser
class CustomBackend(BaseBackend):
def authenticate(self, request, email=None, identifier=None, password=None, **kwargs):
try:
# 이메일로 로그인하는 경우
if email:
user = CustomUser.objects.get(email=email, provider='email')
else:
# identifier로 로그인하는 경우
user = CustomUser.objects.get(identifier=identifier)
return user
# 비밀번호가 일치하는지 확인
if user.check_password(password):
return user
except CustomUser.DoesNotExist:
return None
def get_user(self, user_id):
try:
return CustomUser.objects.get(pk=user_id)
except CustomUser.DoesNotExist:
return None
이 부분은 자신의 환경에 맞게 작성해야 한다. 나의 경우, 이메일로 로그인을 하지 않는다면 identifier로 사용자 정보를 가져왔다. identifier는 Custom User Model에 추가된 필드로 고유한 무작위 문자열이 저장되어 있다.
이메일로 로그인하면, 비밀번호를 확인한 다음 user 객체를 리턴하고 그 외 경우라면 identifier로 사용자 객체를 리턴한다.
그리고 설정 파일에 이 커스텀 백엔드를 추가해야 뷰에서 사용이 가능하다.
AUTHENTICATION_BACKENDS = [
'accounts.backends.CustomBackend', # 추가
'django.contrib.auth.backends.ModelBackend',
]
URL 패턴 추가
콜백 URL을 처리하기 위해 URL 패턴을 추가한다:
# accounts/urls.py
from .views import KakaoCallbackView
urlpatterns = [
# Kakao 소셜 로그인 콜백
path('kakao/login/callback', KakaoCallbackView.as_view()),
# 다른 URL 생략
]
콜백 뷰 구현
KakaoCallbackView는 카카오 인증 서버로부터의 콜백을 처리하는 핵심 뷰다.
우선 전체 코드다.
# accounts/views.py
from django.core.exceptions import PermissionDenied
import requests
from django.contrib.auth import login
class KakaoCallbackView(View):
def get(self, request):
code = request.GET.get('code')
if not code:
# 코드가 잘 넘어오지 않을 때를 위한 디버깅
print('Authorization code is missing.')
print(f"Received code: {code}")
raise PermissionDenied('Authorization code is missing.')
kakao_token_request_url = 'https://kauth.kakao.com/oauth/token'
headers = {"Content-type": "application/x-www-form-urlencoded"}
redirect_uri = 'https://www.your_site.com/accounts/kakao/login/callback' # www 붙여야 함.
if settings.DEBUG:
redirect_uri = 'http://127.0.0.1:8000/accounts/kakao/login/callback'
data = {
'grant_type': 'authorization_code',
'client_id': settings.KAKAO_REST_API_KEY,
'redirect_uri': redirect_uri,
'code': code,
}
token_response = requests.post(kakao_token_request_url, data=data, headers=headers) # 토근 요청
token_json = token_response.json() # json으로 변환
# 유효성 검증
error = token_json.get('error', None)
if error is not None:
print("========= after request token ========")
print('error:', token_json['error'])
print('description: ', token_json['error_description'])
raise PermissionDenied() # 권한 거부하기
# 토큰 요청
access_token = token_json.get('access_token')
if not access_token:
raise PermissionDenied()
# 받은 토큰으로 사용자 정보 요청
user_info_request = requests.get(
'https://kapi.kakao.com/v2/user/me', headers={'Authorization': f"Bearer {access_token}"},
)
user_info_json = user_info_request.json()
kakao_account = user_info_json.get('kakao_account')
# 데이터베이스에서 사용자 가져오기 또는 생성
user, created = CustomUser.objects.get_or_create(email=kakao_account['email'], provider='kakao')
# 신규 회원
if created or (user.privacy_consent == False or user.provision_consent == False):
user.is_active = True
user.save()
login(request, user, backend='accounts.backends.CustomBackend') # 로그인 처리
return HttpResponseRedirect('/')
가장 먼저 인가 코드를 받는다.
code = request.GET.get('code')
if not code:
print('Authorization code is missing.')
raise PermissionDenied('Authorization code is missing.')
카카오 인증 성공 시 code라는 인가 코드를 콜백 URL로 전달받는다. 인가 코드가 없으면 예외를 발생시킨다.
그 다음 카카오 서버에 토큰을 요청한다.
kakao_token_request_url = 'https://kauth.kakao.com/oauth/token'
headers = {"Content-type": "application/x-www-form-urlencoded"}
redirect_uri = 'https://your_site.com/accounts/kakao/login/callback'
if settings.DEBUG:
redirect_uri = 'http://127.0.0.1:8000/accounts/kakao/login/callback'
data = {
'grant_type': 'authorization_code',
'client_id': settings.KAKAO_REST_API_KEY,
'redirect_uri': redirect_uri,
'code': code,
}
token_response = requests.post(kakao_token_request_url, data=data, headers=headers)
token_json = token_response.json()
토큰 요청은 어렵지 않다. url를 만들어 post로 요청하면 된다. 응답으로 액세스 토큰을 발급받는다. 토큰 요청과 관련된 정보는 이 페이지를 참고한다.
그 다음으로 토큰 요청 시 발생하는 오류를 처리한다.
error = token_json.get('error')
if error:
print('error:', token_json['error'])
raise PermissionDenied()
응답에서 오류가 발생했을 경우, 예외를 발생시킨다.
예외에서는 print 함수로 에러를 출력한다. 그래야 나중에 문제가 발생하면 어느 부분이 잘못되었는지 구체적으로 알 수 있다.
그 다음, 액세스 토큰 추출 및 사용자 정보 요청한다.
access_token = token_json.get('access_token')
if not access_token:
raise PermissionDenied()
user_info_request = requests.get(
'https://kapi.kakao.com/v2/user/me', headers={'Authorization': f"Bearer {access_token}"}
)
user_info_json = user_info_request.json()
kakao_account = user_info_json.get('kakao_account')
액세스 토큰을 통해 사용자 정보를 요청한다.
사용자 데이터베이스 연동한다.
user, created = CustomUser.objects.get_or_create(email=kakao_account['email'], provider='kakao')
사용자를 데이터베이스에서 조회하거나, 없다면 새로 생성한다.
참고로 나의 경우, provider 필드로 로그인 수단을 저장했다.
if created or not user.is_active:
user.is_active = True
user.save()
login(request, user, backend='accounts.backends.CustomBackend')
return HttpResponseRedirect('/')
신규 사용자 처리 및 로그인할 차례다. 사용자가 신규라면 활성화를 한다음 로그인 함수를 통해 세션을 시작한다. 마지막으로 HttpResponseRedirect를 이용해 홈 화면으로 리다이렉트한다.
이제 테스트 서버를 열어 카카오 로그인 기능을 테스트한다. 모바일에서 테스트해서 카카오 앱이 연동되어 로그인이 되는지 확인한다.