소셜 로그인(구글) 학습

YOOJUN·2022년 12월 5일
1
post-thumbnail

1. pip install 설치

pip install dj-rest-auth
pip install django-allauth

  • dj-rest-auth : 회원가입, 로그인, 소셜로그인 기능 제공
    (비밀번호 찾기 / 리셋, 회원가입 시 이메일 인증 등 유저 관련 기능 커버)
  • django-allauth : rest-auth를 사용하기 위한 라이브러리
    (소셜로그인 기능을 사용하기 위해서 필요)

2. settings.py 설정


Installed apps = [
#설치한 라이브러리 목록에 추가
	...
	'allauth',
    'allauth.account',
    'allauth.socialaccount',
    'allauth.socialaccount.providers.google',
]

REST_USE_JWT = True

#계정 인증 방식 = 이메일
ACCOUNT_AUTHENTICATION_METHOD = 'email'

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    )
}
  • Installed apps에 있는 allauth.socialaccount.providers.소셜로그인에 google 이외에도 소셜로그인이 가능한 네이버나 카카오를 사용 가능하다.

  • 추가적으로 dj_rest_auth.registration.views.SocialLoginView를 사용하기 위해서는 REST_USE_JWT = True를 추가해야 한다.

3. 라이브러리 url매핑, 커스텀유저 생성

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/user/', include('allauth.urls')),
    path('api/user/', include('user.urls')),
 ]

4. admin페이지 설정

site

  • admin페이지에서 site 설정을 example.com에서 localhost:8000으로 변경

social application

  • social application 등록

5. 구글 client id & secret key 발급

  • 구글 클라우드에서 새 프로젝트 생성 이후에 앱 기본정보 입력 이후 사용자 인증 정보에서 call back url 지정

  • 받아온 키를 client id와 secret key를 admin의 social application 해당 필드에 넣어주고, env 파일에 넣는다.

#.env
SOCIAL_AUTH_GOOGLE_CLIENT_ID = 'client id'
SOCIAL_AUTH_GOOGLE_SECRET = 'secret key'
STATE = '아무 문자열'

6. urls.py 작성

# urls.py

urlpatterns = [
    # 구글 소셜로그인
    path('google/login', google_login, name='name 지정'),
    path('google/callback/', google_callback, name='name 지정'),
    path('google/login/finish/', GoogleLogin.as_view(), name='name 지정'),
]

7. views.py 작성

변수 설정

# views.py
import os

# 구글 소셜로그인 변수 설정
state = os.environ.get("STATE")
BASE_URL = 'http://localhost:8000/'
GOOGLE_CALLBACK_URI = BASE_URL + 'api/user/google/callback/'

구글 로그인 설정

# views.py
from django.shortcuts import redirect
import os

# 구글 로그인
def google_login(request):
    scope = "https://www.googleapis.com/auth/userinfo.email"
    client_id = os.environ.get("SOCIAL_AUTH_GOOGLE_CLIENT_ID")
    return redirect(f"https://accounts.google.com/o/oauth2/v2/auth?client_id={client_id}&response_type=code&redirect_uri={GOOGLE_CALLBACK_URI}&scope={scope}")

access token & 이메일 요청 -> 회원가입/로그인 & jwt 발급

# views.py
from json import JSONDecodeError
from django.http import JsonResponse
import requests
import os
from rest_framework import status
from .models import *
from allauth.socialaccount.models import SocialAccount

def google_callback(request):
    client_id = os.environ.get("SOCIAL_AUTH_GOOGLE_CLIENT_ID")
    client_secret = os.environ.get("SOCIAL_AUTH_GOOGLE_SECRET")
    code = request.GET.get('code')

    # 1. 받은 코드로 구글에 access token 요청
    token_req = requests.post(f"https://oauth2.googleapis.com/token?client_id={client_id}&client_secret={client_secret}&code={code}&grant_type=authorization_code&redirect_uri={GOOGLE_CALLBACK_URI}&state={state}")
    
    ### 1-1. json으로 변환 & 에러 부분 파싱
    token_req_json = token_req.json()
    error = token_req_json.get("error")

    ### 1-2. 에러 발생 시 종료
    if error is not None:
        raise JSONDecodeError(error)

    ### 1-3. 성공 시 access_token 가져오기
    access_token = token_req_json.get('access_token')

    # 2. 가져온 access_token으로 이메일값을 구글에 요청
    email_req = requests.get(f"https://www.googleapis.com/oauth2/v1/tokeninfo?access_token={access_token}")
    email_req_status = email_req.status_code

    ### 2-1. 에러 발생 시 400 에러 반환
    if email_req_status != 200:
        return JsonResponse({'err_msg': 'failed to get email'}, status=status.HTTP_400_BAD_REQUEST)
    
    ### 2-2. 성공 시 이메일 가져오기
    email_req_json = email_req.json()
    email = email_req_json.get('email')

    # return JsonResponse({'access': access_token, 'email':email})

    # 3. 전달받은 이메일, access_token, code를 바탕으로 회원가입/로그인
    try:
        # 전달받은 이메일로 등록된 유저가 있는지 탐색
        user = User.objects.get(email=email)

        # FK로 연결되어 있는 socialaccount 테이블에서 해당 이메일의 유저가 있는지 확인
        social_user = SocialAccount.objects.get(user=user)

        # 있는데 구글계정이 아니어도 에러
        if social_user.provider != 'google':
            return JsonResponse({'err_msg': 'no matching social type'}, status=status.HTTP_400_BAD_REQUEST)

        # 이미 Google로 제대로 가입된 유저 => 로그인 & 해당 우저의 jwt 발급
        data = {'access_token': access_token, 'code': code}
        accept = requests.post(f"{BASE_URL}api/user/google/login/finish/", data=data)
        accept_status = accept.status_code

        # 뭔가 중간에 문제가 생기면 에러
        if accept_status != 200:
            return JsonResponse({'err_msg': 'failed to signin'}, status=accept_status)

        accept_json = accept.json()
        accept_json.pop('user', None)
        return JsonResponse(accept_json)

    except User.DoesNotExist:
        # 전달받은 이메일로 기존에 가입된 유저가 아예 없으면 => 새로 회원가입 & 해당 유저의 jwt 발급
        data = {'access_token': access_token, 'code': code}
        accept = requests.post(f"{BASE_URL}api/user/google/login/finish/", data=data)
        accept_status = accept.status_code

        # 뭔가 중간에 문제가 생기면 에러
        if accept_status != 200:
            return JsonResponse({'err_msg': 'failed to signup'}, status=accept_status)

        accept_json = accept.json()
        accept_json.pop('user', None)
        return JsonResponse(accept_json)
        
	except SocialAccount.DoesNotExist:
    	# User는 있는데 SocialAccount가 없을 때 (=일반회원으로 가입된 이메일일때)
        return JsonResponse({'err_msg': 'email exists but not social user'}, status=status.HTTP_400_BAD_REQUEST)
  • 구글에 access token을 요청하고, 응답받은 access token으로 로그인된 사용자의 이메일 값을 구글에 요청한다.
  • 성공적으로 이메일값을 받았으면 해당 이메일과 access token, code를 바탕으로 회원가입과 로그인을 진행

구글 소셜로그인 view

# views.py
from dj_rest_auth.registration.views import SocialLoginView
from allauth.socialaccount.providers.oauth2.client import OAuth2Client
from allauth.socialaccount.providers.google import views as google_view

class GoogleLogin(SocialLoginView):
    adapter_class = google_view.GoogleOAuth2Adapter
    callback_url = GOOGLE_CALLBACK_URI
    client_class = OAuth2Client

profile
거북이 개발자

0개의 댓글