훈훈훈

파이썬(Django) :: 회원가입 시 이메일 인증 API 본문

파이썬/Django

파이썬(Django) :: 회원가입 시 이메일 인증 API

훈훈훈 2020. 3. 21. 16:43

이번 포스팅은 장고(Django)로 회원가입 시 인증 이메일을 보내는 API Code를 정리 해보려고한다.

 

회원가입 API 기반으로 작성하기 때문에 회원가입/로그인에 관한 자세한 내용은 아래 링크를 참고 바란다.

https://wave1994.tistory.com/65?category=872868

 

API 구현하기에 앞서 Gmail SMTP(Simple Mail Transfer Protocol)을 사용하기 위해 아래와 같은 설정을 하였다.

 

# 사전 설정 

1. IMAP 설정

- 이메일 클라이언트에서 Gmail을 사용할 수 있게 IMAP 설정을 1단게로 설정한다.

https://support.google.com/mail/answer/7126229?hl=ko&rd=3&visit_id=1-636281811566888160-3239280507#ts=1665018

 

2. 보안 수준 설정

- 보안 수준 설정을 통해 Gmail에서 자체 차단 없이 이메일을 발송할 수 있다.

https://support.google.com/accounts/answer/6010255

 

# 코드 구현

모든 설정을 끝마쳤다면, 이제 Django에서 SMTP 설정을 하자,

 

다른 블로그들을 둘러보았을떄 대부분 settings.py에서 설정을 하였지만, 해당 파일은 Git에 올라갈 수 있기 때문에 정보가 외부로 노출될 수 있다. 따라서 my_settings.py 파일을 새로 생성하여 해당 파일에 Json 형식으로 아래와 같이 작성하도록한다.

추가적으로 SECRET_KEY와 데이터 베이스도 외부로 노출되면 안되는 정보이기 때문에 아래와 같이 작성한다.

DATABASES = {
    'default' : {
        'ENGINE'  : 'django.db.backends.mysql',
        'NAME'    : 'Scheme Name',
        'USER'    : 'User Name',
        'PASSWORD': 'Your Password',
        'HOST'    : 'localhost',
        'PORT'    : 'Database Server Port',
        'OPTIONS': {
            'init_command': "SET sql_mode='STRICT_TRANS_TABLES'",
            'charset'     : 'utf8mb4',
            'use_unicode' : True,
        },
    }
}

SECRET_KEY = {
    'secret'   :'m!#@+v40p*05jd2fds2fe)me1f&4mvfi!igbv7b^2dyrn5=o2dw!i-0u7*&^',
    'algorithm':'HS256' 
}


EMAIL = {
    'EMAIL_BACKEND'      :'django.core.mail.backends.smtp.EmailBackend', 
    'EMAIL_USE_TLS'      : True,      
    'EMAIL_PORT'         : 587,                   
    'EMAIL_HOST'         : 'smtp.gmail.com',
    'EMAIL_HOST_USER'    : 'Gmail ID@gmail.com',
    'EMAIL_HOST_PASSWORD': 'Gmail Password',
    'SERVER_EMAIL'       : 'Gmail ID',
    'REDIRECT_PAGE'      : 'https://wave1994.tistory.com' 
}

그 다음 settings로 가서 아래와 코드를 추가한다.

import my_settings 

SECRET_KEY = my_settings.SECRET_KEY
DATABASES  = my_settings.DATABASES

EMAIL_BACKEND       = my_settings.EMAIL['EMAIL_BACKEND']
EMAIL_USE_TLS       = my_settings.EMAIL['EMAIL_USE_TLS']      
EMAIL_PORT          = my_settings.EMAIL['EMAIL_PORT']                
EMAIL_HOST          = my_settings.EMAIL['EMAIL_HOST']
EMAIL_HOST_USER     = my_settings.EMAIL['EMAIL_HOST_USER']
EMAIL_HOST_PASSWORD = my_settings.EMAIL['EMAIL_HOST_PASSWORD']
SERVER_EMAIL        = my_settings.EMAIL['SERVER_EMAIL']

 

이제 토큰을 생성하는 함수를 작성하자.

해당 코드는 token.py 파일을 생성하여 작성하였다.

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

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()

그 다음 이메일 발송 시 보내지는 Text를 작성하는 함수를 작성하자.

해당 코드는 text.py 파일을 생성하여 작성하였다.

def message(domain, uidb64, token):
    return f"아래 링크를 클릭하면 회원가입 인증이 완료됩니다.\n\n회원가입 링크 : http://{domain}/account/activate/{uidb64}/{token}\n\n감사합니다."

위 코드에서 회원가입 링크는 Active를 담당하는 엔드포인트 URL 주소이다. 

해당 엔드포인트는 아래에서 추가적으로 설명하려고한다.

 

이제 회원가입 시 바로 이메일을 발송하는 API를 작성하자. 

먼저 회원가입 관련 앱은 Account 앱을 설치한다.

 

그다음 앱의 경로에 있는 models.py에 아래와 같이 작성한다.

from django.db       import models

class Account(models.Model):
    name             = models.CharField(max_length=50, null=True)
    password         = models.CharField(max_length=400)
    email            = models.EmailField(max_length=200)
    is_active        = models.BooleanField(default=False)
    created_at       = models.DateTimeField(auto_now_add=True)
    updated_at       = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = 'accounts'

project 디렉터리에 있는 urls.py와 account App 경로에 있는 urls.py에 아래와 같이 각각 추가한다.

'''
Project 경로에 있는 urls.py
'''

from django.urls import path, include

urlpatterns = [
    path('account', include('account.urls')),
]


'''
account/urls.py
'''

from django.urls import path
from .views      import SignUpView , SignInView, Activate

urlpatterns = [
    path('/signup', SignUpView.as_view()),
    path('/signin', SignInView.as_view()),
    path('/activate/<str:uidb64>/<str:token>', Activate.as_view())
]

 

이제 View 파일을 작성하겠다.

 

먼저 account/view.py에서 아래 모듈을 임포트한다.

import jwt
import json

from .models                import Account
from .tokens                import account_activation_token
from .text                  import message
from my_settings            import SECRET_KEY, EMAIL

from django.views                    import View
from django.http                     import HttpResponse, JsonResponse
from django.core.exceptions          import ValidationError
from django.core.validators          import validate_email
from django.shortcuts                import redirect
from django.contrib.sites.shortcuts  import get_current_site
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

그 다음 회원가입에 관한 SignUpView 클래스를 생성하였다.

class SignUpView(View):
    def post(self, request):
        data = json.loads(request.body)
        try:
            validate_email(data["email"])

            if Account.objects.filter(email=data["email"]).exists():
                return JsonResponse({"message" : "EXISTS_EMAIL"}, status=400)

            user = Account.objects.create(
                email     = data["email"],
                password  = bcrypt.hashpw(data["password"].encode("UTF-8"), bcrypt.gensalt()).decode("UTF-8"),
                is_active = False 
            )

            current_site = get_current_site(request) 
            domain       = current_site.domain
            uidb64       = urlsafe_base64_encode(force_bytes(user.pk))
            token        = account_activation_token.make_token(user)
            message_data = message(domain, uidb64, token)

            mail_title = "이메일 인증을 완료해주세요"
            mail_to    = data['email']
            email      = EmailMessage(mail_title, message_data, to=[mail_to])
            email.send()         
 
            return JsonResponse({"message" : "SUCCESS"}, status=200)

        except KeyError:
            return JsonResponse({"message" : "INVALID_KEY"}, status=400)
        except TypeError:
            return JsonResponse({"message" : "INVALID_TYPE"}, status=400)
        except ValidationError:
            return JsonResponse({"message" : "VALIDATION_ERROR"}, status=400)

마지막으로 인증을 담당하는 클래스인 Activate를 작성하였다.

class Activate(View):
    def get(self, request, uidb64, token):
        try:
            uid  = force_text(urlsafe_base64_decode(uidb64))
            user = Account.objects.get(pk=uid)
            
            if account_activation_token.check_token(user, token):
                user.is_active = True
                user.save()

                return redirect(EMAIL['REDIRECT_PAGE'])
        
            return JsonResponse({"message" : "AUTH FAIL"}, status=400)

        except ValidationError:
            return JsonResponse({"message" : "TYPE_ERROR"}, status=400)
        except KeyError:
            return JsonResponse({"message" : "INVALID_KEY"}, status=400)

 

# 실행 결과

mac OS 환경에서 httpie를 이용하여 아래와 같이 테스를 진행하였다.

 

회원가입 엔드포인트로 이메일과 패스워드를 입력 시 가입이 완료되며, 아래와 같이 회원 가입 인증 메일이 발송된 것을 확인할 수 있다.

 

 

** 추가적으로 EC2 사용 시 계정에 접근하는 IP가 변경되어 구글에서 차단하는 현상이 발생했다. 해당 현상 발생 시 아래 링크 참고 바란다.

https://support.google.com/mail/forum/AAAAK7un8RUEbelsGrXe68/?hl=en-GB

Comments