Django 이메일 인증을 위한 SMTP 설정 가이드
이메일 인증은 회원가입이나 비밀번호 재설정 기능에서 자주 사용되며, Django에서는 SMTP 설정을 통해 손쉽게 구현할 수 있습니다.
이번 글에서는 SMTP가 무엇인지, 어떻게 설정하는지, 네이버 메일을 기준으로 SMTP 정보를 입력하는 법까지 하나씩 정리합니다.
💡 SMTP란?
SMTP(Simple Mail Transfer Protocol)는 이메일을 전송하는 표준 통신 프로토콜입니다.
웹 애플리케이션에서 이메일을 보내려면 SMTP 서버와의 연결이 필요하며, Django에서는 이 설정을 통해 메일을 발송할 수 있습니다.
✅ SMTP 설정에 필요한 정보 정리
항목 설명
SMTP 서버 주소 | 이메일 제공 업체의 메일 전송 서버 주소 (예: smtp.naver.com) |
SMTP 포트 번호 | TLS: 587, SSL: 465 |
발신자 이메일 주소 | 메일을 보낼 계정 |
이메일 비밀번호 | 보안을 위해 앱 비밀번호 권장 |
TLS / SSL 사용 여부 | 전송 암호화 방식 설정 |
✅ Django의 SMTP 설정 방법
setting.py에 설정
import json
from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
with open(BASE_DIR / '.config_secret' / 'secret.json') as f:
config_secret_str = f.read()
SECRET = json.loads(config_secret_str)
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = SECRET['DJANGO_SECRET_KEY']
secret.json 에 다음과 같이 작성합니다:
{
"DJANGO_SECRET_KEY": "django-insecure-3413@cggy0b%9ie&7g_15nnh3+4la@0xx3q!%vxuda24bs0x1a%b0y$",
"email": {
"user":"jangpanjangsu@naver.com",
"password":"비밀번호"
}
}
settings.py에 다음과 같이 작성합니다:
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.naver.com' # ✅ SMTP 서버명
EMAIL_PORT = 465 # ✅ 포트 번호
EMAIL_USE_SSL = True # ✅ SSL 사용 (중요!)
EMAIL_USE_TLS = False # ❌ TLS는 사용하지 않음
EMAIL_HOST_USER = SECRET["email"]["user"]
EMAIL_HOST_PASSWORD = SECRET["email"]["password"] # 앱 비밀번호 사용 권장
DEFAULT_FROM_EMAIL = 'jangpanjangsu@naver.com'
터미널에서 발송체크
python manage.py shell
from django.core.mail import send_mail
from django.conf import settings
title = '제목입니다.'
message = '이것은 django에서 보낸 메시지입니다.'
from_email = settings.EMAIL_HOST_USER
to_email = ['받는대상']
send_mail(title, message, from_email, to_email)
# views.py
class SignupView(FormView):
template_name = 'auth/signup.html'
form_class = SignupForm
def form_valid(self, form):
user = form.save()
# 이메일 발송
signer = TimestampSigner()
signed_user_email = signer.sign(user.email)
signer_dump = signing.dumps(signed_user_email)
# print(signer_dump)
#
# decoded_user_email = signing.loads(signer_dump)
# print(decoded_user_email)
# email = signer.unsign(decoded_user_email, max_age=60 * 30)
# print(email)
url = f"{self.request.scheme}://{self.request.META["HTTP_HOST"]}/verify/?code={signer_dump}"
print(url)
return render(self.request, 'auth/signup_done.html', {'user': user})
📌 1. 클래스 개요
class SignupView(FormView):
- FormView: Django CBV(Class-Based View) 중 하나
- 회원가입 폼 처리 전담 (GET 요청: 폼 렌더링 / POST 요청: 폼 유효성 검사 수행)
📌 2. 주요 속성 설정
template_name = 'auth/signup.html'
form_class = SignupForm
- template_name: 회원가입 화면으로 렌더링할 템플릿 경로
- form_class: 사용자 입력을 받을 폼 클래스 지정
📌 3. form_valid 메서드 분석 (핵심 로직)
def form_valid(self, form):
- 폼 데이터가 유효할 경우 자동 실행되는 메서드
- 회원가입 이후 이메일 인증 URL 생성 로직 포함
✅ 코드 흐름별 분석
① 사용자 저장
user = form.save()
- DB에 새 사용자(User 객체)를 저장
② 이메일 서명 처리
signer = TimestampSigner()
signed_user_email = signer.sign(user.email)
- TimestampSigner: 시간정보 포함 서명 생성기 (위변조 방지)
- 사용자의 이메일에 서명 부여
③ URL 전달용 문자열 직렬화
signer_dump = signing.dumps(signed_user_email)
- signing.dumps: 파이썬 객체를 안전한 문자열로 변환 (URL에 포함 가능)
④ 서명 복호화 및 만료 검증 (테스트 목적)
decoded_user_email = signing.loads(signer_dump)
email = signer.unsign(decoded_user_email, max_age=60 * 30)
- loads: 문자열을 역직렬화하여 객체로 복원
- unsign: 원래 이메일 추출 (30분 제한 설정)
⑤ 인증 URL 생성
url = f"{self.request.scheme}://{self.request.META['HTTP_HOST']}/verify/?code={signer_dump}"
- 완성된 URL 예시: http://127.0.0.1:8000/verify/?code=...
- 이 URL을 이메일에 포함시켜 사용자에게 발송함
⑥ 완료 페이지 반환
return render(self.request, 'auth/signup_done.html', {'user': user})
- 회원가입 완료 템플릿 반환 (signup_done.html)
views.py verify_email 함수 생성 및 최종수정
from django.contrib.auth import get_user_model
from django.core import signing
from django.core.signing import TimestampSigner, SignatureExpired
from django.shortcuts import render, get_object_or_404
from django.views.generic import FormView
from django.urls import reverse_lazy
from django.shortcuts import render
from member.forms import SignupForm
from utils.email import send_email
User = get_user_model()
class SignupView(FormView):
template_name = 'auth/signup.html'
form_class = SignupForm
def form_valid(self, form):
user = form.save()
# 이메일 발송
signer = TimestampSigner()
signed_user_email = signer.sign(user.email)
signer_dump = signing.dumps(signed_user_email)
# print(signer_dump)
#
# decoded_user_email = signing.loads(signer_dump)
# print(decoded_user_email)
# email = signer.unsign(decoded_user_email, max_age=60 * 30)
# print(email)
url = f"{self.request.scheme}://{self.request.META["HTTP_HOST"]}/verify/?code={signer_dump}"
subject = '[Pystagram] 이메일 인증을 완료해주세요'
message = f'다음 링크를 클릭해주세요. <br><a href="{url}">url</a>'
send_email(subject, message, user.email)
return render(self.request, 'auth/signup_done.html', {'user': user})
def verify_email(request):
code = request.GET.get('code', '')
signer = TimestampSigner()
try:
decoded_user_email = signing.loads(code)
email = signer.unsign(decoded_user_email, max_age=60 * 30)
except (TypeError, SignatureExpired):
return render(request, template_name='auth/not_verified.html')
user = get_object_or_404(User, email=email, is_active=False)
user.is_active = True
user.save()
# return redirect(reverse('login'))
return render(request, template_name='auth/email_verified_done.html', context={'user': user})
email.py 에 메일보내는 기능 작성
from typing import Union
from django.core.mail import send_mail
from django.conf import settings
def send_email(subject, message, to_email):
# to_email이 리스트인지 확인하여, 아니면 리스트로 감쌈
to_email = to_email if isinstance(to_email, list) else [to_email, ]
# 아래는 같은 로직의 주석 처리된 버전 (이전 방식)
# if isinstance(to_email, list):
# to_email = to_email
# else:
# to_email = [to_email, ]
# 이메일 발송 함수 호출
send_mail(subject, message, settings.EMAIL_HOST_USER, to_email)
not_verfied.html / verified_done.html 작성
# not_verified.html
{% extends 'base.html' %}
{% block content %}
<div>
<p>
이메일이 제대로 인증 되지 않았습니다. 다시 시도해주세요.
</p>
</div>
{% endblock %}
# email_verified_done.html
{% extends 'base.html' %}
{% block content %}
<div>
<p>
이메일이 인증이 완료되었습니다.
</p>
</div>
{% endblock %}
urls.py 작성
from django.contrib import admin
from django.urls import path
from member import views as member_views
urlpatterns = [
path('admin/', admin.site.urls),
path('signup/', member_views.SignupView.as_view(), name='signup'),
path('verify/', member_views.verify_email, name='verify_email'),
]
💡 실전 팁 & 주의사항
항목 설명
🔐 이메일 서명 | TimestampSigner를 통해 위변조 방지 가능 |
⏰ 만료 시간 | max_age=1800 (30분 제한)으로 보안 강화 |
📧 이메일 발송 | 현재는 print()로 URL 확인 → 실제 사용 시 send_mail() 활용 필요 |
❌ 예외 처리 | SignatureExpired 발생 가능 → try-except 문으로 예외 처리 필요 |
🎯 요약
- 사용자의 이메일에 시간 기반 서명을 추가하여 위변조 방지
- 직렬화한 서명을 포함한 인증 URL 생성
- 인증 URL은 30분간 유효하며, 해당 링크로 본인 인증 가능
- 본 기능은 실전 배포 시 이메일 전송 로직(send_mail)과 예외 처리 코드가 반드시 필요함
⚠️ 보안 주의사항
- 네이버 계정에서 2단계 인증이 설정된 경우 일반 비밀번호가 아닌 앱 비밀번호를 발급받아 사용해야 합니다.
- .env 또는 config_secret.json 등 환경변수 파일에 저장하고 코드에 직접 노출되지 않도록 관리합니다.
✅ 네이버 SMTP 사용 시 필수 설정 사항
- IMAP/SMTP 사용 설정
- [네이버 메일 > 환경설정 > POP3/IMAP 설정] 진입
- "IMAP/SMTP 사용함" 으로 설정
- 모바일/메일 앱 등록 방법
- 안드로이드: 메일 앱에서 네이버 선택 후 등록
- iOS: 설정 > Mail > 계정 추가 > 기타 > 이메일 입력
- IMAP/SMTP 서버 정보 요약
구분 서버 주소 포트 보안
IMAP | imap.naver.com | 993 | SSL |
SMTP | smtp.naver.com | 587 | TLS (또는 SSL) |
- 사용 중단 시 방법
- 메일 환경설정에서 "IMAP/SMTP 사용안함"으로 변경 가능
🔐 환경변수로 SMTP 정보 보호하기
# .env
EMAIL_HOST_USER=your_email@naver.com
EMAIL_HOST_PASSWORD=your_app_password
# settings.py
import os
from dotenv import load_dotenv
load_dotenv()
EMAIL_HOST_USER = os.getenv('EMAIL_HOST_USER')
EMAIL_HOST_PASSWORD = os.getenv('EMAIL_HOST_PASSWORD')
✅ 반드시 .env 파일은 .gitignore에 추가하여 Git에 푸시되지 않도록 해야 합니다.
📌 마무리 체크리스트
- SMTP 서버 정보 입력 완료 (EMAIL_HOST, PORT 등)
- 발신자 계정은 앱 비밀번호 사용 권장
- 네이버 IMAP/SMTP 사용 설정 "사용함"으로 변경
- 환경변수 또는 JSON 파일로 보안 정보 관리
다음 글에서는 이 SMTP 설정을 기반으로 실제 이메일 인증 링크를 생성하고 인증 로직을 처리하는 방법을 알아보겠습니다! ✉️
'Django' 카테고리의 다른 글
🔍 Django에서 User.objects.model의 정체는? (0) | 2025.05.12 |
---|---|
Chapter 8-6 Mini Project: Django Login & Logout 기능 만들기 (0) | 2025.05.12 |
Chapter 8-4 환경변수 관리와 python-dotenv 사용법 (0) | 2025.05.12 |
Chapter 8-3 Django 회원가입 페이지 만들기 (정적 파일 + 폼 커스텀 + 뷰 구현) (0) | 2025.05.12 |
Chapter 8-2 Django에서 커스텀 유저 모델 만들기 (AbstractBaseUser 활용) (0) | 2025.05.12 |