본문 바로가기

Web Programming/Django

Django 회원가입 이메일 인증(SMTP)

많은 포털 사이트에서 회원가입 시 웹 메일 인증 시스템을 채택하고 있다.

(현재는 대부분 보안성 때문에 기기 인증 등도 활용되고 있다고 한다...)

 

프로젝트를 진행하면서 해당 부분이 필요하게 되어 찾아보며 정리해봤다.

진행되는 로직은 크게 SMTP를 활용하여, User의 is_actvie 속성을 활용한다.

SMTP 웹 메일을 통해 토큰을 날려주고, 해당 토큰을 인증하기 전 까지 is_active 속성을 False로 둔다.

그리고 토큰을 통해 인증을 하는 순간 is_active 속성을 True로 변경하여 User 객체를 인증한다.

(즉, 토큰을 SMTP 프로토콜을 통해 날려준다)

# SMTP

간이 전자 우편 전송 프로토콜(Simple Mail Transfer Protocol, SMTP)은 인터넷에서 이메일을 보내기 위해 이용되는 프로토콜이다. 쉽게 말해, 간단히 두 메일 서버 간의 통신을 지원해주는 프로토콜로 우리는 SMTP를 통해 회원가입 관련 인증 메일을 전송할 것이다.

# 선행 설정

웹메일 SMTP를 활용하기 위해서는 다음과 같이 두 가지의 설정이 필요하다

  • IMAP 사용함 : IMAP은 인터넷을 통해 메일 서버에 접근하기 위한 프로토콜로 우리는 이 프로토콜을 사용해서 처리한다
  • 보안수준 낮은 앱 허용 : 우리는 Django 서버를 통해 보안수준이 낮은 형태로 접근하기에 해당 설정을 허용해야 한다

# Django SMTP 설정

# settings.py

EMAIL_HOST = 'smtp.gmail.com' 		 # 메일 호스트 서버
EMAIL_PORT = '587' 			 # 서버 포트
EMAIL_HOST_USER = 'ID@gmail.com' 	 # 우리가 사용할 Gmail
EMAIL_HOST_PASSWORD = 'pw'		 # 우리가 사용할 Gmail p
EMAIL_USE_TLS = True			 # TLS 보안 설정
DEFAULT_FROM_EMAIL = EMAIL_HOST_USER	 # 응답 메일 관련 설정

# Shell 통해 확인하기

$ python manage.py shell
>>> from django.core.mail import EmailMessage
>>> email = EmailMessage('title', 'content', to=['id@gmail.com'])
>>> email.send()

다음의 결과 값으로 1이 들어오면 정상적으로 발송된 것이다.

실제 메일이 수신됨을 확인할 수 있다.

# Singup Template에 Email 입력칸 추가하기

<h1>Sign Up!</h1>

<form method="POST" action="{% url 'signup'%}">
    {% csrf_token %}

    Username:
    <br>
    <input name="username" type="text" value="">
    <br>
    Email:
    <br>
    <input name="email" type="email" value="">
    <br>
    Password:
    <br>
    <input name="password1" type="password" value="">
    <br>
    Confirm Password:
    <br>
    <input name="password2" type="password" value="">
    <br>
    <br>
    <input class="btn btn-primary" type="submit" value="Sign Up!">
</form>

# View.py 코드 변경

/project/accounts/views.py

from django.shortcuts import render, redirect
from django.contrib.auth.models import User
from django.contrib import auth

# SMTP 관련 인증
from django.contrib.sites.shortcuts import get_current_site
from django.template.loader import render_to_string
from django.utils.http import urlsafe_base64_encode,urlsafe_base64_decode
from django.core.mail import EmailMessage
from django.utils.encoding import force_bytes, force_text
from .tokens import account_activation_token

# Create your views here.
def signup(request):
    # 포스트 방식으로 들어오면
    if request.method == 'POST':
        # 비밀번호 확인도 같다면
        if request.POST['password1'] ==request.POST['password2']:
            # 유저 만들기
            user = User.objects.create_user(username=request.POST['username'], password=request.POST['password1'])
            user.is_active = False # 유저 비활성화
            user.save()
            current_site = get_current_site(request) 
            message = render_to_string('accounts/activation_email.html', {
                'user': user,
                'domain': current_site.domain,
                'uid': urlsafe_base64_encode(force_bytes(user.pk)),
                'token': account_activation_token.make_token(user),
            })
            mail_title = "계정 활성화 확인 이메일"
            mail_to = request.POST["email"]
            email = EmailMessage(mail_title, message, to=[mail_to])
            email.send()
            return redirect("home")

    # 포스트 방식 아니면 페이지 띄우기
    return render(request, 'accounts/signup.html')



def login(request):
    # 포스트 방식으로 들어오면
    if request.method == 'POST':
        # 정보 가져와서 
        username = request.POST['username']
        password = request.POST['password']
        # 로그인
        user = auth.authenticate(request, username=username, password=password)
        # 성공
        if user is not None:
            auth.login(request, user)
            return redirect('home')
        # 실패
        else:
            return render(request, 'accounts/login.html', {'error': 'username or password is incorrect.'})
    else:
        return render(request, 'accounts/login.html')

def logout(request):
    # 포스트 방식으로 들어오면
    if request.method == 'POST':
        # 유저 로그아웃
        auth.logout(request)
        return redirect('home')
    return render(request, 'accounts/signup.html')

# 계정 활성화 함수(토큰을 통해 인증)
def activate(request, uidb64, token):
    try:
        uid = force_text(urlsafe_base64_decode(uidb64))
        user = User.objects.get(pk=uid)
    except(TypeError, ValueError, OverflowError, User.DoesNotExsit):
        user = None
    if user is not None and account_activation_token.check_token(user, token):
        user.is_active = True
        user.save()
        auth.login(request, user)
        return redirect("home")
    else:
        return render(request, 'home.html', {'error' : '계정 활성화 오류'})
    return 

# Tokens.py 파일 추가하기

여기서 약간의 문제가 있었다.

six import 에러 발생

처음에는 six Library를 활용하여 토큰을 만들어내는 코드들이 활용되고 있었는데 계속 six 를 import할 수 없다고 에러가 발생하였다. 찾아보니 3.0에서 현재 지원하지 않는다고 하여 import를 할 수 없다고 한다...

 

그래서 six library 대신하여 str를 통해 단순히 pk 와 timestamp를 변경해주기로 하였다.

실제 서비스에서는 이렇게 구현하면 안될것 같다... user의 pk와 같은 주요데이터들이 노출된다던지, 겹친다던지 등의 문제 때문에 위험할 것 같다. 그렇지만 추후에 보완할 수 있도록 하고 지금은 이렇게 해결하였다.  

/project/accounts/tokens.py 파일 생성

from django.contrib.auth.tokens import PasswordResetTokenGenerator

class AccountActivationTokenGenerator(PasswordResetTokenGenerator):
    def _make_hash_value(self, user, timestamp):
        return (str(user.pk) + str(timestamp)) +  str(user.is_active)

account_activation_token = AccountActivationTokenGenerator()

# 웹 메일 인증 템플릿 생성

project/accounts/templates/account/activation_email.html 파일 생성

{{user.username}}님, 아래 링크를 클릭하여 계정을 활성화하세요:
http://{{domain}}{% url 'activate' uid token %}

# Accounts의 url 추가하기

project/accounts/urls.py

from django.urls import path
from . import views
   
urlpatterns = [
    path('signup/', views.signup, name="signup"),
    path('login/', views.login, name="login"),
    path('logout/', views.logout, name="logout"),
    path('activate/<str:uidb64>/<str:token>/', views.activate, name="activate"),
]

# 결과

인증을 하지 않고 로그인 하면 다음과 같이 로그인이 되지 않음
다음과 같이 인증 메일이 수신됨

 

다음의 링크를 통해 웹 메일 인증 가능
인증 완료

 

참조

https://ssungkang.tistory.com/105#comment5657240

 

[Django] 회원가입 시 이메일 인증, SMTP

SMTP SMTP 는 Simple Mail Transfer Protocol 의 약자로 전자 메일 전송을 위한 표준 프로토콜입니다. 이를 이용해서 인증메일을 보내보도록 하겠습니다. 사전 설정 IMAP 설정 : 링크로 들어가서 IMAP 1단계 설정..

ssungkang.tistory.com

 

'Web Programming > Django' 카테고리의 다른 글

Ajax 통신하기  (0) 2020.02.29
Static 추가하기  (0) 2020.02.16
Like 좋아요 모델 추가하기  (1) 2019.06.25
백엔드_CRUD 실습 3  (0) 2019.05.13
백엔드_CRUD 실습 2  (4) 2019.05.10