🚧 해당 글은 Python으로 OAuth로 구글 소셜로그인을 위한 OpenAPI를 단순 호출하는 과정과 코드를 담았을 뿐, OAuth에 관한 내용은 이전 글을 참고해주세요!
accounts.models.py
에 User 모델 정의
AbstractBaseUser
처럼 password가 없거나 필수가 아닌 모델GCP console에서 프로젝트 생성 - 이름은 아무거나
API 및 서비스 > OAuth 동의 화면 > “외부” 선택 후 만들기
앱 정보 설정 - 앱 이름 맘대로 + 본인 이메일 입력
일단 승인된 도메인 일단 없이 계속 진행
→ 로그인 테스트할 사용자 이메일 입력하기
API 및 서비스 > 사용자 인증 정보 만들기 > OAuth 클라이언트 ID
secrets.json
{
"SECRET_KEY": ...,
"GOOGLE_CLIENT_ID": ...,
"GOOGLE_SECRET": ...,
"GOOGLE_SCOPE_USERINFO": "https://www.googleapis.com/auth/userinfo.email",
"GOOGLE_CALLBACK_URI": "http://localhost:8000/account/google/callback/",
"GOOGLE_REDIRECT": "https://accounts.google.com/o/oauth2/v2/auth"
}
📎 scope(가져올 수 있는 데이터 목록)는 Google API의 OAuth 2.0 범위에서 확인 가능
다른 어떤 방식으로라도 OAuth 구조만 지켜진다면
적절하게 OpenAPI 호출만 한다면어떤 방식으로 구현해도 상관이 없습니다.당연하게도 실습의 serializer, 함수/view 등을 꼭 따라야하는 법은 어디에도 없습니당
django-allauth
패키지 설치하기 → 기본 로그인 기능과 소셜로그인 테이블 지원
pip install django-allauth
pip install requests # API 호출
config/settings.py
에 다음 내용 추가해주기
THIRD_PARTY_APPS = [
# ...
"allauth",
"allauth.account",
"allauth.socialaccount",
"allauth.socialaccount.providers.google",
# "allauth.socialaccount.providers.{제공_업체}" 찾아서 사용 가능
]
# ...
MIDDLEWARE = [
# ...
"allauth.account.middleware.AccountMiddleware",
]
# ...
ACCOUNT_EMAIL_REQUIRED = True # email 필드 사용 o
ACCOUNT_USERNAME_REQUIRED = True # username 필드 사용 o
ACCOUNT_AUTHENTICATION_METHOD = "email"
django-allauth
테이블 생성
python manage.py makemigrations - 암것도 없다 그럼
python manage.py migrate - 변경사항 와다다 뜸
THIRD_PARTY_APPS 영향으로 생성된 테이블 목록
account_emailaddress
account_emailconfirmation
socialaccount_socialaccount
socialaccount_socialapp
socialaccount_socialtoken
아래 테이블을 알잘딱깔센 활용 꼭 안 써도 돌아가긴 해서 알잘딱깔센..
(옵션) config/urls.py
에 엔드포인트 추가
allauth가 기본 로그인 기능도 지원한다고 했죠.
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path("admin/", admin.site.urls),
path("post/", include("posts.urls")),
path("account/", include("accounts.urls")),
path("account/", include("allauth.urls")),
]
Note that you do not necessarily need the URLs provided by
django.contrib.auth.urls
. Instead of the URLslogin
,logout
, andpassword_change
(among others), you can use the URLs provided byallauth
:account_login
,account_logout
,account_set_password
…
대충 로그인 관련 API 지원한다는 소리입니다.
accounts/urls.py
에 엔드포인트 추가 → 이제부터 두 함수를 채워갈 겁니다.
urlpatterns = [
# ...
path("google/login/", google_login, name="google_login"),
path("google/callback/", google_callback, name="google_callback"),
]
accounts/views.py
에 구현 추가
### --------
# secrets.json 읽어오기
from pathlib import Path
import os, json
from django.core.exceptions import ImproperlyConfigured
BASE_DIR = Path(__file__).resolve().parent.parent
secret_file = os.path.join(BASE_DIR, "secrets.json")
with open(secret_file) as f:
secrets = json.loads(f.read())
def get_secret(setting, secrets=secrets):
try:
return secrets[setting]
except KeyError:
error_msg = "Set the {} environment variable".format(setting)
raise ImproperlyConfigured(error_msg)
GOOGLE_SCOPE_USERINFO = get_secret("GOOGLE_SCOPE_USERINFO")
GOOGLE_REDIRECT = get_secret("GOOGLE_REDIRECT")
GOOGLE_CALLBACK_URI = get_secret("GOOGLE_CALLBACK_URI")
GOOGLE_CLIENT_ID = get_secret("GOOGLE_CLIENT_ID")
GOOGLE_SECRET = get_secret("GOOGLE_SECRET")
### --------
from django.shortcuts import redirect
from json import JSONDecodeError
from django.http import JsonResponse
from allauth.socialaccount.models import SocialAccount
import requests
# 로그인 페이지 연결
def google_login(request):
scope = GOOGLE_SCOPE_USERINFO # + "https://www.googleapis.com/auth/drive.readonly" 등 scope 설정 후 자율적으로 추가
return redirect(f"{GOOGLE_REDIRECT}?client_id={GOOGLE_CLIENT_ID}&response_type=code&redirect_uri={GOOGLE_CALLBACK_URI}&scope={scope}")
# 인가 코드를 받아 로그인 처리
def google_callback(request):
code = request.GET.get("code") # Query String 으로 넘어옴
token_req = requests.post(f"https://oauth2.googleapis.com/token?client_id={GOOGLE_CLIENT_ID}&client_secret={GOOGLE_SECRET}&code={code}&grant_type=authorization_code&redirect_uri={GOOGLE_CALLBACK_URI}")
token_req_json = token_req.json()
error = token_req_json.get("error")
if error is not None:
raise JSONDecodeError(error)
google_access_token = token_req_json.get('access_token')
email_response = requests.get(f"https://www.googleapis.com/oauth2/v1/tokeninfo?access_token={google_access_token}")
res_status = email_response.status_code
if res_status != 200:
return JsonResponse({"status": 400,"message": "Bad Request"}, status=status.HTTP_400_BAD_REQUEST)
email_res_json = email_response.json()
email = email_res_json.get('email')
try:
user = User.objects.get(email=email)
if user is None:
return JsonResponse({"status": 404,"message": "User Account Not Exists"}, status=status.HTTP_404_NOT_FOUND)
# 소셜로그인 계정 유무 확인
social_user = SocialAccount.objects.get(user=user)
if social_user.provider != "google":
return JsonResponse({"status": 400,"message": "User Account Not Exists"}, status=status.HTTP_400_BAD_REQUEST)
token = RefreshToken.for_user(user)
refresh_token = str(token)
access_token = str(token.access_token)
res = JsonResponse(
{
"user": {
"id": user.id,
"email": user.email,
},
"message": "login success",
"token": {
"access_token": access_token,
"refresh_token": refresh_token,
},
},
status=status.HTTP_200_OK,
)
return res
except:
return JsonResponse(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
AssertionError: .accepted_renderer not set on Response
Response로 하면 위와 같은 에러가 발생합니다. 지금 우리는 엔드포인트에 APIView가 아니라 함수를 연결해서 실행해주고 있어요. Response - rest_framework
패키지에서 지원하는 함수 ⇒ 일반 함수에서는 사용이 불가능 ! 그래서 그냥 django
패키지에서 기본 지원하는 JsonResponse를 썼습니다 !localhost:8000/account/google/login/
으로 이동해서
앞에서 설정한 테스트 사용자의 계정으로 로그인하면
기존 User 데이터에 해당 구글 계정이 email로 등록된 사용자
404 Error
발생테스트를 원한다면 admin에서 데이터를 추가해서 테스트해주세요.
그럼 구글 로그인 완료 ~ !
django-allauth
를 설치하면 아래 테이블이 추가됩니다.
account_emailaddress
account_emailconfirmation
socialaccount_socialaccount
socialaccount_socialapp
socialaccount_socialtoken
소셜로그인 제공업체 검증과 사용자 검증에 활용 추천드립니다!