📚 Django 이메일 인증 기능 추가 (SMTP, 인증 토큰 발송)
1️⃣ SMTP 이메일 발송 설정
✔️ SMTP 서버 준비
- SMTP 서버: smtp.naver.com
- 포트: SSL 465
- 앱 비밀번호 사용 권장 (2단계 인증 필수)
✔️ secret.json
{
"EMAIL_HOST": "smtp.naver.com",
"EMAIL_PORT": 465,
"EMAIL_HOST_USER": "your_email@naver.com",
"EMAIL_HOST_PASSWORD": "your_app_password",
"EMAIL_USE_TLS": false,
"EMAIL_USE_SSL": true
}
✔️ settings.py (Email 설정)
import json
import os
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent
with open(os.path.join(BASE_DIR, 'config', 'secret.json')) as f:
secrets = json.load(f)
def get_secret(setting):
try:
return secrets[setting]
except KeyError:
raise Exception(f"Set the {setting} setting in secret.json")
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = get_secret('EMAIL_HOST')
EMAIL_PORT = get_secret('EMAIL_PORT')
EMAIL_HOST_USER = get_secret('EMAIL_HOST_USER')
EMAIL_HOST_PASSWORD = get_secret('EMAIL_HOST_PASSWORD')
EMAIL_USE_TLS = get_secret('EMAIL_USE_TLS')
EMAIL_USE_SSL = get_secret('EMAIL_USE_SSL')
DEFAULT_FROM_EMAIL = EMAIL_HOST_USER
✅ 비밀번호 하드코딩 ❌, secret.json 관리
2️⃣ 이메일 인증 로직 구현
✔️ 유저 모델
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin
from django.db import models
from django.utils import timezone
class CustomUserManager(BaseUserManager):
def create_user(self, email, password=None, **extra_fields):
if not email:
raise ValueError('이메일은 필수입니다.')
email = self.normalize_email(email)
user = self.model(email=email, **extra_fields)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, email, password=None, **extra_fields):
extra_fields.setdefault('is_staff', True)
extra_fields.setdefault('is_superuser', True)
return self.create_user(email, password, **extra_fields)
class CustomUser(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(unique=True)
nickname = models.CharField(max_length=50, blank=True)
name = models.CharField(max_length=50, blank=True)
phone_number = models.CharField(max_length=20, blank=True)
is_active = models.BooleanField(default=False) # ❗ 초기 비활성화
is_staff = models.BooleanField(default=False)
date_joined = models.DateTimeField(default=timezone.now)
objects = CustomUserManager()
USERNAME_FIELD = 'email'
✅ is_active = False
✅ 회원가입 직후 로그인 차단
✔️ 회원가입 시 이메일 인증 발송
utils.py
from django.core.mail import send_mail
from django.utils.http import urlsafe_base64_encode
from django.utils.encoding import force_bytes
from django.contrib.auth.tokens import default_token_generator
def send_verification_email(user):
uid = urlsafe_base64_encode(force_bytes(user.pk))
token = default_token_generator.make_token(user)
activation_link = f"http://localhost:8000/api/users/activate/{uid}/{token}/"
subject = "회원가입 인증 이메일"
message = f"아래 링크를 클릭하여 이메일 인증을 완료하세요:\n{activation_link}"
send_mail(
subject,
message,
from_email=None, # settings.DEFAULT_FROM_EMAIL
recipient_list=[user.email],
fail_silently=False,
)
serializers.py
from rest_framework import serializers
from django.contrib.auth import get_user_model
from .utils import send_verification_email
User = get_user_model()
class SignupSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True)
class Meta:
model = User
fields = ['email', 'password', 'nickname', 'name', 'phone_number']
def create(self, validated_data):
password = validated_data.pop('password')
user = User(**validated_data)
user.set_password(password) # 🔥 암호화
user.is_active = False # 🔥 비활성화
user.save()
send_verification_email(user) # 🔥 메일 발송
return user
3️⃣ 인증 링크 확인 API
views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from django.contrib.auth.tokens import default_token_generator
from django.utils.http import urlsafe_base64_decode
from django.contrib.auth import get_user_model
from rest_framework.permissions import AllowAny
User = get_user_model()
class ActivateUserView(APIView):
permission_classes = [AllowAny]
def get(self, request, uidb64, token):
try:
uid = urlsafe_base64_decode(uidb64).decode()
user = User.objects.get(pk=uid)
except (TypeError, ValueError, OverflowError, User.DoesNotExist):
user = None
if user and default_token_generator.check_token(user, token):
user.is_active = True
user.save()
return Response({"message": "이메일 인증이 완료되었습니다."}, status=status.HTTP_200_OK)
else:
return Response({"message": "유효하지 않은 인증 링크입니다."}, status=status.HTTP_400_BAD_REQUEST)
4️⃣ URL 등록
from django.urls import path
from .views import SignupView, ActivateUserView
urlpatterns = [
path('signup/', SignupView.as_view(), name='signup'),
path('users/activate/<uidb64>/<token>/', ActivateUserView.as_view(), name='activate-user'),
]
5️⃣ Swagger 설정 (선택)
운영 배포 시 Swagger 문서는:
# settings.py
SPECTACULAR_SETTINGS = {
'TITLE': '가계부 API',
'DESCRIPTION': 'Django REST Framework 기반 가계부 API',
'VERSION': '1.0.0',
'SERVE_INCLUDE_SCHEMA': False,
'COMPONENT_SPLIT_REQUEST': True,
'SCHEMA_PATH_PREFIX': r'/api/',
'SERVE_PERMISSIONS': ['rest_framework.permissions.AllowAny'], # 개발모드
}
배포 시 Swagger 접근 막기:
if DJANGO_ENV == 'production':
SPECTACULAR_SETTINGS['SERVE_PERMISSIONS'] = ['rest_framework.permissions.IsAdminUser']
✅ 완성!
📌 흐름 요약
- 회원가입 → 이메일 발송 (is_active=False)
- 이메일 링크 클릭 → 인증 GET 요청
- is_active=True 업데이트
- 로그인 가능
🧠 주의 사항
- SMTP 서버 앱 비밀번호 사용 (네이버, 구글 등)
- URL Expire Time(토큰 만료) → 기본 1일
- 운영 배포 시 Swagger 보호 필요
- 메일 서버는 SMTP 외 AWS SES, Mailgun 고려 가능
'프로젝트' 카테고리의 다른 글
📌 Docker PostgreSQL과 로컬 Django 연동 문제 해결 과정 정리 1/2 (0) | 2025.06.11 |
---|---|
ChatGPT Connection Error "Something went wrong," Global Users Experiencing Outage 📝 What's Happening? (0) | 2025.06.10 |
📚 회원 탈퇴(DELETE) 기능 정리 (10) (0) | 2025.06.10 |
🚩[3단계-1] Django 5.2 + DRF + Swagger (drf-spectacular) 적용 정리 (0) | 2025.06.09 |
🚩[3단계-1] 내 프로젝트 맞춰 만들어보는 API 스펙 (0) | 2025.06.09 |