📚 Django 이메일 인증 기능 추가 (SMTP, 인증 토큰 발송) (10)

2025. 6. 10. 03:19·프로젝트

📚 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']

✅ 완성!

📌 흐름 요약

  1. 회원가입 → 이메일 발송 (is_active=False)
  2. 이메일 링크 클릭 → 인증 GET 요청
  3. is_active=True 업데이트
  4. 로그인 가능

🧠 주의 사항

  • 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
'프로젝트' 카테고리의 다른 글
  • 📌 Docker PostgreSQL과 로컬 Django 연동 문제 해결 과정 정리 1/2
  • ChatGPT Connection Error "Something went wrong," Global Users Experiencing Outage 📝 What's Happening?
  • 📚 회원 탈퇴(DELETE) 기능 정리 (10)
  • 🚩[3단계-1] Django 5.2 + DRF + Swagger (drf-spectacular) 적용 정리
Chansman
Chansman
안녕하세요! 코딩을 시작한 지 얼마 되지 않은 초보 개발자 찬스맨입니다. 이 블로그는 제 학습 기록을 남기고, 다양한 코딩 실습을 통해 성장하는 과정을 공유하려고 합니다. 초보자의 눈높이에 맞춘 실습과 팁, 그리고 개발하면서 겪은 어려움과 해결 과정을 솔직하게 풀어내려 합니다. 함께 성장하는 개발자 커뮤니티가 되기를 바랍니다.
  • Chansman
    찬스맨의 프로그래밍 스토리
    Chansman
  • 전체
    오늘
    어제
    • 분류 전체보기 (798)
      • Python (32)
      • 프로젝트 (113)
      • 과제 (25)
      • Database (40)
      • 멘토링 (11)
      • 특강 (37)
      • 기술블로그 (41)
      • 기술블로그-Fastapi편 (33)
      • 기술블로그-Django편 (154)
      • 기술블로그-Flask편 (36)
      • AI 분석 (5)
      • HTML & CSS (31)
      • JavaScript (17)
      • AWS_Cloud (21)
      • 웹스크래핑과 데이터 수집 (14)
      • Flask (42)
      • Django (77)
      • Fastapi (16)
      • 연예 (14)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    chinanightlife
    self-growth
    basalcellcarcinoma
    뷔
    urbantrends
    RM
    btsjungkook
    lawsuitculture
    remittance
    global politics
    bts
    classaction
    life reflection
    americaparty
    btsdischarge
    btsreunion
    travel ban
    homebartrend
    trumpmuskclash
    hotcoffeecase
    college reunions
    btscomeback
    titaniumcase
    youngprofessionals
    smartphonedurability
    livebroadcast
    chatgpterror
    newpoliticalparty
    americanlaw
    gpterror
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
Chansman
📚 Django 이메일 인증 기능 추가 (SMTP, 인증 토큰 발송) (10)
상단으로

티스토리툴바