수학과의 좌충우돌 프로그래밍

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

웹프로그래밍/Django

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

ssung.k 2019. 5. 18. 17:56

SMTP

SMTP 는 Simple Mail Transfer Protocol 의 약자로 전자 메일 전송을 위한 표준 프로토콜입니다. 이를 이용해서 인증메일을 보내보도록 하겠습니다.

사전 설정

IMAP 설정 : 링크로 들어가서 IMAP 1단계 설정을 해줍니다. 이로서 다른 이메일 클라이언트에서 Gmail 을 사용할 수 있도록 해줍니다.

보안수준 설정 : 위 설정만으로는raise SMTPSenderRefused(code, resp, from_addr 에러가 발생합니다. 제가 접근하는 방식이 보안수준이 낮기 때문에 위험을 느끼고 Gmail 에서 접근을 막는 것 입니다. 따라서 위 링크로 들어가서 보안 수준을 낮춰줘야 합니다.

다음으로는 장고 내부 settings.py 로 이동하여 설정을 해보도록 합시다.

# settings.py

EMAIL_HOST = 'smtp.gmail.com'
# 메일을 호스트하는 서버
EMAIL_PORT = '587'
# gmail과의 통신하는 포트
EMAIL_HOST_USER = '********@gmail.com'
# 발신할 이메일
EMAIL_HOST_PASSWORD = '********'
# 발신할 메일의 비밀번호
EMAIL_USE_TLS = True
# TLS 보안 방법
DEFAULT_FROM_EMAIL = EMAIL_HOST_USER
# 사이트와 관련한 자동응답을 받을 이메일 주소,'webmaster@localhost'

메일 보내기

우선 테스트용 메일을 보내보도록 하겠습니다. 장고 쉘을 켜보도록 하겠습니다.

python manage.py shell

그리고 메일을 보내는 건 간단합니다.

>>> from django.core.mail import EmailMessage
>>> email = EmailMessage('title', 'content', to=['아이디@서버의 주소'])
>>> email.send()
1
>>>

1은 메일이 성공적으로 보내졌을 때의 return 값 입니다. 메일은 title이라는 제목과 content 라는 내용으로 to 에게 보내지게 됩니다.

회원가입 시 인증메일 보내기

메일을 보낼 수 있다고 해서 인증절차가 끝난 건 아닙니다. 이 외에도 알아야 할 것이 많지만 함께 구현하면서 알아보도록 하겠습니다.

저번 시간에 알아봤던 로그인 기능 유지하기 에서 응용한 코드니 이해가 잘 안된다면, 보고 오셔도 좋습니다. 그 외에도 import 해오는 메소드들이 많습니다.

# views.py
# 기존에 사용하던 메소드
from django.shortcuts import render,redirect,HttpResponse
from django.contrib.auth.models import User
from django.contrib.auth.hashers import check_password
from django.contrib.auth import get_user_model
from django.contrib import auth
from ..models import Profile

# 새로 추가하는 메소드
from django.contrib.sites.shortcuts import get_current_site
from django.template.loader import render_to_string
rom django.utils.http import urlsafe_base64_encode,urlsafe_base64_decode
from django.utils.encoding import force_bytes
from django.core.mail import EmailMessage
from .tokens import account_activation_token
from django.utils.encoding import force_bytes, force_text

메소드들은 해당 메소드가 사용될 시에 알아보도록 하고, 전체 코드를 조금씩 나눠서 보도록 하겠습니다.

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()
            nickname = request.POST["nickname"]
            profile = Profile(user=user, nickname=nickname)
            profile.save()

회원가입 시 이메일(유저아이디), 비밀번호, 비밀번호 확인, 닉네임을 입력하게 됩니다. 비밀번호가 같음을 체크하고 같다면 이메일과 비밀번호로 user를 생성합니다. 이 떄 user는 장고에서 기본 제공해주는 auth.User 입니다. 그리고 is_active 속성을 False 로 해주었습니다. 아직 이메일 인증이 안되었기 때문에 활성화를 꺼주는 것이죠. 다음으로는 닉네임을 받아 위에서 생성한 user 와 매칭 되는 profile 을 만들어주었습니다. 이는auth.User 와 onetoone 관계로서 장고의 auth.User 를 커스터마이징하기 위해 만들어주었습니다.

            current_site = get_current_site(request) 
            # localhost:8000
            message = render_to_string('account/user_activate_email.html',                         {
                'user': user,
                'domain': current_site.domain,
                'uid': urlsafe_base64_encode(force_bytes(user.pk)).encode().decode(),
                'token': account_activation_token.make_token(user),
            })
  • get_current_site

    • request를 보낸 site 를 알려줍니다.
    • 이를 templates 에 넘겨서 이를 통해 url에 동적으로접근하도록 합니다.
  • render_to_string

    • template 객체를 반환함과 동시에 render 합니다.
    • 뒤에는 rendering 할 때 사용할 context 가 위치합니다.

다음으로는 user.pk 를 암호화하는 과정을 알아보도록 하겠습니다.

현재 user.pk 는 자연수값입니다. 이를 force_byte 를 통해 bytes 로 바꿔줍니다. 다음으로 urlsafe_base64_encode 로 인코딩해주고 이를 다시 decode 메소드를 통해 bytes에서 str 로 바꿔줍니다. 예를 통해 살펴보면 다음과 같습니다.

user.pk: 57 force_bytes(user.pk): b'57' urlsafe_base64_encode(force_bytes(user.pk)): b'NTc' urlsafe_base64_encode(force_bytes(user.pk)).decode(): NTc

이제 tokens 입니다. tokens 도 model 의 field에 추가해주고 위와 같은 과정을 똑같이 거쳐서 만들 수도 있지만 조금 다른 방식으로 만들어주었습니다. tokens.py 를 만들고 token 을 만드는 class 를 작성해주었습니다.

# tokens.py

from django.contrib.auth.tokens import PasswordResetTokenGenerator
from django.utils import six

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

account_activation_token = AccountActivationTokenGenerator()

 

장고 3버젼부터 six를 지원해주지 않아서 설치 후 사용해야합니다.

pip install six

 

그 후 six를 settings.py의 INSTALLED_APPS에 넣어주고 다음과 같이 사용할 수 있습니다.

from django.contrib.auth.tokens import PasswordResetTokenGenerator
from six import text_type


class AccountActivationTokenGenerator(PasswordResetTokenGenerator):
    def _make_hash_value(self, profile, timestamp):
        return (text_type(profile.id)) + text_type(timestamp)


account_activation_token = AccountActivationTokenGenerator()

 

 

 

 

만들어준 AccountActivationTokenGeneratorPasswordResetTokenGenerator 를 상속받고 있습니다. PasswordResetTokenGenerator 는 장고에서 제공해주는 class 로서 비밀번호를 리셋할 떄 token을 발급해주고 해당 기능을 처리해줍니다. 우리는 이 방법을 빌려 토큰을 받겠습니다.

text_type 은 유니코드 정수로부터 유니코드 문자열을 가져옵니다. user의 pk, 현재시간, user의 활성화를 가지고 합쳐 tokens 을 생성해줍니다.

다시 views 로 넘어오도록 하죠.

            mail_subject = "[SOT] 회원가입 인증 메일입니다."
            user_email = user.username
            email = EmailMessage(mail_subject, message, to=[user_email])
            email.send()
            return HttpResponse(
                '<div style="font-size: 40px; width: 100%; height:100%; display:flex; text-align:center; '
                'justify-content: center; align-items: center;">'
                '입력하신 이메일<span>로 인증 링크가 전송되었습니다.</span>'
                '</div>'
            )
            return redirect('account:home')
    return render(request, 'account/signup.html')

위에서 실습해봤던 이메일 보내는 과정입니다. 자세한 설명은 생략하도록 하겠습니다.

email 로 도착하게 되는 user_active_email 입니다.

{% autoescape off %}
    안녕하세요

    아래 링크를 클릭하면 회원가입 인증이 완료됩니다.

    회원가입 링크 : http://{{ domain }}{% url 'account:activate' uid64=uid token=token %}

{% endautoescape %}

autoescape 는 html 을 그대로 화면에 보여주게 됩니다. 그래서 이를 off 로 꺼주도록 합시다. 만일 켜져있다면 "&quot 다음과 표시되는 등 문제가 발생할 것입니다. active 라는 이름의 url로 접속하게 되면 views/activate 가 실행됩니다.

# views.py

def activate(request, uid64, token):

    uid = force_text(urlsafe_base64_decode(uid64))
    user = User.objects.get(pk=uid)

    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('account:home')
    else:
        return HttpResponse('비정상적인 접근입니다.')

암호화하였던 user의 id 를 다시 decode 함으로서 해당 user를 불러옵니다. 또한 위에서 언급했던 PasswordResetTokenGenerator 안에 있는 check_token 으로 유효성을 검사해줍니다. 해당 user가 존재하고 token도 유효하다면 active 를 활성화 해주고 로그인 해줍니다.

Comments