페트병 Rest API. 섹션 5: 비밀번호 재설정
섹션 5: 암호 재설정
안녕하세요.이 시리즈의 앞부분에서 Flask의 오류를 처리하고 클라이언트에게 의미 있는 오류 메시지를 보내는 방법을 배웠습니다.
이 부분에서 우리는 응용 프로그램에서 암호 리셋 기능을 실현할 것이다.
다음은 비밀번호 재설정 프로세스의 약도입니다.
암호 재설정 프로세스 맵
우리는
flask-jwt-extended
라이브러리를 사용하여 암호 리셋 영패를 생성할 것이다. 좋은 것은 우리가 신분 검증을 실현할 때 이미 그것을 설치했기 때문이다.우리는 사용자에게 리셋 영패를 이메일로 보내야 하기 때문에 Flask Mail 을 사용할 것입니다.pipenv install flask-mail
이 메일 서버를 등록하려면 다음과 같이 하십시오.#~/movie-bag/app.py
from flask import Flask
from flask_bcrypt import Bcrypt
from flask_jwt_extended import JWTManager
+from flask_mail import Mail
...
api = Api(app, errors=errors)
bcrypt = Bcrypt(app)
jwt = JWTManager(app)
+mail = Mail(app)
app.config['MONGODB_SETTINGS'] = {
'host': 'mongodb://localhost/movie-bag'
...
이제 클라이언트에게 전자 메일을 보내고 새 폴더 app.py
와 새 파일 services
을 만드는 서비스를 만듭니다.새로 만든 파일에 다음 내용을 추가합니다.mkdir services
cd services
touch mail_service.py
#~/movie-bag/services/mail_service.py
from threading import Thread
from flask_mail import Message
from app import app
from app import mail
def send_async_email(app, msg):
with app.app_context():
try:
mail.send(msg)
except ConnectionRefusedError:
raise InternalServerError("[MAIL SERVER] not working")
def send_email(subject, sender, recipients, text_body, html_body):
msg = Message(subject, sender=sender, recipients=recipients)
msg.body = text_body
msg.html = html_body
Thread(target=send_async_email, args=(app, msg)).start()
여기에서 볼 수 있듯이 우리는 함수mail_service.py
를 만들었는데, 함수send_mail()
, subject
, sender
, recipients
, text_body
를 매개 변수로 한다.그리고 메시지 대상을 만들고 단독 루트에서 실행합니다 html_body
. 클라이언트에게 이메일을 보낼 때 구글, Outlook 등 단독 서비스에 중계해야 하기 때문입니다.이러한 서비스가 실제로 전자메일을 보내는 데 시간이 좀 걸릴 수 있기 때문에, 우리는 고객에게 그들의 요청이 성공했다고 알리고, 단독 라인에서 전자메일을 보내기 시작할 것이다.
지금 우리는 비밀번호 리셋을 실시할 준비가 되어 있다.위의 그림에서 보듯이 우리는 이 때문에 두 개의 다른 단점을 만들 것이다.
1)
send_async_email()
: 이 노드에서 계정을 변경해야 하는 사용자/forget
를 가져옵니다.그리고 이 단점은 비밀번호를 재설정하기 위해 영패 재설정을 포함하는 링크를 사용하여 사용자에게 이메일을 보냅니다.2)
email
: 이 노드는 전자 메일에서 보낸 /reset
및 새 reset_token
를 수신합니다.password
폴더에 reset_password.py
을 만듭니다.코드는 다음과 같습니다.#~/movie-bag/resources/reset_password.py
from flask import request, render_template
from flask_jwt_extended import create_access_token, decode_token
from database.models import User
from flask_restful import Resource
import datetime
from resources.errors import SchemaValidationError, InternalServerError, \
EmailDoesnotExistsError, BadTokenError
from jwt.exceptions import ExpiredSignatureError, DecodeError, \
InvalidTokenError
from services.mail_service import send_email
class ForgotPassword(Resource):
def post(self):
url = request.host_url + 'reset/'
try:
body = request.get_json()
email = body.get('email')
if not email:
raise SchemaValidationError
user = User.objects.get(email=email)
if not user:
raise EmailDoesnotExistsError
expires = datetime.timedelta(hours=24)
reset_token = create_access_token(str(user.id), expires_delta=expires)
return send_email('[Movie-bag] Reset Your Password',
sender='[email protected]',
recipients=[user.email],
text_body=render_template('email/reset_password.txt',
url=url + reset_token),
html_body=render_template('email/reset_password.html',
url=url + reset_token))
except SchemaValidationError:
raise SchemaValidationError
except EmailDoesnotExistsError:
raise EmailDoesnotExistsError
except Exception as e:
raise InternalServerError
class ResetPassword(Resource):
def post(self):
url = request.host_url + 'reset/'
try:
body = request.get_json()
reset_token = body.get('reset_token')
password = body.get('password')
if not reset_token or not password:
raise SchemaValidationError
user_id = decode_token(reset_token)['identity']
user = User.objects.get(id=user_id)
user.modify(password=password)
user.hash_password()
user.save()
return send_email('[Movie-bag] Password reset successful',
sender='[email protected]',
recipients=[user.email],
text_body='Password reset was successful',
html_body='<p>Password reset was successful</p>')
except SchemaValidationError:
raise SchemaValidationError
except ExpiredSignatureError:
raise ExpiredTokenError
except (DecodeError, InvalidTokenError):
raise BadTokenError
except Exception as e:
raise InternalServerError
resources
자원에서 우리는 먼저 클라이언트가 제공한 ForgotPassword
에 따라 사용자를 얻는다.그리고 email
기반 create_access_token()
영패를 만듭니다. 이 영패는 24시간 후에 만료됩니다.그리고 우리는 이메일을 고객에게 보낼 것이다.전자 우편에는 user.id
및 텍스트 형식 정보가 포함되어 있습니다.이와 유사하게
HTML
자원에서 우리는 우선reset_영패의 사용자 id에 따라 사용자를 얻고 사용자가 제공한 비밀번호에 따라 사용자의 비밀번호를 리셋합니다.마지막으로 사용자에게 재설정 성공 이메일을 보냅니다.ResetPassword
에서 새로운 이상 EmailDoesnotExistsError
과 BadTokenError
을 만듭니다.#~/movie-bag/resources/errors.py
class UnauthorizedError(Exception):
pass
+class EmailDoesnotExistsError(Exception):
+ pass
+
+class BadTokenError(Exception):
+ pass
+
errors = {
"InternalServerError": {
"message": "Something went wrong",
@@ -54,5 +60,13 @@ errors = {
"UnauthorizedError": {
"message": "Invalid username or password",
"status": 401
+ },
+ "EmailDoesnotExistsError": {
+ "message": "Couldn't find the user with given email address",
+ "status": 400
+ },
+ "BadTokenError": {
+ "message": "Invalid token",
+ "status": 403
}
}
클라이언트에게 보낼 HTML과 텍스트 파일에 대한 템플릿을 만들어야 합니다.루트 디렉터리에 errors.py
폴더를 만들고, templates
에 다른 폴더 templates
를 만들고, 거기서 두 개의 새 파일 email
과 reset_password.html
을 만들 것입니다.mkdir templates
cd templates
mkdir email
cd email
touch reset_password.html
touch reset_password.txt
reset_password에 있습니다.html에서는 다음을 추가합니다.<!-- #~/movie-bag/templates/email/reset-password.html -->
<p>Dear, User</p>
<p>
To reset your password
<a href="{{ url }}">
click here
</a>.
</p>
<p>Alternatively, you can paste the following link in your browser's address bar:</p>
<p>{{ url }}</p>
<p>If you have not requested a password reset simply ignore this message.</p>
<p>Sincerely</p>
<p>Movie-bag Support Team</p>
여기reset_password.txt
는 우리가 이전에 {{ url }}
함수에서 보낸 URL을 대체했습니다.마찬가지로
render_template()
에 다음을 추가합니다.Dear, User
To reset your password click on the following link:
{{ url }}
If you have not requested a password reset simply ignore this message.
Sincerely
Movie-bag Support Team
이제 우리는 이것reset_password.txt
을 우리의 Resources
에 연결할 준비를 하고 있다. from .movie import MoviesApi, MovieApi
from .auth import SignupApi, LoginApi
+from .reset_password import ForgotPassword, ResetPassword
def initialize_routes(api):
...
api.add_resource(LoginApi, '/api/auth/login')
+
+ api.add_resource(ForgotPassword, '/api/auth/forgot')
+ api.add_resource(ResetPassword, '/api/auth/reset')
현재 응용 프로그램을 실행하려면 routes.py
다음과 같은 오류가 표시됩니다.ImportError: cannot import name 'initialize_routes' from 'resources.routes' (/home/paurakh/blog/flask/flask-restapi-series/movie-bag/resources/routes.py)
이는python에 순환 의존성 문제가 존재하기 때문이다.우리의 python app.py
중에서 우리는 reset_password.py
에서 send_mail
를 가져왔고, app
는 우리의 app.py
에서 정의되지 않았다.이 문제를 해결하기 위해서, 우리는 루트 디렉터리에 다른 파일
app
을 만들 것입니다. 이 파일은 우리의 응용 프로그램을 실행하는 것을 책임집니다.또한 응용 프로그램을 초기화한 후에 루트/보기 기능을 초기화해야 합니다.touch run.py
지금 우리 app.py
는 이렇게 해야 한다.#~/movie-bag/app.py
from database.db import initialize_db
from flask_restful import Api
-from resources.routes import initialize_routes
from resources.errors import errors
app = Flask(__name__)
app.config.from_envvar('ENV_FILE_LOCATION')
+mail = Mail(app)
+
+# imports requiring app and mail
+from resources.routes import initialize_routes
api = Api(app, errors=errors)
bcrypt = Bcrypt(app)
jwt = JWTManager(app)
-mail = Mail(app)
...
initialize_db(app)
initialize_routes(api)
-
-app.run()
Dellrun.py
에서는 애플리케이션만 실행합니다.#~/movie-bag/run.py
from app import app
app.run()
app.py
에 추가run.py
구성
JWT_SECRET_KEY = 't1NP63m4wnBg6nyHYKfmc2TpCOGI4nss'
+MAIL_SERVER = "localhost"
+MAIL_PORT = "1025"
+MAIL_USERNAME = "[email protected]"
+MAIL_PASSWORD = ""
다음 터미널에서 SMTP 서버를 시작합니다.python -m smtpd -n -c DebuggingServer localhost:1025
그러면 SMTP 서버가 만들어져 전자 메일 기능을 테스트합니다.현재 실행 응용 프로그램 사용
python run.py
참고: 내보내기 MAIL_SERVER
전자 메일이 기존 사용자의 경우 실행 중인 서버의 터미널에서 전자 메일을 볼 수 있습니다.
<p>Dear, User</p>
<p>
To reset your password
<a href="http://localhost:3000/reset/eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1NzgzOTU0ODUsIm5iZiI6MTU3ODM5NTQ4NSwianRpIjoiZTEyZDg3ODgtMTkwZS00NWI1LWI0YzYtZTdkMTYzZjc5ZGZlIiwiZXhwIjoxNTc4NDgxODg1LCJpZGVudGl0eSI6IjVlMTQxNTJmOWRlNzQxZDNjNGYwYmNiYiIsImZyZXNoIjpmYWxzZSwidHlwZSI6ImFjY2VzcyJ9.dLJnhYTYMnLuLg_cHDdqi-jsXeISeMq75mb-ozaNxlw">
click here
</a>.
</p>
<p>Alternatively, you can paste the following link in your browser's address bar:</p>
<p>http://localhost:3000/reset/eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1NzgzOTU0ODUsIm5iZiI6MTU3ODM5NTQ4NSwianRpIjoiZTEyZDg3ODgtMTkwZS00NWI1LWI0YzYtZTdkMTYzZjc5ZGZlIiwiZXhwIjoxNTc4NDgxODg1LCJpZGVudGl0eSI6IjVlMTQxNTJmOWRlNzQxZDNjNGYwYmNiYiIsImZyZXNoIjpmYWxzZSwidHlwZSI6ImFjY2VzcyJ9.dLJnhYTYMnLuLg_cHDdqi-jsXeISeMq75mb-ozaNxlw</p>
<p>If you have not requested a password reset simply ignore this message.</p>
<p>Sincerely</p>
<p>Movie-bag Support Team</p>
보시다시피 URL의 형식은 다음과 같습니다..env
, 이 영패를 ENV_FILE_LOCATION
단점으로 복사해서 수동으로 보내야 합니다.주의: 우리는 전단 시리즈에서 자동 리셋을 실현하는 방법을 배울 것입니다. 그러나 현재는reset\u 영패를 수동으로 복사해야 합니다
비밀번호를 변경한 것을 축하합니다.이제 새 암호로 로그인할 수 있습니다.
암호가 재설정되었음을 알리는 이메일도 받아야 합니다.
<p>Password reset was successful</p>
지금까지 작성한 모든 코드를 찾을 수 있습니다 here우리는 이 시리즈의 이 부분에서 무엇을 배웠습니까?
smtp
그 전에 즐거움 코드😊
Reference
이 문제에 관하여(페트병 Rest API. 섹션 5: 비밀번호 재설정), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/paurakhsharma/flask-rest-api-part-5-password-reset-2f2e텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)