안녕세계

[Django] 회원가입 인증 메일 보내기 [2/2] (gmail SMTP) 본문

[Django] 회원가입 인증 메일 보내기 [2/2] (gmail SMTP)

Junhong Kim 2018. 4. 30. 18:00
728x90
반응형

장고에서 회원가입 인증 메일 보내기 [2/2] (gmail SMTP)

NOTE:
지난 포스팅에서 진행한 gmail 계정 셋팅 장고 settings.py 설정 하신후 진행하셔야 합니다.
설정 후 장고 쉘에서 테스트로 메일을 전송 해보신 후 진행하시는 것을 추천드립니다!

우선, user 테이블을 생성합시다.

user/models.py

user 테이블에 필수적인 속성만 추가합니다.

from django.db import models from utils.common.cipher import AESCipher class User(models.Model): class Meta: db_table = "users" created_at = models.DateTimeField(auto_now_add=True) updated_ay = models.DateTimeField(auto_now=True) email = models.CharField(max_length=128, unique=True) password = models.CharField(max_length=255) active = models.BooleanField(default=False)

db_table="users": 테이블명을 users로 정의
created_at: 생성 시간
updated_at: 수정 시간
email: 아이디 겸 이메일로 유일
password: 비밀번호
active: 인증 여부(=활성화 여부)

장고에서 API 2개를 만들어 봅시다.

  1. POST /users/signup: 회원가입 후 인증 메일 전송
  2. GET /users/activate: 인증 메일 클릭시 해당 아이디를 활성화

/user/urls.py

from django.urls import path from . import views app_name = 'user' urlpatterns= [ path('signup', views.SignUp.as_view(), name='signup') path('activate/<str:uidb64>/<str:token>', views.UserActivate.as_view(), name='activate') ]


1. 회원 가입 및 이메일 전송 API

일반적인 POST API와 동일하지만 내부적으로 하는 일이 다릅니다.
회원 가입에 성공하면 인증 이메일을 전송합니다.
serializer.is_valid()는 UserSerializer 클래스의 create()를 실행합니다.

/user/views.py

class SignUp(APIView): def post(self, request): """ 사용자 데이터 생성 """ serializer = UserSerializer(data=request.data) if serializer.is_valid(): serializer.save() return Response(serializer.data), status=status.HTTP_200_OK) return Response(serializer.errors), status=status.HTTP_400_BAD_REQUEST)

/user/serializers.py

user 테이블에 해당하는 serializer를 만듭시다.

from django.contrib.sites.shortcuts import get_current_site from django.core.mail import EmailMessage from django.utils.encoding import force_bytes from django.utils.http import urlsafe_base64_encode from django.template.loader import render_to_string from rest_framework import serializers from .models import User from .tokens import account_activation_token from utils.common.cipher import AESCipher class UserSerializer(serializers.ModelSerializer): created_by = serializers.CharField(max_length=64, required=False) updated_by = serializers.CharField(max_length=64, required=False) email = serializers.EmailField() class Meta: model = User fields = '__all__' def to_internal_value(self, data): """ POST/PUT과 같이 데이터 변경이 있을 때 데이터를 저장하기 전에 핸들링 할수 있는 함수입니다. """ ret = super(UserSerializer, self).to_internal_value(data) # 비밀번호를 암호화하기 위한 과정으로 예제에서는 생략해도 됩니다. cipher = AESCipher() ret['password'] = cipher.encrypt_str(ret['password']) return ret def to_representation(self, obj): """ GET/POST/PUT과 같이 데이터 변경이 있고 난 후 serializer.data로 접근할 때 값을 변환하여 보여줍니다. """ ret = super(UserSerializer, self).to_representation(obj) return ret def validate_email(self, value): """ 이메일이 데이터베이스에 존재하는지 확인합니다. """ if User.objects.filter(email=value).exists(): raise serializers.ValidationError("이메일이 이미 존재합니다.") return value def validate_password(self, value): """ 패스워드가 8글자 이하인지 확인합니다. """ if len(value) < 8: raise serializers.ValidationError("패스워드는 최소 %s자 이상이어야 합니다." % 8) return value def create(self, validated_data): """ 데이터를 저장할 때 필요한 과정을 구현합니다. """ user = User.objects.create( email=validated_data['username'], password=validated_data['password'], ) user.active = False user.save() # current_site = get_current_site(self.context['request']) message = render_to_string('user/account_activate_email.html', { 'user': user, 'domain': 'localhost:8000', 'uid': urlsafe_base64_encode(force_bytes(user.pk)).decode('utf-8'), 'token': account_activation_token.make_token(user) }) """ 이메일 전송 과정 """ mail_subject = 'test' to_email = '아이디@gmail.com' # EmailMessage(제목, 본문, 받는이) email = EmailMessage(mail_subject, message, to=[to_email]) email.send() return validated_data

코드가 길지만 우리는 message 변수에 대해서만 잘 알면 됩니다.

user: 생성한 사용자 객체
domain: 도메인 주소(예제는 localhost)
uid: 생성한 사용자 객체의 PK를 암호화한 값
token: 생성한 사용자 객체를 통해 생성한 token 값

/user/tokens.py

token을 생성할 때는 다음과 같은 파일을 생성후 진행합니다.

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.active) ) account_activation_token = AccountActivationTokenGenerator()


위에서 만든 4가지 값을 account_activate_email.html로 넘겨주고 렌더링할 수 있도록 message에 담습니다.
이메일 전송 과정에서 알아야할 것은 EmailMessage()가 전부입니다.
EmailMessage()에 들어가는 인자는 제목, 본문, 받는이 순서 입니다.
예제에서 제목은 test이고 본문은 message 변수에 담겨진 html 페이지 입니다.
email 객체를 민들었다면 이제 send() 하기면 하면 메일이 전송됩니다.
그 전에 account_activate_email.html 페이지에 대해 알아봅시다.

/user/templates/user/account_activate_email.html

html 페이지는 별게 없습니다. 넘겨 받은 값을 html 페이지에 뿌려주기만 하면 됩니다.

{{ user.name }}님, 아래 링크를 클릭해서 계정을 활성화해주세요. http://{{ domain }}{% url 'user:activate' uidb64=uid token=token %}

여기까지가 회원가입 및 이메일 전송 API의 전부입니다.
다음으로는 이제 전송된 메일을 확인하여 사용자를 활성화 시키면 됩니다.

2. 인증 메일을 확인한 사용자 활성화 API

/user/views.py

class UserActivate(APIView): permission_classes = (permissions.AllowAny, ) def get(self, request, uidb64, token): try: uid = force_text(urlsafe_base64_decode(uidb64.encode('utf-8'))) user = User.objects.get(pk=uid) except(TypeError, ValueError, OverflowError, User.DoesNotExist): user = None try: if user is not None and account_activation_token.check_token(user, token): user.active = True user.save() return Response(user.email + ' 계정이 활성화 되었습니다.', status=status.HTTP_200_OK) else: return Response('만료된 링크입니다.', status=status.HTTP_400_BAD_REQUEST) except Exception as e: print(traceback.format_exc())


우리가 보낸 이메일을 확인을 누르면 activate API를 호출하게 됩니다. 사용자가 호출한 api에 암호화된 uid와 token이 있는데 이를 복호화가 정상적으로 되면 사용자의 active 속성을 True로 변경하면 됩니다.

[참고]
https://jamiejang.blogspot.kr/2018/01/blog-post_51.html
https://jamiejang.blogspot.kr/2018/01/rest-api.html
https://wikidocs.net/10373
https://docs.djangoproject.com/ko/2.0/intro/tutorial03/

728x90
반응형
Comments