JWT기반 인증을 해야하기 때문에 simplejwt 패키지 설치가 필요하며, 장고의 유저 기능들을 구현하기 위한 django_allauth 패키지와 소셜 로그인을 위한 dj-rest-auth 패키지를 설치한다.
pip install djangorestframework
pip install djangorestframework-simplejwt
pip install django-allauth
pip install dj-rest-auth
외부 노출을 방지하기 위해 django의 secret_key를 분리해준다.
secret.json
{
"SOCIAL_AUTH_GOOGLE_CLIENT_ID" : "google_client_id",
"SOCIAL_AUTH_GOOGLE_SECRET" : "google_secret_key",
"STATE" : "random_string",
"DATABASES" : {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": "db_name",
"USER": "db_user",
"PASSWORD": "db_password",
"HOST": "localhost",
"PORT" : "3306",
}}
}
settings.py
# 별도 파일로 관리중인 SECRET_KEY를 django에 연결
from pathlib import Path
import os
import json
import sys
BASE_DIR = Path(__file__).resolve().parent.parent
ROOT_DIR = os.path.dirname(BASE_DIR)
SECRET_BASE_FILE = os.path.join(BASE_DIR, 'secrets.json')
secrets = json.loads(open(SECRET_BASE_FILE).read())
for key, value in secrets.items():
setattr(sys.modules[__name__], key, value)
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.sites',
# my app
'users',
#django-rest-auth
'rest_framework',
'dj_rest_auth',
'dj_rest_auth.registration',
#django-allauth
'allauth',
'allauth.account',
'allauth.socialaccount',
]
# Custom UserModel
AUTH_USER_MODEL = 'users.User'
SITE_ID = 1
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.AllowAny',
),
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.SessionAuthentication',
'dj_rest_auth.jwt_auth.JWTCookieAuthentication',
),
}
# 커스텀한 유저 모델의 USERNAME_FIELD를 username -> email로 변경했으므로 ACCOUNT설정
ACCOUNT_USER_MODEL_USERNAME_FIELD = None
ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_UNIQUE_EMAIL = True
ACCOUNT_USERNAME_REQUIRED = False
ACCOUNT_AUTHENTICATION_METHOD = 'email'
ACCOUNT_EMAIL_VERIFICATION = 'none'
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
# JWT 환경 설정
from datetime import timedelta
REST_USE_JWT = True
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(hours=2),
'REFRESH_TOKEN_LIFETIME': timedelta(days=7),
'ROTATE_REFRESH_TOKENS': False,
'BLACKLIST_AFTER_ROTATION': True,
}
User 모델이 있는 users 앱에 settings에서 추가한 라이브러리들을 매핑한다.
urls.py
urlpatterns = [
path('admin/', admin.site.urls),
path('users/', include('users.urls')),
path('users/', include('dj_rest_auth.urls')),
path('users/', include('allauth.urls')),
]
setiings 설정이 끝나고 migration 후 admin 페이지의 site를 localhost:8000으로 추가
Social applications에서 소셜로그인을 제공할 플랫폼을 선택하고, 이름과 client_id, secret_key를 입력 후 이전에 추가한 localhost의 site를 선택하여 Social application을 추가해준다.
client_id, secret_key는 Google API 에서 앱 등록 후 사용자 인증 정보에서 Redirection URI를 설정하여 발급받을 수 있다.
urlpatterns = [
path('google/login', views.google_login, name='google_login'),
path('google/callback/', views.google_callback, name='google_callback'),
path('google/login/finish/', views.GoogleLogin.as_view(), name='google_login_todjango'),
]
google_login 실행 후 로그인 성공 시, Callback 함수로 Code 값 전달
받은 Code와 Google에 Access Token 요청
Access Token으로 Email 값을 Google에게 요청
전달받은 Email, Access Token, Code를 바탕으로 회원가입/로그인 진행
from users.models import User
from django.conf import settings
from django.http import JsonResponse
from dj_rest_auth.registration.views import SocialLoginView
from allauth.socialaccount.models import SocialAccount
from allauth.socialaccount.providers.google import views as google_view
from allauth.socialaccount.providers.oauth2.client import OAuth2Client
from rest_framework import status
from json.decoder import JSONDecodeError
import requests
state = getattr(settings, 'STATE')
BASE_URL = 'http://localhost:8000/'
GOOGLE_CALLBACK_URI = BASE_URL + 'users/google/callback/'
def google_login(request):
"""
Code Request
"""
scope = "https://www.googleapis.com/auth/userinfo.email"
client_id = getattr(settings, "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}")
def google_callback(request):
client_id = getattr(settings, "SOCIAL_AUTH_GOOGLE_CLIENT_ID")
client_secret = getattr(settings, "SOCIAL_AUTH_GOOGLE_SECRET")
code = request.GET.get('code')
"""
Access Token Request
"""
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}")
token_req_json = token_req.json()
error = token_req_json.get("error")
if error is not None:
raise JSONDecodeError(error)
access_token = token_req_json.get('access_token')
"""
Email Request
"""
email_req = requests.get(
f"https://www.googleapis.com/oauth2/v1/tokeninfo?access_token={access_token}")
email_req_status = email_req.status_code
if email_req_status != 200:
return JsonResponse({'err_msg': 'failed to get email'}, status=status.HTTP_400_BAD_REQUEST)
email_req_json = email_req.json()
email = email_req_json.get('email')
"""
Signup or Signin Request
"""
try:
user = User.objects.get(email=email)
# 기존에 가입된 유저의 Provider가 google이 아니면 에러 발생, 맞으면 로그인
# 다른 SNS로 가입된 유저
social_user = SocialAccount.objects.get(user=user)
if social_user is None:
return JsonResponse({'err_msg': 'email exists but not social user'}, status=status.HTTP_400_BAD_REQUEST)
if social_user.provider != 'google':
return JsonResponse({'err_msg': 'no matching social type'}, status=status.HTTP_400_BAD_REQUEST)
# 기존에 Google로 가입된 유저
data = {'access_token': access_token, 'code': code}
accept = requests.post(
f"{BASE_URL}users/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:
# 기존에 가입된 유저가 없으면 새로 가입
data = {'access_token': access_token, 'code': code}
accept = requests.post(
f"{BASE_URL}users/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)
class GoogleLogin(SocialLoginView):
adapter_class = google_view.GoogleOAuth2Adapter
callback_url = GOOGLE_CALLBACK_URI
client_class = OAuth2Client
위와 같이 코드를 작성했을 때 소셜 로그인으로 access token과 refresh token을 발급받을 수 있었고,
jwt에서 토큰 정보를 확인해보면 DB에 등록된 사용자임을 알 수 있었다.
하지만 소셜 로그인 과정이 종료된 후 access token, refresh token이 보여지는 페이지로 redirect 되는데 로그인 이후 token이 보여지지 않고 내 프로젝트의 main 페이지로 이동시켜주어야 한다.
소셜 로그인으로 사용자 인증 처리 후 어떻게 메인페이지로 이동시킬지 찾아봐야 할 것 같다.