์ด ํ๋ก์ ํธ๋ Django๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ผ๋ฐ ํ์๊ฐ์ ๋ฐ ์์ ๋ก๊ทธ์ธ์ ๊ตฌํํ ์น ์ ํ๋ฆฌ์ผ์ด์ ์ ๋๋ค.
- ํ๋ก์ ํธ ์๊ฐ
- ์ค์น ๋ฐ ์คํ ๋ฐฉ๋ฒ
- ํ๋ก์ธ์ค ํ๋ฆ
- ์ฃผ์ ์ฝ๋ ์ค๋ช
- ์ฐธ๊ณ ์๋ฃ
MSES SERVICE๋ ์ผ๋ฐ ํ์๊ฐ์ ๋ฐ ๋ก๊ทธ์ธ๊ณผ OAuth2.0 ๊ธฐ๋ฐ ์์ ๋ก๊ทธ์ธ(์นด์นด์ค, ๋ค์ด๋ฒ)์ ์ ๊ณตํฉ๋๋ค.
-
์ผ๋ฐ ํ์๊ฐ์ ๋ฐ ๋ก๊ทธ์ธ
- ์ฌ์ฉ์ ์ ์ ๋ชจ๋ธ๊ณผ Django ์ธ์ฆ ์์คํ ์ ์ฌ์ฉํ ํ์๊ฐ์ ๋ฐ ๋ก๊ทธ์ธ
-
OAuth2.0 ๊ธฐ๋ฐ ์์ ํ์๊ฐ์ ๋ฐ ๋ก๊ทธ์ธ
- ์นด์นด์ค, ๋ค์ด๋ฒ ์์ ๋ก๊ทธ์ธ ์ ๊ณต
-
๋ก๊ทธ์์
- ๋ก๊ทธ์ธ๋ ์ฌ์ฉ์์ ์ธ์
์ ์ข
๋ฃํ๊ณ
/users/landing์ผ๋ก ์ด๋
- ๋ก๊ทธ์ธ๋ ์ฌ์ฉ์์ ์ธ์
์ ์ข
๋ฃํ๊ณ
-
์ด๋ฉ์ผ ์ค๋ณต ์ฒ๋ฆฌ
- ๋์ผ ์ด๋ฉ์ผ์ด ์ฌ๋ฌ ๊ณ์ ์์ ์ฌ์ฉ๋ ๊ฒฝ์ฐ ๊ณ์ ์ ํ๋๋ก ๋ฌถ์ด ๊ด๋ฆฌ
- ์์:
- ์นด์นด์ค ๊ณ์ ์ ์ด๋ฉ์ผ๊ณผ ๋ค์ด๋ฒ ๊ณ์ ์ ์ด๋ฉ์ผ์ด ๊ฐ์ผ๋ฉด, ๋จผ์ ์์ฑ๋ ๊ณ์ ์ผ๋ก ๋ก๊ทธ์ธ ์ฒ๋ฆฌ
- ์ผ๋ฐ ํ์๊ฐ์ ์, ์ด๋ฏธ ๋์ผํ ์ด๋ฉ์ผ๋ก ๋ฑ๋ก๋ ์์ ๊ณ์ ์ด ์์ผ๋ฉด ํ์๊ฐ์ ๋ถ๊ฐ
-
์ ๊ทผ ๊ถํ ๊ด๋ฆฌ
- ๋ก๊ทธ์ธ ์ํ์ ๋ฐ๋ผ ํน์ URL ์ ๊ทผ์ ์ ํ.
- ์์:
- ๋ก๊ทธ์์ ์ํ์์
/users/userinfo์ ๊ทผ ๋ถ๊ฐ โ/users/landing์ผ๋ก ์ด๋ - ๋ก๊ทธ์ธ ์ํ์์
/users/landing์ ๊ทผ ๋ถ๊ฐ โ/users/userinfo๋ก ์ด๋
- ๋ก๊ทธ์์ ์ํ์์
Visual Studio Code(VSCode)์ ํฐ๋ฏธ๋(bash)์ ๊ธฐ์ค์ผ๋ก ์์ฑํ์์ต๋๋ค.
-
๋ ํฌ์งํ ๋ฆฌ ํด๋ก :
git clone https://github.com/d2doo/mses.git cd ./mses/be/ -
๊ฐ์ํ๊ฒฝ ์ค์ ๋ฐ ์์กด์ฑ ์ค์น:
- ๊ฐ์ํ๊ฒฝ ์์ฑ:
python -m venv venv
- ๊ฐ์ํ๊ฒฝ ํ์ฑํ:
-
Windows:
source venv\Scripts\activate
ํน์
venv\Scripts\activate
-
- ์์กด์ฑ ์ค์น:
pip install -r requirements.txt
- ๊ฐ์ํ๊ฒฝ ์์ฑ:
-
ํ๊ฒฝ ๋ณ์ ์ค์ :
.envํ์ผ ์์น: ํ๋ก์ ํธ ๋ฃจํธ ๋๋ ํ ๋ฆฌ(์:manage.py์ ๋์ผํ ์์น)์.envํ์ผ ์์ฑ.envํ์ผ์ ๋ณด์ ์ ์ด๋ฉ์ผ์ ์ฒจ๋ถํ์์ต๋๋ค.
-
๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ง์ด๊ทธ๋ ์ด์ :
python manage.py makemigrations python manage.py migrate
-
์๋ฒ ์คํ:
python manage.py runserver
-
GET์์ฒญ:- ํด๋ผ์ด์ธํธ๊ฐ
/users/signup๊ฒฝ๋ก๋ก ์ ๊ทผ - Django๋ ๋น ํ์๊ฐ์ ํผ์ ๋ ๋๋งํ์ฌ ์ฌ์ฉ์์๊ฒ ์ ๊ณต
- ํด๋ผ์ด์ธํธ๊ฐ
-
POST์์ฒญ:- ์ฌ์ฉ์๊ฐ ์ ๋ ฅํ ๋ฐ์ดํฐ๋ฅผ ์๋ฒ๋ก ์ ์ก
- Django๋ ์ ๋ฌ๋ ๋ฐ์ดํฐ์ ์ ํจ์ฑ์ ๊ฒ์ฆ
- ์ ํจ์ฑ ๊ฒ์ฆ ์คํจ ์: ์ค๋ฅ ๋ฉ์์ง์ ํจ๊ป ํผ ์ฌ์ ์ก
- ์ ํจ์ฑ ๊ฒ์ฆ ์ฑ๊ณต ์: ์ ์ฌ์ฉ์ ๊ณ์ ์ ์์ฑ
- ๊ณ์ ์์ฑ ํ:
- ์ฌ์ฉ์ ์๋ ๋ก๊ทธ์ธ ์ฒ๋ฆฌ
- ์ฌ์ฉ์ ์ ๋ณด ํ์ด์ง(
/users/userinfo/)๋ก ์ด๋
-
์์ ๋ก๊ทธ์ธ ์์ฒญ:
- ์ฌ์ฉ์๊ฐ ์นด์นด์ค/๋ค์ด๋ฒ ๋ก๊ทธ์ธ ๋ฒํผ ํด๋ฆญ
- ํด๋น ์์ ํ๋ซํผ์ ์ธ์ฆ ํ์ด์ง๋ก ์ด๋
-
OAuth2.0 ์ธ์ฆ:
- ์ฌ์ฉ์๊ฐ ์์ ํ๋ซํผ์์ ์ธ์ฆ ์น์ธ
- ์ธ์ฆ์ด ์๋ฃ๋๋ฉด, Django
allauth๊ฐ ์ธ์ฆ ์ฝ๋๋ฅผ ๋ฐ์ ์ฝ๋ฐฑ URL๋ก ์ฒ๋ฆฌ
-
์ฌ์ฉ์ ๋ฐ์ดํฐ ์ฒ๋ฆฌ:
- Django
allauth๊ฐ ์์ ํ๋ซํผ์์ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ด - ๊ฐ์ ธ์จ ๋ฐ์ดํฐ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก
populate_user๋ฉ์๋๊ฐ ์คํ:- ์ฌ์ฉ์์ ๋ฐ์ดํฐ(๋๋ค์, ํ๋กํ ์ด๋ฏธ์ง, ์ด๋ฉ์ผ, ๋ณ๋ช , ์์ผ) ์ ์ฅ
- ๊ธฐ์กด ๋์ผ ์ด๋ฉ์ผ์ ๊ณ์ ์ด ์กด์ฌํ ๊ฒฝ์ฐ
- ๊ณ์ ์ฐ๊ฒฐ
- ๋ก๊ทธ์ธ ํ ์ฌ์ฉ์ ์ ๋ณด ํ์ด์ง(
/users/userinfo/)๋ก ์ด๋
- Django
-
๋ก๊ทธ์์ ์ํ:
/users/userinfo/์ ๊ทผ ์ ๋ก๊ทธ์ธ ํ์ด์ง(/users/landing/)๋ก ์ด๋
-
๋ก๊ทธ์ธ ์ํ:
/users/landing/์ ๊ทผ ์ ์ฌ์ฉ์ ์ ๋ณด ํ์ด์ง(/users/userinfo/)๋ก ์ด๋- ์ด๋ฏธ ๋ก๊ทธ์ธ๋ ์ํ์์ ๋ถํ์ํ ํ์๊ฐ์ /๋ก๊ทธ์ธ ํ์ด์ง ํ์ ๋ฐฉ์ง
-
์๋๋ฆฌ์ค 1:
- ์นด์นด์ค ๊ณ์ ๊ณผ ๋ค์ด๋ฒ ๊ณ์ ์ ์ด๋ฉ์ผ์ด ๋์ผํ ๊ฒฝ์ฐ
- ๋์ผ ์ด๋ฉ์ผ์ ์ฌ์ฉํ๋ ๋ค๋ฅธ ์์ ๊ณ์ ๋ก๊ทธ์ธ ์, ๊ธฐ์กด ๊ณ์ ๊ณผ ์ฐ๊ฒฐ
-
์๋๋ฆฌ์ค 2:
- ๋์ผ ์ด๋ฉ์ผ๋ก ์ผ๋ฐ ํ์๊ฐ์ ์๋
- ์ด๋ฏธ ์ฌ์ฉ ์ค์ธ ์ด๋ฉ์ผ๋ก๋ ์ผ๋ฐ ํ์๊ฐ์ ๋ถ๊ฐ
- ํ์๊ฐ์ /๋ก๊ทธ์ธ ์์ฒญ โ ๋ฐ์ดํฐ ๊ฒ์ฆ โ ๊ณ์ ์์ฑ/๋ก๊ทธ์ธ โ ๋ฆฌ๋ค์ด๋ ํธ
- OAuth2.0 โ ์ธ์ฆ ๋ฐ ์ฌ์ฉ์ ์ ๋ณด ์ฒ๋ฆฌ โ ๊ธฐ์กด ๊ณ์ ๊ณผ ์ฐ๊ฒฐ/์ ๊ณ์ ์์ฑ
- ๋ก๊ทธ์ธ ์ํ โ URL ์ ๊ทผ ์ ํ ๋ฐ ๋ณดํธ
Django์ allauth ํจํค์ง์ Django ๊ธฐ๋ณธ auth ์์คํ ์ ํ์ฉํ์ฌ ์ผ๋ฐ ํ์๊ฐ์ ๋ฐ OAuth2.0 ๊ธฐ๋ฐ ์์ ๋ก๊ทธ์ธ์ ๊ตฌํํ์ต๋๋ค.
- ์ผ๋ฐ ํ์๊ฐ์
: ์ฌ์ฉ์ ์ ์ ๋ชจ๋ธ(
User)์ ์ฌ์ฉํ์ฌ ์ฌ์ฉ์ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅ - ์์
๋ก๊ทธ์ธ:
django-allauth๋ฅผ ์ฌ์ฉํ์ฌ OAuth2.0 ๊ธฐ๋ฐ ๋ก๊ทธ์ธ(Kakao, Naver)์ ์ฒ๋ฆฌ - ์ผ๋ฐ ํ์๊ฐ์ ๊ณผ ์์ ๋ก๊ทธ์ธ์ ๋ ๋ฆฝ์ ์ผ๋ก ๊ด๋ฆฌ๋๋ฉฐ, ๋์ผ ์ด๋ฉ์ผ ์ฌ์ฉ ์ ๊ณ์ ์ ํตํฉ ๊ด๋ฆฌํ๋๋ก ์ค๊ณ
User ๋ชจ๋ธ์ Django์ ๊ธฐ๋ณธ ์ฌ์ฉ์ ๋ชจ๋ธ์ ํ์ฅํ ์ปค์คํ
์ฌ์ฉ์ ๋ชจ๋ธ์
๋๋ค.
์์
๋ก๊ทธ์ธ์์ ์ ๊ณตํ๋ ๋ฐ์ดํฐ(์ด๋ฉ์ผ, ๋๋ค์)๋ฅผ ์ ์ฅํ ์ ์๋๋ก ํ๋๋ฅผ ํ์ฅํ์์ต๋๋ค.
from django.contrib.auth.models import AbstractUser
from django.db import models
# User ์ปค์คํ
class User(AbstractUser):
email = models.EmailField(unique=True) # ์ด๋ฉ์ผ (unique ์ค์ )
nickname = models.CharField(max_length=50, blank=True) # ๋๋ค์ (์ ํ ํ๋)
def __str__(self):
return self.username- ์ผ๋ฐ ํ์๊ฐ์
์์ฒญ์ ์ฒ๋ฆฌํ๋
signup_viewํจ์๋ Django ๊ธฐ๋ณธ ํผ ์์คํ ์ ์ฌ์ฉํด ๊ตฌํ๋์์ต๋๋ค. - ์ ํจํ ๋ฐ์ดํฐ๋ฅผ ์ ๋ ฅ๋ฐ์ผ๋ฉด ์ ์ฌ์ฉ์ ๊ณ์ ์ ์์ฑํ๊ณ ์๋์ผ๋ก ๋ก๊ทธ์ธํ๊ฒ ๋ฉ๋๋ค.
django.shortcuts์render()๋ฉ์๋๋ฅผ ํ์ฉํ์ฌ http ์์ฒญ๊ณผ ์๋ต์ ์ฒ๋ฆฌํ์์ต๋๋ค.
from django.shortcuts import render, redirect
from django.contrib.auth import login as auth_login, get_backends
from .forms import SignupForm
def signup_view(request):
if request.method == 'POST':
form = SignupForm(request.POST)
# ์ ํจ์ฑ ๊ฒ์ฆ
if form.is_valid():
user = form.save() # ์ ํจํ ํผ ๋ฐ์ดํฐ๋ก ์ฌ์ฉ์ ์ ์ฅ
backend = get_backends()[0] # ์ธ์ฆ ๋ฐฑ์๋ ๊ฐ์ ธ์ค๊ธฐ
auth_login(request, user, backend=backend.__module__ + '.' + backend.__class__.__name__) # ๋ฐฑ์๋ ์ค์ ์ผ๋ก ๋ก๊ทธ์ธ
return redirect('/users/userinfo/') # ์ฌ์ฉ์ ์ ๋ณด ํ์ด์ง๋ก redirect
else:
form = SignupForm() # GET ์์ฒญ์ ๋น์ด์๋ ํผ ์ ๊ณต
context = {
'form': form
}
return render(request, 'signup.html', context)Django allauth ํจํค์ง๋ฅผ ํ์ฉํ์ฌ Kakao ๋ฐ Naver OAuth2.0 ํ์๊ฐ์
๊ณผ ๋ก๊ทธ์ธ์ ๊ตฌํํ์์ต๋๋ค.
- ์์
๋ก๊ทธ์ธ์ allauth์
SocialAccount๋ชจ๋ธ์์ ๊ด๋ฆฌ๋๋ฉฐ, ์ผ๋ฐ ํ์๊ฐ์ ๋ฐ์ดํฐ(User๋ชจ๋ธ)์ ๋ ๋ฆฝ์ ์ผ๋ก ์ ์ฅ๋ฉ๋๋ค. pre_social_login๋ฐpopulate_user๋ฉ์๋๋ฅผ ์ปค์คํฐ๋ง์ด์งํ์ฌ ๋ฐ์ดํฐ ํตํฉ ๋ฐ ์ฌ์ฉ์ ์์ฑ ๋ฐฉ์์ ์ ์ดํฉ๋๋ค.
- ์ฌ์ฉ์ ๋ชจ๋ธ ๋งคํ ๋ฐ ์์ฑ
Django allauth์ ๊ธฐ๋ณธ ์์
๊ณ์ ์ด๋ํฐ(DefaultSocialAccountAdapter)๋ฅผ ์ปค์คํฐ๋ง์ด์งํ์ฌ Kakao ๋ฐ Naver OAuth2.0 ๋ก๊ทธ์ธ์ ์ฒ๋ฆฌํ์์ต๋๋ค.
-
์์ ๋ก๊ทธ์ธ ๋ฐ์ดํฐ ์ฒ๋ฆฌ:
- ๊ฐ ์ ๊ณต์(Kakao, Naver)์ ๋ฐ์ดํฐ๋ฅผ
populate_user๋ฉ์๋์์ ๊ฐ๊ณตํ์ฌ ์ฌ์ฉ์ ๋ชจ๋ธ์ ๋งคํ - ์ถ์ ํด๋์ค์ ํฉํ ๋ฆฌ ํจํด์ ํ์ฉํด ์ ๊ณต์๋ณ ๋ฐ์ดํฐ ์ฒ๋ฆฌ ๋ก์ง์ ๋ถ๋ฆฌ
- ๊ฐ ์ ๊ณต์(Kakao, Naver)์ ๋ฐ์ดํฐ๋ฅผ
-
์ค๋ณต ์ด๋ฉ์ผ ์ฒ๋ฆฌ:
pre_social_login๋ฉ์๋์์ ์ค๋ณต๋ ์ด๋ฉ์ผ์ ๊ฐ์ง ๊ณ์ ์ ์ฐ๊ฒฐ
from abc import ABC, abstractmethod
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
from allauth.account.models import EmailAddress
from django.shortcuts import render
from django.contrib.auth import get_user_model
User = get_user_model()
# ์์
๋ก๊ทธ์ธ ๋ฐ์ดํฐ ์ฒ๋ฆฌํ๋ ์ถ์ํด๋์ค
class SocialLoginHandler(ABC):
@abstractmethod
def populate_user(self, user, extra_data):
pass
# ์นด์นด์ค ๋ฐ์ดํฐ ์ฒ๋ฆฌ
class KakaoLoginHandler(SocialLoginHandler):
def populate_user(self, user, extra_data):
kakao_account = extra_data.get('kakao_account', {})
profile = kakao_account.get('profile', {})
user.username = profile.get('nickname', '') or f"user_{extra_data.get('id', '')}"
user.first_name = kakao_account.get('name', '') or profile.get('nickname', '')
# ๋ค์ด๋ฒ ๋ฐ์ดํฐ ์ฒ๋ฆฌ
class NaverLoginHandler(SocialLoginHandler):
def populate_user(self, user, extra_data):
user.username = extra_data.get('name', '') or extra_data.get('nickname', '') or f"user_{extra_data.get('id', '')}"
user.first_name = extra_data.get('name', '') or extra_data.get('nickname', '')
# ํฉํ ๋ฆฌ ํด๋์ค
class SocialLoginHandlerFactory:
# ์ ๊ณต์ ๋ณ ํธ๋ค๋ฌ
HANDLERS = {
'kakao': KakaoLoginHandler,
'naver': NaverLoginHandler,
}
@staticmethod
def get_handler(provider):
handler_class = SocialLoginHandlerFactory.HANDLERS.get(provider)
if handler_class is None:
raise ValueError(f"Provider '{provider}'์ ๋ํ ํธ๋ค๋ฌ๊ฐ ์ ์๋์ง ์์์ต๋๋ค.")
return handler_class()
# django allauth ์์
๊ณ์ ์ด๋ํฐ ์ปค์คํ
class MySocialAccountAdapter(DefaultSocialAccountAdapter):
# ๋ก๊ทธ์ธ ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฉ์ ๋ชจ๋ธ์ ๋งคํ
def populate_user(self, request, sociallogin, data):
user = super().populate_user(request, sociallogin, data)
extra_data = sociallogin.account.extra_data
provider = sociallogin.account.provider
# ์ ๊ณต์ ๋ณ ๋ฐ์ดํฐ ์ฒ๋ฆฌ
handler = SocialLoginHandlerFactory.get_handler(provider)
handler.populate_user(user, extra_data)
user.last_name = '' # ์ด๊ธฐํ
return user
# ์ค๋ณต ์ด๋ฉ์ผ ๊ณ์ ํตํฉ ์ฒ๋ฆฌ
def pre_social_login(self, request, sociallogin):
if sociallogin.is_existing:
return
email = sociallogin.account.extra_data.get('email')
if email:
try:
existing_user = EmailAddress.objects.get(email=email).user
sociallogin.connect(request, existing_user)
except EmailAddress.DoesNotExist:
pass-
populate_user:- ์์ ์ ๊ณต์๋ก๋ถํฐ ๋ฐ์ ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฉ์ ๋ชจ๋ธ์ ๋งคํ
- ํ์ฅ์ฑ์ ์ํด Kakao์ Naver ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฆฌ ์ฒ๋ฆฌ
-
pre_social_login:- ์ค๋ณต ์ด๋ฉ์ผ ํ์ธ ๋ฐ ๊ธฐ์กด ์ฌ์ฉ์ ๊ณ์ ์ฐ๊ฒฐ
EmailAddress๋ชจ๋ธ์ ํ์ฉํด ์ด๋ฉ์ผ ์ค๋ณต ์ฌ๋ถ๋ฅผ ํ์ธ
-
์ถ์ ํด๋์ค ๋ฐ ํฉํ ๋ฆฌ ํจํด:
SocialLoginHandler์ถ์ ํด๋์ค๋ฅผ ํตํด ๊ณตํต ์ธํฐํ์ด์ค ์ ์- ํฉํ ๋ฆฌ ํด๋์ค(
SocialLoginHandlerFactory)๋ฅผ ํ์ฉํด ์ ๊ณต์๋ณ ๋ก์ง์ ๋ถ๋ฆฌ
Django ๊ธฐ๋ณธ auth ์์คํ
์ logout() ๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ ๊ตฌํ
from django.urls import path
from django.contrib.auth.views import LogoutView
urlpatterns = [
...
path('logout/', LogoutView.as_view(), name='logout'),
]
- ๋ก๊ทธ์ธ ์ํ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํน์ ํ์ด์ง ์ ๊ทผ ์ ํ.
- ์: ๋ก๊ทธ์ธ ์ํ์์
/users/landing์ ๊ทผ ์/user/info๋ก ์ด๋ - ๋ก๊ทธ์์ ์ํ์์
/users/userinfo์ ๊ทผ ์/user/landing์ผ๋ก ์ด๋
- ์: ๋ก๊ทธ์ธ ์ํ์์
from django.shortcuts import render, redirect
from allauth.socialaccount.models import SocialAccount
def landing_view(request):
# ๋ก๊ทธ์ธ ์ํ์ธ์ง ํ์ธ ํ ๋ฆฌ๋ค์ด๋ ํธ
if request.user.is_authenticated:
return redirect('/users/userinfo')
return render(request, 'landing.html')
def userinfo_view(request):
# ๋ก๊ทธ์ธ ์ํ์ธ์ง ํ์ธ ํ ๋ฆฌ๋ค์ด๋ ํธ
if not request.user.is_authenticated:
return redirect('/users/landing')
social_account = SocialAccount.objects.filter(user=request.user).first()
extra_data = social_account.extra_data if social_account else {}
context = {
'extra_data': extra_data
}
return render(request, 'userinfo.html', context)
์ฝ์ด์ฃผ์ ์ ๊ฐ์ฌํฉ๋๋ค ๐