[DRF] DRF Django로 이메일 인증(SMTP) 구현하기 - 4편

2022. 5. 19. 17:11

DRF django restDRF django rest framework를 이용하여 email 인증을 구현하는 방법에 대해서 살펴보도록 하겠다.

회원가입 시 이메일 인증을 구현하기 위해서는 SMTP와 User의 is_active 속성을 활용한다.

SMTP를 통해 보내준 메일의 링크를 통해서 인증을 완료하기 전까지는 is_active 속성을 False로 두고, 

인증을 하는 순간 is_active 속성을 True로 변경하여 User를 인증한다.

 

SMTP란?

Simple mail Transfer Protocol이 약자로 인터넷에서 이메일을 보내기 위해 이용되는 프로토콜이다.

SMTP 웹 메일을 통해서 토큰을 날려주어 인증을 구현한다.

 

설정하기

장고에서 SMTP를 활용하기 위해서는

1. gmail에서 IMAP를 사용함으로 바꾸고

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

2. 보안 수준 낮은 앱 허용 

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

으로 바꾸어야 한다. 

 

그 다음, settings.py에서 email 관련 설정을 해주어야 한다. 

 

#이메일 인증
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_USE_TLS = True
EMAIL_PORT = 587
EMAIL_HOST = 'smtp.gmail.com'
EMAIL_HOST_USER = 'your email'
#os.environ.get('EMAIL_HOST_USER')
EMAIL_HOST_PASSWORD = 'your password'
SERVER_EMAIL = EMAIL_HOST_USER
DEFAULT_FROM_MAIL = EMAIL_HOST_USER

 

EMAIL_HOST_USER와 EMIAL_HOST_PASSWORD에는

메일을 보낼 때 사용할 gmail을 적어주면된다. 

 

그 다음, shell을 통해 메일일 잘 전송되는지 확인보자.

 

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

 

email.send()를 보냈을 때 1의 값이 반환되면 완료된 것이다.

실제로 메일이 보내지는 것을 확인할 수 있다. 

 

그 다음 serializers.py create함수에 이메일 전송 관련 내용을 추가한다.

 

    def create(self, validated_data):
        #password = validated_data.get("password")
        user = super().create(validated_data)
        user.set_password(validated_data["password"])
        user.is_active = False
        user.save()
        payload = JWT_PAYLOAD_HANDLER(user)
        jwt_token = JWT_ENCODE_HANDLER(payload)
        message = render_to_string('users/user_activate_email.html', {
            'user': user,
            'domain': 'localhost:8000',
            'uid': force_str(urlsafe_base64_encode(force_bytes(user.pk))),
            'token': jwt_token,
        })
        print(message)
        mail_subject = '[SDP] 회원가입 인증 메일입니다'
        to_email = user.email
        email = EmailMessage(mail_subject, message, to = [to_email])
        email.send()
        return user

 

앞서 로그인할 때 사용했던 JWT_ENCODE_HANDLER, JWT_PAYLOAD_HANDLER로 token을 새성한다.

이 코드는 장고 이메일 인증을 치면 나오는 대부분의 게시물을 참고한 것인데,

장고 2.x 버전에서 작성된 코드라서 자잘한 에러들이 많이 났다.

장고 버전이 업그레이드 됨에 따라서 지원하지 않는 함수, 혹은 변경된 기능들이 많아져서 그런데

위와 같이 조금 수정하였더니 잘 돌아가는 것을 확인할 수 있다.

 

'str' object has no attribute 'decode' 에러. 

위와 같은 에러는 함수에서 decode할 필요가 없는 것을 decode할 때 발생하므로 

decode부분을 삭제해주었더니 잘 돌아간다.

 

그 다음 user_activate_email.html을 작성한다.

이 부분은 실제로 보낼 메일의 내용이다.

 

{% autoescape off %}
안녕하세요

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

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

{% endautoescape %}

 

장고 templates를 너무 오랜만에 써봐서 간단한 부분에서 헤맸었다.

Template Does Not Exist 에러가 그것이었는데,

장고는 templates를 templates/앱이름/html의 구조로 넣어줘야, 해당 html을 수월하게 인식한다.

 

그 다음, user가 링크를 클릭했을 때 user를 activate하는 함수를 작성한다.

 

class UserActivateView(APIView):
    permission_classes = [AllowAny]
    def get(self, request, uid, token):
        try:
            real_uid = force_str(urlsafe_base64_decode(uid))
            print(real_uid)
            user = User.objects.get(pk=real_uid)
            if user is not None:
                payload = jwt_decode_handler(token)
                user_id =jwt_payload_get_user_id_handler(payload)
                print(type(user))
                print(type(user_id))
                if int(real_uid) == int(user_id):
                    user.is_active = True
                    user.save()
                    return Response(user.email + '계정이 활성화 되었습니다', status=status.HTTP_200_OK)
                return Response('인증에 실패하였습니다', status=status.HTTP_400_BAD_REQUEST)
            else:
                return Response('인증에 실패하였습니다', status=status.HTTP_400_BAD_REQUEST)

        except(TypeError, ValueError, OverflowError, User.DoesNotExist):
            user = None
            print(traceback.format_exc())
            return Response('인증에 실패하였습니다',status=status.HTTP_400_BAD_REQUEST)

 

UserActivateView에서는 유저의 real_uid를 decode한 값과 token을 decode해서 얻은 user_id를 비교하여 

두개가 같다면, user를 인증하는 방식으로 함수가 돌아간다.

 

발생한 에러

AssertionError: Expected a `Response`, `HttpResponse` or `HttpStreamingResponse` to be returned from the view, but received a `<class 'NoneType'>`

당연하게 들릴 수도 있지만, 모든 if, try, except문의 끝에는 Response를 작성해주어야 한다.

Response를 주지 않아서 에러가 났었다.

 

그 다음, 메일의 링크를 따라서 url을 작성한다.

 

path('activate/<str:uid>/<str:token>',views.UserActivateView.as_view(), name ='activate'),

 

아래 링크로 들어오면, 유저의 인증이 완료되는 것을 확인할 수 있다.

 

drf를 이용하여 간단하게 이메일 인증을 구현해볼 수 있었다. 

BELATED ARTICLES

more