[Django] 비밀번호 재설정 메일 보내기
# SMTP
Simple Mail Transfer Protocol
Simple Mail Transfer Protocol
smtp는 인터넷에서 이메일을 보내기 위해 이용되는 프로토콜로 두 메일 서버간의 통신을 지원해준다.
# gmail SMTP를 사용하기 위한 발신 메일 계정 설정
## 1. IMAP 사용 설정
설정 > 모든 설정 보기 > 전달 및 POP/IMAP 탭
변경사항 저장을 누르면
이렇게 IMAP를 사용할 수 있습니다.
로 바뀐다.
## 2. 이메일 클라이언트에 로그인할 수 있도록 설정
다른 이메일 플랫폼을 통해 Gmail 확인하기
를 보면 앱 비밀번호를 사용하던지 보안 수준이 낮은 앱 허용을 설정하라고 되어있는데, 2022년 5월 30일부터 보안 수준이 낮은 앱 허용 설정을 더 이상 사용할 수 없다고 한다.
따라서 나는 2단계 인증을 사용하도록 하겠다.
### 2단계 인증
대신 2단계 인증을 사용하면 나중에 settings.py에 넣는 비밀번호에 앱 비밀번호를 설정해주어야 한다
앱 비밀번호는
[Gmail] 2단계 인증 사용 시 앱 비밀번호 생성하여 타 클라이언트(보안수준이 낮은 앱) 연결하기
에 나와있듯이 앱이나 기기에 내 Google 계정 액세스 권한을 부여하는 16자리 비밀번호이다.
생성시 나오는 16자리 앱 비밀번호를 이용해서 메일 서비스를 진행한다.
### 보안 수준이 낮은 앱의 액세스를 허용 (참고)
그래도 만약 수준이 낮은 앱의 액세스를 허용하는 방법으로 하고싶다면
Goole 계정관리
로 들어가서
보안 수준이 낮은 앱의 액세스를 허용해준다.
# 장고
## SMTP 설정
Sending email 에 나외있듯이
django.core.mail 모듈을 이용해서 메일을 보내기 위한 SMTP 설정을 해줘야한다.
### settings.py
우선 settings.py에 Email 전송을 위한 설정을 해준다.
EMAIL_HOST_PASSWORD와 같이 숨겨야 하는 값은 env로 가져오자.
# email
# 메일을 보내는 호스트 서버
EMAIL_HOST = 'smtp.gmail.com'
# ENAIL_HOST에 정의된 SMTP 서버가 사용하는 포트 (587: TLS/STARTTLS용 포트)
EMAIL_PORT = '587'
# 발신할 이메일 주소 '[email protected]'
EMAIL_HOST_USER = env('EMAIL_HOST_USER')
# 발신할 이메일 비밀번호 (2단계 인증일경우 앱 비밀번호)
EMAIL_HOST_PASSWORD = env('EMAIL_HOST_PASSWORD')
# TLS 보안 방법 (SMPT 서버와 통신할 떄 TLS (secure) connection 을 사용할지 말지 여부)
EMAIL_USE_TLS = True
# 사이트와 관련한 자동응답을 받을 이메일 주소
DEFAULT_FROM_EMAIL = EMAIL_HOST_USER
### 이메일이 잘 연결되는지 확인하기 위해 테스트
django.core.mail.send_mail() 을 이용해서 메일을 보내보자
>>> r = send_mail(subject='매일 제목', message='메일 내용', from_email='[email protected]', recipient_list=['[email protected]'])
>>> r
1
- email_from: 메일 발송 주소
- recipient_list: 받는 메일 주소 리스트
1이 리턴되면 메일이 성공적으로 보내진거다.
### urls.py
장고는 로그인, 로그아웃, 패스워드 관리와 관련된 8개의 뷰와 url을 제공하고, 나는 비밀번호 리셋을 위해 마지막 4개를 사용할 예정이다.
우선 그냥 기본으로 제공하는 url과 템플릿을 그대로 사용해보고 이후에 변경해보겠다.
# urls.py
from django.urls import includd
...
urlpatterns = [
...
path('password/', include('django.contrib.auth.urls')),
]
패스워드 리셋 테스트
password_reset/
(http://localhost:8000/password/password_reset/ 접속)
이메일로 password reset link를 발송할 수 있는 폼 화면이 나온다.
password_reset_done/
reset my password를 버튼을 누르면 나오는 화면
password_reset_confirm/<uidb64>/<token>/
사용자에게 발송되는 링크 형식
사용자가 링크를 클릭하면 여기서 비빌번호 변경을 할 수 있다.
/reset/done/
근데 사용자에게 Django administration이라고 보이는 이런 화면으로 보여줄 순 없으니까, 커스텀 템플릿을 따로 만들어보겠다.
## url
프로젝트/urls.py
from django.contrib import admin
from django.contrib.auth import views as auth_views
from django.urls import path
urlpatterns = [
...
path('password_reset/done/',
auth_views.PasswordResetDoneView.as_view(template_name='account/password_reset_done.html'),
name='password_reset_done'),
path('reset/<uidb64>/<token>/',
auth_views.PasswordResetConfirmView.as_view(template_name='account/password_reset_confirm.html'),
name='password_reset_confirm'),
path('reset/done/',
auth_views.PasswordResetCompleteView.as_view(template_name='account/password_reset_complete.html'),
name='password_reset_complete'),
]
프로젝트/account/urls.py
from django.urls import path
from rest_framework_simplejwt.views import TokenRefreshView
from . import views
app_name = 'account'
urlpatterns = [
...
path('password_reset/', views.password_reset_request, name="password_reset"),
]
## 템플릿 설정
나는 프로젝트 폴더 하위에 templates 폴더를 만들어서 이곳에서 모든 템플릿 파일들을 관리하려고 한다.
이를 위해서는 장고가 템플릿 파일들을 해당 경로(templates 폴더)에서 찾을 수 있도록 settings.py의 TEMPLATES의 DIRS에 os.path.join(BASE_DIR, 'templates')
를 추가해줘야 한다.
# settings.py
...
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')], # <-이부분!
...
위와 같이 base.html, head.html은 공통으로 두고
비밀번호 리셋 관련 파일들은 프로젝트 > templates > 앱이름 > 안에 넣었다.
base.html
<!DOCTYPE html>
<html lang="ko">
{% include 'head.html' %}
<body>
{% block content %}
{% endblock %}
</body>
</html>
head.html
<head>
<meta charset="UTF-8">
<title>Givwang</title>
</head>
## password_reset
password_reset.html
{% extends 'base.html' %}
{% block content %}
<!--Reset Password-->
<div>
<h2>비밀번호 재설정</h2>
<hr>
<p>비밀번호를 잊어버리셨나요?<br>
가입하신 이메일을 입력해 주세요.<br>
잠시 후 해당 이메일로 비밀번호 재설정 메일이 발송됩니다.</p>
<form method="POST">
{% csrf_token %}
{{ password_reset_form }}
<button type="submit">전송</button>
</form>
</div>
{% endblock %}
## password_reset_done
password_reset_done.html
{% extends 'base.html' %}
{% block content %}
<!--Password reset sent-->
<div>
<h2>비밀번호 재설정 메일 발송 완료</h2>
<hr>
<p>
비밀번호 재설정을 위한 메일이 발송되었습니다.<br>
30분 내에 비밀번호를 재설정 해주세요.<br>
만약 메일이 오지 않는다면, 이메일 주소를 다시 한번 확인해 주시고, 스팸 폴더를 확인해 주세요.
</p>
</div>
{% endblock %}
## email 로 전송될 텍스트
프로젝트/templates/account/password_reset_email.txt
{% autoescape off %}
안녕하세요 :)
다음 링크를 누르시면 [email protected] 계정의 기브왕 앱의 비밀번호를 재설정 할 수 있는 화면으로 이동합니다.
{{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %}
비밀번호 재설정을 요청하지 않았다면 이 이메일을 무시하셔도 됩니다.
기브왕과 함께해주셔서 감사합니다.
{% endautoescape %}
이메일로 보내질 내용이 포함된 txt 파일을 생성한다.
## password_reset_confirm
password_reset_confirm.html
{% extends 'base.html' %}
{% block content %}
<!--Password Reset Confirm-->
<div>
<h2>비밀번호 재설정</h2>
<hr>
<p>새 비밀번호를 입력해 주세요.</p>
<form method="POST">
{% csrf_token %}
{{ form }}
<button>저장</button>
</form>
</div>
{% endblock %}
여기까지 문제는 없는데, 영어로 보여지기 때문에 한국에서 서비스하는 내 앱은 한국어로 보여줘야 한다.
내용을 커스텀하기 위해 오버라이드 하는 방법도 있을 것 같은데, 나는 우선 settings.py에 LANGUAGE_CODE = 'ko-KR'
를 추가해서 지원 언어가 한글이 되도록 하겠다.
settings.py
...
LANGUAGE_CODE = 'ko-KR'
추가하면 이렇게 한글로 보여진다.
## password_reset_complete
password_reset_complete.html
{% extends 'base.html' %}
{% block content %}
<div>
<h2>패스워드 변경 완료</h2>
<hr>
<p>패스워드 변경이 완료되었습니다. 기브왕 앱에서 다시 로그인 해주세요.</p>
</div>
{% endblock %}
## account/views.py
from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth.forms import PasswordResetForm
from django.contrib.auth.tokens import default_token_generator
from django.core.mail import send_mail, BadHeaderError
from django.db.models.query_utils import Q
from django.http import HttpResponse
from django.shortcuts import render, redirect
from django.template.loader import render_to_string
from django.utils.encoding import force_bytes
from django.utils.http import urlsafe_base64_encode
def password_reset_request(request):
if request.method == "POST":
password_reset_form = PasswordResetForm(request.POST)
if password_reset_form.is_valid():
data = password_reset_form.cleaned_data['email']
associated_users = get_user_model().objects.filter(Q(email=data))
if associated_users.exists():
for user in associated_users:
subject = '[기브왕] 비밀번호 재설정'
email_template_name = "account/password_reset_email.txt"
c = {
"email": user.email,
# local: '127.0.0.1:8000', prod: 'givwang.herokuapp.com'
'domain': settings.HOSTNAME,
'site_name': 'givwang',
# MTE4
"uid": urlsafe_base64_encode(force_bytes(user.pk)),
"user": user,
# Return a token that can be used once to do a password reset for the given user.
'token': default_token_generator.make_token(user),
# local: http, prod: https
'protocol': settings.PROTOCOL,
}
email = render_to_string(email_template_name, c)
try:
send_mail(subject, email, '[email protected]' , [user.email], fail_silently=False)
except BadHeaderError:
return HttpResponse('Invalid header found.')
return redirect("/password_reset/done/")
password_reset_form = PasswordResetForm()
return render(
request=request,
template_name='account/password_reset.html',
context={'password_reset_form': password_reset_form}
Author And Source
이 문제에 관하여([Django] 비밀번호 재설정 메일 보내기), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@oen/Django-비밀번호-재설정-메일-보내기저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)