2025.7.24: dj-rest-auth로 소셜 로그인하기 (구글)

jiyongg·2025년 7월 24일

TIL: Today I Learned

목록 보기
7/30

요즘 웹사이트들은 소셜 로그인을 지원하지 않는 곳을 찾기가 힘든 것 같다. 그 중에서도 구글 로그인은 어느 정도 규모가 있는 사이트라면 지원하지 않는 사이트를 찾기가 더 힘들지 않을까?

그래서, 오늘은 소셜 로그인 중에서도 구글로 로그인하는 것을 한 번 실습해 보았다.

🚩 목표

목표는 간단하다. 실제로 내 구글 계정을 가지고 소셜 로그인을 해보는 것이다. 구글 로그인을 한 후, 인증 및 인가가 잘 동작하는지 테스트용 엔드포인트에 POST 요청을 날려볼 것이다.

0. 개요

이 프로젝트에서 구글 로그인의 흐름은 다음과 같다.

  • 구글 로그인 URL로 접속한다. 구글 로그인 및 필요한 권한을 허가받는다.
  • 이후 구글 로그인 URL의 Query Parameter로 담겨있던 redirect_uri로 리다이렉트된다.
  • 리다이렉트된 URI에 연결되어 있는 뷰(콜백 뷰)가 터미널에 code를 출력한다.
  • code를 복사하고, 요청 본문에 code를 넣어 코드를 처리하는 엔드포인트에 POST 요청을 날린다.
  • code에 해당하는 계정 정보를 바탕으로 계정을 생성 또는 조회하여 액세스 토큰이 발급된다.
  • Postman으로 테스트용 엔드포인트에 POST 요청을 보내본다. 인증 및 인가의 작동을 확인하기 위해 Authentication 없이도 보내보고, Bearer 토큰으로 액세스 토큰을 넣어서도 보내본다.

여기에서 왜 굳이 터미널에 code를 출력시키는지 궁금할 수도 있다. 사실 콜백 뷰에서 코드를 처리하는 엔드포인트로 POST 요청을 보내도 되겠지만, 나는 과정 하나하나를 끊어서 진행해보고 싶어서 일부러 code를 출력시키고 직접 엔드포인트로 요청을 보내보는 방법을 선택했다.

1. 🧱 프로젝트 생성 및 패키지 설치

일단, 어떤 실습이든 프로젝트를 만들어야 시작할 수 있다.

$ django-admin startproject sociallogin

대충 아무 이름의 프로젝트를 만들어준다.

$ cd sociallogin
$ django-admin startapp socialloginaccount

앱 이름도 적절히 잘 지어준다. 참고로 socialaccount를 쓰면 allaccount.socialaccount와 겹쳐서 앱이 unique하지 않다는 오류가 발생하므로 조심해야 한다.

그리고 패키지를 설치해주어야 하는데, 이번 프로젝트에 필요한 패키지들은 다음과 같다.

  • DRF (pip install django-rest-framework)
  • dj-rest-auth (dj_rest_auth): 로그인, 회원가입이 구현되어 있는 패키지이다.
    • 이 패키지를 설치할 때, pip install dj_rest_auth[with_social]로 설치하는 것을 추천한다.
  • django-allauth: (allauth): dj-rest-auth가 이 패키지를 필요로 한다.
  • Simple JWT (rest_framework_simplejwt): JWT를 사용할 수 있게 해준다.

2. ⚙️ 프로젝트 설정

앱을 생성하고, 패키지를 설치했다면, settings.py를 수정해 주어야 한다.

settings.py에서 네 부분을 바꿀 것이다.

INSTALLED_APPS

INSTALLED_APPS = [
    # ...
    'rest_framework',
    'rest_framework.authtoken',
    'dj_rest_auth',
    'allauth',
    'allauth.account',
    'dj_rest_auth.registration',
    'allauth.socialaccount.providers.google',
    'allauth.socialaccount',
    'socialloginaccount',
    'rest_framework_simplejwt',
]

MIDDLEWARE

MIDDLEWARE = [
    # ...
    'allauth.account.middleware.AccountMiddleware',
]

MIDDLEWAREallauth.account.middleware.AccountMiddleware를 추가해 준다. 추가하지 않으면 오류가 발생한다.

DRF(REST_FRAMEWORK) 설정

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    ]
}

기본 인증 클래스를 Simple JWT의 JWTAuthentication으로 바꿔준다.

dj-rest-auth(REST_AUTH) 설정

REST_AUTH = {
    'USE_JWT': True,
}

USE_JWTTrue로 설정하여 JWT를 사용하게 바꿔준다.

3. ⚒️ 뷰 작성

이제 socialloginaccount 앱의 views.py에 코드를 처리할 뷰, 콜백 뷰, 인증 및 인가 테스트를 위한 뷰를 작성한다.

코드를 처리하는 뷰

from allauth.socialaccount.providers.google.views import GoogleOAuth2Adapter
from allauth.socialaccount.providers.oauth2.client import OAuth2Client
from dj_rest_auth.registration.views import SocialLoginView

class GoogleLogin(SocialLoginView):
    adapter_class = GoogleOAuth2Adapter
    callback_url = "(콜백 URL)"
    client_class = OAuth2Client
  • 공식 문서에 있는 코드를 그대로 사용했다. 딱히 문서에 자세한 설명이 적혀있지 않았다.

콜백 뷰

#...
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
#...

class GoogleLoginCallback(APIView):
    def get(self, request, *args, **kwargs):
        print(f"code: {request.query_params.get('code')}")
        return Response(status=status.HTTP_200_OK)
  • 구글로 로그인하면, 콜백 URL에 GET 방식으로 요청을 보낸다.
  • 요청의 Query Parmeter들 중에는 code가 있는데, 이 code를 요청 본문에 넣어 코드를 처리하는 엔드포인트로 POST 요청을 보내면 된다.

테스트용 뷰

#...
from rest_framework.permissions import IsAuthenticatedOrReadOnly
from rest_framework_simplejwt.authentication import JWTAuthentication
#...

class TestView(APIView):
    authentication_classes = [JWTAuthentication]
    permission_classes = [IsAuthenticatedOrReadOnly]
    
    def post(self, request, *args, **kwargs):
        return Response("Ok")
  • 인증과 인가를 테스트하기 위해서 post 메소드를 만들고, 메소드는 그냥 Ok라는 내용의 응답을 보내게 만들었다.
  • 인증되어 있지 않은 사용자가 POST 요청을 보내면 응답으로 401이 반환되기 때문에, Ok라는 내용의 응답은 인증되어 있는 사용자만이 받을 수 있을 것이다.

4. 🔗 URL 연결

뷰를 작성했으니 엔드포인트와 뷰를 연결한다. 원래 앱 폴더의 urls.py에 작성한 후 루트 프로젝트에서 include하지만, 이번에는 그냥 루트 프로젝트의 urls.py에 바로 작성해보겠다.

from django.contrib import admin
from django.urls import path
from socialloginaccount.views import GoogleLogin, GoogleLoginCallback, TestView

urlpatterns = [
    path('admin/', admin.site.urls),
    path('login/google/', GoogleLogin.as_view(), name='login-google'),
    path('login/google/callback/', GoogleLoginCallback.as_view(), name='google-callback'),
    path('test/', TestView.as_view()),
]

5. 💽 migrate

설치된 앱들이 DB의 구조를 바꿨기 때문에 migrate 해준다.

$ python manage.py migrate

6. 🔑 OAuth 클라이언트 만들기

여기에서 잠깐 Django 프로젝트를 벗어나, 브라우저를 열고 구글 API 콘솔에 접속한다.

그러면 프로젝트가 없는 상태에서는 이렇게 프로젝트를 만들라고 한다.

프로젝트 만들기를 클릭해준다.

프로젝트 이름은 아무렇게 바꿔주고 만들기를 클릭한다. 나는 dj-rest-google이라고 정했다. 할당량이 11개 남았다고 나오는데, 실습하려고 만들어 본 게 있기 때문에, 남은 할당량은 다르게 나올 수도 있다.

Google Cloud 로고 옆에 아까 만든 프로젝트가 선택되어 있는 것을 확인하고 OAuth 동의 화면을 클릭한다.

시작하기를 클릭한다.

입력하라는 정보를 순서대로 모두 채우고 나서 만들기를 클릭한다.

OAuth 클라이언트 만들기를 클릭한다.

애플리케이션 유형으로 웹 애플리케이션을 선택한다.

이름은 아무렇게나 정하고, 승인된 리다이렉션 URI에 아까 콜백 뷰를 연결한 URI를 입력한다. 나는 로컬에서 테스트할 것이므로 http://127.0.0.1:8000/login/google/callback이라고 입력했다. 이후 만들기를 클릭한다.

그러면 OAuth 클라이언트 생성됨이라는 문구와 함께 창이 나타나는데, JSON 다운로드를 클릭한다.

그리고 다운로드된 JSON을 열어보면

{"web":{"client_id":"(클라이언트 ID)","auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://oauth2.googleapis.com/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","client_secret":"(클라이언트 보안 비밀번호)","redirect_uris":["http://127.0.0.1:8000/login/google/callback"]}}

이런 내용이 있는데, client_idclient_secret의 값을 추후에 사용해야 하므로 이 파일을 잘 보관해두자.

왼쪽의 클라이언트 위에 있는 대상을 클릭한다.

여기에서 테스트 사용자 밑의 + Add users를 클릭한 후, 본인의 구글 계정을 추가해준다.

7. 📝 소셜 앱 등록하기

소셜 앱을 등록해야 한다. 이 과정에 어드민 계정이 필요하므로 어드민 계정을 하나 만든다.

$ python manage.py createsuperuser

그 후 서버를 실행하고 어드민 엔드포인트로 들어가서 아까 만든 어드민 계정으로 로그인한다.

Social applications 옆에 있는 + Add를 클릭한다.

여기서 4개의 항목을 입력해준다.

  • Provider: Google로 선택한다. 지금은 provider가 하나뿐이라 자동으로 Google로 선택되어 있을 것이다.
  • Name: 자유롭게 정한다. 다만, django-allauth의 공식 문서에서는 Google을 추천한다.
  • Client id: 아까 받은 json 파일에서 client_id의 값을 복사 붙여넣기한다.
  • Secret key: 아까 받은 json 파일에서 client_secret의 값을 복사 붙여넣기한다.

다 입력했으면 SAVE를 클릭한다.

8. 🧪 테스트

구글 계정으로 로그인

이제 브라우저로 구글 로그인 URL에 접속해서 테스트를 진행한다.

https://accounts.google.com/o/oauth2/v2/auth?redirect_uri=<CALLBACK_URL_YOU_SET_ON_GOOGLE>&prompt=consent&response_type=code&client_id=<YOUR CLIENT ID>&scope=openid%20email%20profile&access_type=offline
  • redirect_uri: 아까 OAuth 클라이언트를 만들 때 승인된 리다이렉션 URI로 지정한 URI를 넣는다.
  • client_id: 아까 받은 json 파일에서 client_id의 값을 넣는다.

따로 대화형 인터프리터를 하나 열어준다.

>>> from urllib.parse import quote
>>> uri = "http://127.0.0.1:8000/login/google/callback"
>>> clid = "클라이언트 id"
>>> print(quote(uri))
http%3A//127.0.0.1%3A8000/login/google/callback
>>> print(quote(clid))
"이스케이프 처리된 클라이언트 id"
  • urllib.parse는 URL을 파싱하기 위한 모듈인데, 그 모듈의 quote 함수는 문자열에서 특수한 문자들을 %xx 형태의 이스케이프로 대체한 후 문자열을 반환한다.
  • 브라우저에 URL을 입력하기 위해, 문자열을 이스케이프해준다.

여기서 출력된 이스케이프 처리한 uriclid를 각각 redirect_uriclient_id에 넣어준다.

조금 더 나아가서, uriclid가 들어간 구글 로그인 URL 코드를 얻는 코드를 짤 수도 있겠지만, 생략하겠다.

그러면 아까 입력한 앱 이름(프로젝트 구성에서 입력한 앱 이름)이 나타나고 어디서 많이 보던 로그인 화면이 나타난다.

계속을 누른다.

그러면 아까 만들었던 콜백 뷰를 연결한 URI로 리다이렉트된다.

터미널을 보면 code가 출력되어 있을 것이다. 이것을 복사한다.

그 후 /login/google/로 이동하여 아까 복사한 코드를 Code에 붙여넣고, POST를 클릭한다.

에러가 났다!

OAuth2Client.__init__() got multiple values for argument 'scope_delimiter' 에러가 발생한다.

이 에러에 대한 깃헙 이슈가 있는데, 해당 이슈의 답변에 따르면 해결책은 dj_rest_auth/registration/serializers.py에 있는 SocialLoginSerializervalidate 메소드에서 130번째 줄의 scope를 지우거나 주석 처리하는 것이다.

문제를 해결한 후 다시 테스트 과정을 시도한다.

이렇게 된다면 성공한 것이다. 이제 액세스 토큰을 복사해둔다.

인증 및 인가 테스트

이제 Postman으로 테스트 엔드포인트에 POST를 보내서 테스트해볼 것이다.

먼저 Authentication 없이 보내본다.

401 Anauthorized가 뜨며 Ok가 출력되지 않는다.

이번에는 Authentication에 Bearer Token으로 아까의 액세스 토큰을 넣고 POST를 보내본다.

Ok라는 내용이 담긴 응답이 반환됐다. 성공했다!

소셜 계정 목록 보기

구글 계정 로그인이 되었고, 구글 계정 정보를 바탕으로 계정이 생성되었음을 어드민에서 확인할 수 있다.

Social accounts를 누르면, 이렇게 자신이 로그인한 계정의 이름과 UID 그리고 google이라는 Provider를 볼 수 있을 것이다.

9. 🔚 결론

이렇게 하여 DRF를 쓰는 백엔드 입장에서 구글 로그인을 하는 실습을 해 보았다.

이번에 멋사 중앙 해커톤을 참가하게 되어 새로운 프로젝트를 하게 되는데, 이 실습 내용을 활용할 수 있을지는 잘 모르겠다. 이번 실습은 정말 찍먹만 해본 것이라, 아직 실전에서 사용할 만큼 숙련도가 쌓이지는 않았다.

이번 주제는 실습하면서 정말 힘들었다. 맨땅에 헤딩하는 기분이었다. 처음에는 dj-rest-auth 공식 문서django-allauth 공식 문서를 참고하면서 진행했는데, 이 두 문서로는 흐름이 잡히지 않았다. 그러던 중, OAuth 2.0을 사용하여 Google API에 액세스하기 문서에서 흐름에 대한 아이디어를 얻었다. 사실 이 문서는 구글에서 액세스 토큰을 받아 Google API를 이용하는 방법이라서 코드를 받아 내 서버에서 액세스 토큰을 생성하는 것과는 살짝 다른 이야기지만, 결국 코드로 액세스 토큰을 생성한다는 맥락이 비슷했기 때문에 꽤나 참고가 되었다.

Django REST framework JWT Authentication — Social login — Login with Google에서는 콜백 뷰 부분의 로직을 참고했다.

또 날짜가 밀려서 24일의 TIL을 25일 거의 3시쯤에 끝냈다. 앞으로는 이러지 않도록 분량 조절을 잘 해보고 글도 좀 일찍 쓰기 시작해야겠다.. 블로그 글이 자정 이전에 작성되지 않으면, 미리 간단히 정리한 노션을 공유하고 있기에 벌금은 안 내고 있다. 그래도 블로그 글도 자정 이전에 끝내면 새벽엔 여유가 좀 생길텐데..

profile
그냥 쓰고 싶은 것 쓰는 개발(?) 블로그

0개의 댓글