이전에 만들어놓은 회원 기능에서 이메일 인증을 추가하려는 과정에서 문제가 발생하였다.
회원탈퇴를 하면 바로 데이터베이스에서 삭제하는 것이 아닌 계정을 비활성화 한 후에 일정 시간이 지나면 삭제하도록 작성하였다
따라서 비활성화 계정이 회원가입을 시도하면 로그인하여 계정을 활성화하라는 메세지를 출력해주고,
로그인을 하면 메세지와 함께 즉각적으로 활성화 계정으로 변경되도록 작성하였다.
1. 회원가입시에 인증 이메일 발송
회원가입시에 이메일 전송을 위해 이메일 전송 프로토콜을 사용해야 한다. gamil을 사용했으며, settings.py에 작성해주어야 한다
# 이메일 인증 관련 설정
EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
EMAIL_HOST = "smtp.gmail.com" # 메일 호스트 서버
EMAIL_PORT = 587 # gamil과 통신하는 포트
EMAIL_HOST_USER = env("EMAIL") # 발신할 이메일
EMAIL_HOST_PASSWORD = env("EMAIL_PASSWORD") # 발신할 이메일의 비밀번호
EMAIL_USE_TLS = True # TLS 보안 방법
DEFAULT_FROM_EMAIL = EMAIL_HOST_USER
ACCOUNT_CONFIRM_EMAIL_ON_GET = True
2. serializer 수정
위와 같이 수정해준 후에 인증 이메일을 전송하기 위해 serializer를 수정해주었다.
인증을 통해 회원가입이 마무리 될 수 있도록 계정을 생성할 때 is_active를 false로 설정해준 후에 인증 이메일을 통해 전송된 링크로 들어오면 is_acticve를 True로 변경해주도록 하였다
...
from django.contrib.auth.tokens import PasswordResetTokenGenerator
from django.template.loader import render_to_string
from django.utils.http import urlsafe_base64_encode
from django.utils.encoding import force_bytes
from django.core.mail import send_mail
...
class UserCreateSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True, max_length=128)
confirm_password = serializers.CharField(write_only=True, max_length=128)
class Meta:
model = User
fields = [
"id",
"email",
"password",
"confirm_password",
"nickname",
"age",
"bio",
"gender",
]
def validate(self, attrs):
# 유효성 검사
return attrs
def send_verification_email(self, user, uid, token):
message = render_to_string(
"accounts/account_active_email.html",
{
"user": user,
"domain": "127.0.0.1:8000", # 실제 도메인으로 교체
"uid": uid,
"token": token,
},
)
send_mail(
subject="Activate your account", # 이메일 제목
message=message, # 이메일 내용
from_email=settings.DEFAULT_FROM_EMAIL, # 송신 이메일
recipient_list=[user.email], # 수신 이메일
)
def create(self, validated_data):
user = User.objects.create_user(
email=validated_data["email"],
password=validated_data["password"],
nickname=validated_data.get("nickname"),
gender=validated_data.get("gender"),
age=validated_data.get("age"),
bio=validated_data.get("bio"),
)
user.is_active = False # 이메일 인증 전에는 계정 비활성화
user.save()
# 이메일 전송을 위한 토큰 생성
token_generator = PasswordResetTokenGenerator()
token = token_generator.make_token(user)
uid = urlsafe_base64_encode(force_bytes(user.pk))
# 이메일 전송
self.send_verification_email(user, uid, token)
return user
PasswordRestTokenGenerator()
- Django에서 비밀번호 재설정 시 사용하는 토큰을 생성하고 관리하는 역할
- make_token(user) 메서드를 사용하여 사용자의 고유한 토큰을 생성하여, 해당 사용자가 인증 과정에서 이 토큰을 통해 유효성을 검증 할 수 있도록 함
- 주로 비밀번호 재설정, 계정 활성화 등에서 안전하게 링크를 통해 인증할 때 사용
make_token(user)
- user 객체를 인자로 받아 해당 사용자의 고유한 토큰을 생성
- 단순 문자열이며 PasswordRestTokenGenerator 클래스에서 내부적으로 생성 규칙을 따름
- 이메일로 전송된 링크에 포함되어 사용자에게 전달
- 링크가 클릭되면 서버에서 토큰의 유효성을 확인하여 사용자가 올바른 링크를 통해 인증하는지 확인
urlsafe_base64_encode(force_bytes(user.pk))
- 사용자의 pk를 안전하게 url에서 사용할 수 있도록 인코딩하는 과정
- force_bytes(user.pk)는 user.pk를 바이트 형태로 변환
- urlsafe_base64_encode()는 이 바이트 데이터를 base64 방식으로 인코딩하여 URL에 안전하게 포함될 수 있는 문자열로 변환, 인코딩된 ID는 URL에 포함되어도 문제가 없고, 다시 디코딩하여 원래의 사용자 ID로 변환할 수 있습니다.
- uid : 인코딩된 사용자 ID
- token : 생성된 토큰
3. template에 html파일 생성
전송될 이메일에 담길 내용을 html파일로 만들어 전송하기 위해 html파일을 만들어준다
{% block content %}
안녕하세요, {{ user.nickname }}님!
계정을 활성화하려면 아래 링크를 클릭하세요:
http://{{ domain }}/api/v1/accounts/activate/{{ uid }}/{{ token }}/
감사합니다!
{% endblock %}
serializer의 send_varification_email 함수에서 전달된 변수들이 들어가며 메일이 전송된다.
4. urls.py
urlpatterns = [
...
path("activate/<uidb64>/<str:token>/", views.UserActivate.as_view()),
...
]
인증 이메일로 보내질 url을 포함해준다
5. views.py
...
from django.contrib.auth.tokens import PasswordResetTokenGenerator
from django.utils.http import urlsafe_base64_decode
...
class UserActivate(APIView):
def get(self, request, uidb64, token, *args, **kwargs):
try:
uid = urlsafe_base64_decode(uidb64)
user = User.objects.get(pk=uid)
except (TypeError, ValueError, OverflowError, User.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,
)
token_generator.check_token(user, token)
- 이메일로 전송된 토큰과 사용자의 정보를 기반으로 토큰이 유효한지 확인한다
- 사용자가 이메일에서 클릭한 토큰이 서버에서 생성한 토큰과 일치하고, 유효 기간 내에 있는지를 검증한다
'[내일배움캠프]스파르타코딩클럽 AI 웹개발 > Today I Learned' 카테고리의 다른 글
[TIL] Django에서 계정 일시정지 구현 (0) | 2024.10.10 |
---|---|
[TIL] 이메일 인증(2) - 로그인 (1) | 2024.10.07 |
[TIL] axios delete 요청시 데이터를 함께 전달해야 하는 경우 (0) | 2024.10.02 |
[TIL] modal 창의 내용 중복 이슈 (0) | 2024.10.01 |
[TIL] parcel 삭제 (0) | 2024.09.30 |