728x90
320x100
DRF 프로젝트 - 이메일 인증 구현하기
이번 프로젝트에서 구현해야 하는 기능중에 꽤 생소한 이메일 인증
1,2 일차에 기본적인 백엔드 코드를 완성시켜뒀기 때문에 오늘 다 같이 이메일 인증에 도전하기로 했다.
5명이서 각자 해보고, 괜찮은 것을 고르거나 합쳐서 보완해 사용하기로 결정
이것저것 찾아보다가 장고에서 제공하는 EmailMessage 모듈이 있다는 것을 알았다.
이 모듈을 쓰면 SMTP를 이용해 이메일을 보낼 수 있당.
👇참고한 블로그👇
https://ssungkang.tistory.com/entry/entry/Django-회원가입-시-이메일-인증-SMTP
# users/serializers.py
class EmailThread(threading.Thread):
def __init__(self, email):
self.email = email
threading.Thread.__init__(self)
def run(self):
self.email.send()
class Util:
@staticmethod
def send_email(message):
email = EmailMessage(subject=message["email_subject"], body=message["email_body"], to=[message["to_email"]])
EmailThread(email).start()
# 비밀번호 찾기 serializer
class PasswordResetSerializer(serializers.Serializer):
email = serializers.EmailField()
class Meta:
fields = ("email",)
def validate(self, attrs):
try:
email = attrs.get("email")
user = Users.objects.get(email=email)
uidb64 = urlsafe_b64encode(smart_bytes(user.id))
token = PasswordResetTokenGenerator().make_token(user)
frontend_site = "127.0.0.1:8000"
absurl = f"http://{frontend_site}/set_password.html?/{uidb64}/{token}"
email_body = "비밀번호 재설정 " + absurl
message = {
"email_body": email_body,
"to_email": user.email,
"email_subject": "비밀번호 재설정",
}
Util.send_email(message)
return super().validate(attrs)
except Users.DoesNotExist:
raise serializers.ValidationError(detail={"email": "잘못된 이메일입니다. 다시 입력해주세요."})
# 비밀번호 재설정 serializer
class SetNewPasswordSerializer(serializers.Serializer):
password = serializers.CharField(
write_only=True,
)
repassword = serializers.CharField(
write_only=True,
)
token = serializers.CharField(
max_length=100,
write_only=True,
)
uidb64 = serializers.CharField(
max_length=100,
write_only=True,
)
class Meta:
fields = (
"repassword",
"password",
"token",
"uidb64",
)
def validate(self, attrs):
password = attrs.get("password")
repassword = attrs.get("repassword")
token = attrs.get("token")
uidb64 = attrs.get("uidb64")
try:
user_id = force_str(urlsafe_base64_decode(uidb64))
user = Users.objects.get(id=user_id)
if PasswordResetTokenGenerator().check_token(user, token) == False:
raise exceptions.AuthenticationFailed("토큰이 유효하지 않습니다.", 401)
if password != repassword:
raise serializers.ValidationError(detail={"repassword": "비밀번호가 일치하지 않습니다."})
user.set_password(password)
user.save()
return super().validate(attrs)
except Users.DoesNotExist:
raise serializers.ValidationError(detail={"user": "존재하지 않는 회원입니다."})
# User Token 획득
class TokenSerializer(serializers.Serializer):
email = serializers.EmailField()
password = serializers.CharField(
write_only=True,
)
# users/views.py
# 회원가입
class Util:
@staticmethod
def send_email(message):
email = EmailMessage(subject=message["email_subject"], body=message["email_body"], to=[message["to_email"]])
EmailThread(email).start()
class SignupView(APIView):
def post(self, request):
serializer = UserSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
user = serializer.save()
user.is_active = False
user = serializer.save()
# 토큰 생성
uid = urlsafe_b64encode(force_bytes(user.pk))
token = PasswordResetTokenGenerator().make_token(user)
# 이메일 전송
email = user.email
authurl = f'http://localhost:8000/verify-email/{uid}/{token}/'
email_body = "이메일 인증" + authurl
message = {
"email_body": email_body,
"to_email": email,
"email_subject": "이메일 인증",
}
Util.send_email(message)
return Response({"message": "가입완료!"}, status=status.HTTP_201_CREATED)
else:
return Response({"message": f"${serializer.errors}"}, status=status.HTTP_400_BAD_REQUEST)
class VerifyEmailView(APIView):
def get(self, request, uidb64, token):
try:
# URL에 포함된 uid를 디코딩하여 사용자 식별
uid = urlsafe_base64_decode(uidb64).decode()
user = Users.objects.get(pk=uid)
except (TypeError, ValueError, OverflowError, Users.DoesNotExist):
user = None
token_generator = PasswordResetTokenGenerator()
# 사용자가 존재하고 토큰이 유효한지 확인
if user is not None and token_generator.check_token(user, token):
# 이메일 인증 완료 처리
user.is_active = True
user.save()
return Response({"message": "이메일 인증이 완료되었습니다."}, status=status.HTTP_200_OK)
else:
return Response({"message": "잘못된 링크입니다."}, status=status.HTTP_400_BAD_REQUEST)
# 비밀번호 찾기
class PasswordResetView(APIView):
def post(self, request):
serializer = PasswordResetSerializer(data=request.data)
if serializer.is_valid():
return Response({"message": "비밀번호 재설정 이메일"}, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
# 비밀번호 재설정 토큰 확인
class PasswordTokenCheckView(APIView):
def get(self, request, uidb64, token):
try:
user_id = force_str(urlsafe_b64decode(uidb64))
user = get_object_or_404(Users, id=user_id)
if not PasswordResetTokenGenerator().check_token(user, token):
return Response({"message": "링크가 유효하지 않습니다."}, status=status.HTTP_401_UNAUTHORIZED)
return Response({"uidb64": uidb64, "token": token}, status=status.HTTP_200_OK)
except DjangoUnicodeDecodeError as identifier:
return Response({"message": "링크가 유효하지 않습니다."}, status=status.HTTP_401_UNAUTHORIZED)
# 비밀번호 재설정
class SetNewPasswordView(APIView):
def put(self, request):
serializer = SetNewPasswordSerializer(data=request.data)
if serializer.is_valid():
return Response({"message": "비밀번호 재설정 완료"}, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
# 회원정보 인증 토큰 발급
class ObtainUserTokenView(APIView):
def post(self, request):
serializer = TokenSerializer(data=request.data, context={"request": request})
if serializer.is_valid():
user = authenticate(
email=serializer.validated_data["email"],
password=serializer.validated_data["password"],
)
token, _ = Token.objects.get_or_create(user=user)
return Response({"token": token.key}, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
300x250
반응형
GitHub 댓글