Hướng dẫn này trình bày chi tiết cách xác thực địa chỉ email trong quá trình đăng ký người dùng.
Đã cập nhật ngày 30 tháng 4 năm 2015 :Đã thêm hỗ trợ Python 3.
Về quy trình làm việc, sau khi người dùng đăng ký tài khoản mới, một email xác nhận sẽ được gửi đi. Tài khoản người dùng được đánh dấu là “chưa được xác nhận” cho đến khi người dùng “xác nhận” tài khoản qua hướng dẫn trong email. Đây là quy trình làm việc đơn giản mà hầu hết các ứng dụng web đều tuân theo.
Một điều quan trọng cần lưu ý là những gì người dùng chưa được xác nhận được phép làm. Nói cách khác, họ có toàn quyền truy cập vào ứng dụng của bạn, quyền truy cập có giới hạn / bị hạn chế, hoặc không có quyền truy cập nào cả? Đối với ứng dụng trong hướng dẫn này, người dùng chưa được xác nhận có thể đăng nhập nhưng họ ngay lập tức được chuyển hướng đến trang nhắc nhở rằng họ cần xác nhận tài khoản của mình trước khi có thể truy cập ứng dụng.
Trước khi bắt đầu, hầu hết các chức năng mà chúng tôi sẽ thêm vào là một phần của các tiện ích mở rộng Flask-User và Flask-Security - điều này đặt ra câu hỏi tại sao không chỉ sử dụng các tiện ích mở rộng? Vâng, trước hết, đây là một cơ hội để học hỏi. Ngoài ra, cả hai phần mở rộng đó đều có những hạn chế, như cơ sở dữ liệu được hỗ trợ. Ví dụ:nếu bạn muốn sử dụng RethinkDB?
Hãy bắt đầu.
Đăng ký cơ bản của Flask
Chúng ta sẽ bắt đầu với bảng soạn sẵn Flask bao gồm đăng ký người dùng cơ bản. Lấy mã từ kho lưu trữ. Sau khi bạn đã tạo và kích hoạt virtualenv, hãy chạy các lệnh sau để nhanh chóng bắt đầu:
$ pip install -r requirements.txt
$ export APP_SETTINGS="project.config.DevelopmentConfig"
$ python manage.py create_db
$ python manage.py db init
$ python manage.py db migrate
$ python manage.py create_admin
$ python manage.py runserver
Kiểm tra readme để biết thêm thông tin.
Khi ứng dụng đang chạy, hãy điều hướng đến http:// localhost:5000 / register và đăng ký người dùng mới. Lưu ý rằng sau khi đăng ký, ứng dụng sẽ tự động đăng nhập bạn và chuyển hướng bạn đến trang chính. Hãy xem xung quanh, sau đó chạy qua mã - cụ thể là bản thiết kế "người dùng".
Hủy máy chủ khi hoàn tất.
Cập nhật ứng dụng hiện tại
Mô hình
Đầu tiên, hãy thêm confirmed
trường User
của chúng tôi mô hình trong project / models.py :
class User(db.Model):
__tablename__ = "users"
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String, unique=True, nullable=False)
password = db.Column(db.String, nullable=False)
registered_on = db.Column(db.DateTime, nullable=False)
admin = db.Column(db.Boolean, nullable=False, default=False)
confirmed = db.Column(db.Boolean, nullable=False, default=False)
confirmed_on = db.Column(db.DateTime, nullable=True)
def __init__(self, email, password, confirmed,
paid=False, admin=False, confirmed_on=None):
self.email = email
self.password = bcrypt.generate_password_hash(password)
self.registered_on = datetime.datetime.now()
self.admin = admin
self.confirmed = confirmed
self.confirmed_on = confirmed_on
Lưu ý cách trường này mặc định là 'False'. Chúng tôi cũng đã thêm một confirmed_on
là trường [datetime
] (https://realpython.com/python-datetime/). Tôi cũng muốn bao gồm trường này để phân tích sự khác biệt giữa registered_on
và confirmed_on
ngày sử dụng phân tích theo nhóm.
Hãy hoàn toàn bắt đầu lại với cơ sở dữ liệu và quá trình di chuyển của chúng tôi. Vì vậy, hãy tiếp tục và xóa cơ sở dữ liệu, dev.sqlite , cũng như thư mục "di chuyển".
Quản lý lệnh
Tiếp theo, trong management.py , cập nhật create_admin
lệnh để tính đến các trường cơ sở dữ liệu mới:
@manager.command
def create_admin():
"""Creates the admin user."""
db.session.add(User(
email="[email protected]",
password="admin",
admin=True,
confirmed=True,
confirmed_on=datetime.datetime.now())
)
db.session.commit()
Đảm bảo nhập datetime
. Bây giờ, hãy tiếp tục và chạy lại các lệnh sau:
$ python manage.py create_db
$ python manage.py db init
$ python manage.py db migrate
$ python manage.py create_admin
register()
chức năng xem
Cuối cùng, trước khi có thể đăng ký lại người dùng, chúng ta cần thực hiện thay đổi nhanh chóng đối với register()
chức năng xem trong project / user / views.py …
Thay đổi:
user = User(
email=form.email.data,
password=form.password.data
)
Tới:
user = User(
email=form.email.data,
password=form.password.data,
confirmed=False
)
Có lý? Hãy nghĩ về lý do tại sao chúng ta muốn confirmed
mặc định thành False
.
Được chứ. Chạy lại ứng dụng. Điều hướng đến http:// localhost:5000 / register và đăng ký lại người dùng mới. Nếu bạn mở cơ sở dữ liệu SQLite của mình trong Trình duyệt SQLite, bạn sẽ thấy:
Vì vậy, người dùng mới mà tôi đã đăng ký, [email protected]
, không được xác nhận. Hãy thay đổi điều đó.
Thêm xác nhận email
Tạo mã thông báo xác nhận
Xác nhận email phải chứa một URL duy nhất mà người dùng chỉ cần nhấp vào để xác nhận tài khoản của mình. Tốt nhất, URL phải trông giống như thế này - http://yourapp.com/confirm/<id>
. Chìa khóa ở đây là id
. Chúng tôi sẽ mã hóa email người dùng (cùng với dấu thời gian) trong id
bằng cách sử dụng gói nguy hiểm của nó.
Tạo một tệp có tên là project / token.py và thêm mã sau:
# project/token.py
from itsdangerous import URLSafeTimedSerializer
from project import app
def generate_confirmation_token(email):
serializer = URLSafeTimedSerializer(app.config['SECRET_KEY'])
return serializer.dumps(email, salt=app.config['SECURITY_PASSWORD_SALT'])
def confirm_token(token, expiration=3600):
serializer = URLSafeTimedSerializer(app.config['SECRET_KEY'])
try:
email = serializer.loads(
token,
salt=app.config['SECURITY_PASSWORD_SALT'],
max_age=expiration
)
except:
return False
return email
Vì vậy, trong generate_confirmation_token()
chức năng chúng tôi sử dụng URLSafeTimedSerializer
để tạo mã thông báo bằng địa chỉ email có được trong quá trình đăng ký của người dùng. thực tế email được mã hóa trong mã thông báo. Sau đó, để xác nhận mã thông báo, trong confirm_token()
, chúng ta có thể sử dụng loads()
phương thức, lấy mã thông báo và thời hạn - có giá trị trong một giờ (3.600 giây) - làm đối số. Miễn là mã thông báo chưa hết hạn, thì nó sẽ trả về một email.
Đảm bảo thêm SECURITY_PASSWORD_SALT
vào cấu hình ứng dụng của bạn (BaseConfig()
):
SECURITY_PASSWORD_SALT = 'my_precious_two'
Cập nhật register()
chức năng xem
Bây giờ chúng ta hãy cập nhật register()
xem lại chức năng từ project / user / views.py :
@user_blueprint.route('/register', methods=['GET', 'POST'])
def register():
form = RegisterForm(request.form)
if form.validate_on_submit():
user = User(
email=form.email.data,
password=form.password.data,
confirmed=False
)
db.session.add(user)
db.session.commit()
token = generate_confirmation_token(user.email)
Ngoài ra, hãy đảm bảo cập nhật các mục nhập:
from project.token import generate_confirmation_token, confirm_token
Xử lý xác nhận email
Tiếp theo, hãy thêm một chế độ xem mới để xử lý xác nhận email:
@user_blueprint.route('/confirm/<token>')
@login_required
def confirm_email(token):
try:
email = confirm_token(token)
except:
flash('The confirmation link is invalid or has expired.', 'danger')
user = User.query.filter_by(email=email).first_or_404()
if user.confirmed:
flash('Account already confirmed. Please login.', 'success')
else:
user.confirmed = True
user.confirmed_on = datetime.datetime.now()
db.session.add(user)
db.session.commit()
flash('You have confirmed your account. Thanks!', 'success')
return redirect(url_for('main.home'))
Thêm cái này vào project / user / views.py . Ngoài ra, hãy đảm bảo cập nhật các mục nhập:
import datetime
Ở đây, chúng tôi gọi là confirm_token()
, chuyển vào mã thông báo. Nếu thành công, chúng tôi cập nhật người dùng, thay đổi email_confirmed
thuộc tính cho True
và đặt datetime
cho thời điểm xác nhận diễn ra. Ngoài ra, trong trường hợp người dùng đã trải qua quá trình xác nhận - và được xác nhận - thì chúng tôi sẽ thông báo cho người dùng về điều này.
Tạo mẫu email
Tiếp theo, hãy thêm một mẫu email cơ sở:
<p>Welcome! Thanks for signing up. Please follow this link to activate your account:</p>
<p><a href="{{ confirm_url }}">{{ confirm_url }}</a></p>
<br>
<p>Cheers!</p>
Lưu cái này dưới dạng active.html trong “dự án / mẫu / người dùng”. Thao tác này sử dụng một biến duy nhất được gọi là confirm_url
, sẽ được tạo trong register()
chức năng xem.
Gửi email
Hãy tạo một chức năng cơ bản để gửi email với một chút trợ giúp từ Flask-Mail, chức năng này đã được cài đặt và thiết lập trong project/__init__.py
.
Tạo một tệp có tên là email.py :
# project/email.py
from flask.ext.mail import Message
from project import app, mail
def send_email(to, subject, template):
msg = Message(
subject,
recipients=[to],
html=template,
sender=app.config['MAIL_DEFAULT_SENDER']
)
mail.send(msg)
Lưu cái này vào thư mục “dự án”.
Vì vậy, chúng ta chỉ cần chuyển một danh sách người nhận, một chủ đề và một mẫu. Chúng tôi sẽ giải quyết một chút về cài đặt cấu hình thư.
Cập nhật register()
chức năng xem trong dự án / user / views.py (một lần nữa!)
@user_blueprint.route('/register', methods=['GET', 'POST'])
def register():
form = RegisterForm(request.form)
if form.validate_on_submit():
user = User(
email=form.email.data,
password=form.password.data,
confirmed=False
)
db.session.add(user)
db.session.commit()
token = generate_confirmation_token(user.email)
confirm_url = url_for('user.confirm_email', token=token, _external=True)
html = render_template('user/activate.html', confirm_url=confirm_url)
subject = "Please confirm your email"
send_email(user.email, subject, html)
login_user(user)
flash('A confirmation email has been sent via email.', 'success')
return redirect(url_for("main.home"))
return render_template('user/register.html', form=form)
Thêm nhập sau:
from project.email import send_email
Ở đây, chúng tôi đang kết hợp mọi thứ lại với nhau. Về cơ bản, chức năng này hoạt động như một bộ điều khiển (trực tiếp hoặc gián tiếp) cho toàn bộ quá trình:
- Xử lý đăng ký ban đầu,
- Tạo mã thông báo và URL xác nhận,
- Gửi email xác nhận,
- Xác nhận flash,
- Đăng nhập người dùng và
- Chuyển hướng người dùng.
Bạn có nhận thấy _external=True
lý lẽ? Điều này thêm URL tuyệt đối đầy đủ bao gồm tên máy chủ và cổng (http:// localhost:5000, trong trường hợp của chúng tôi.)
Trước khi chúng tôi có thể kiểm tra điều này, chúng tôi cần thiết lập cài đặt thư của mình.
Thư
Bắt đầu bằng cách cập nhật BaseConfig()
trong project / config.py :
class BaseConfig(object):
"""Base configuration."""
# main config
SECRET_KEY = 'my_precious'
SECURITY_PASSWORD_SALT = 'my_precious_two'
DEBUG = False
BCRYPT_LOG_ROUNDS = 13
WTF_CSRF_ENABLED = True
DEBUG_TB_ENABLED = False
DEBUG_TB_INTERCEPT_REDIRECTS = False
# mail settings
MAIL_SERVER = 'smtp.googlemail.com'
MAIL_PORT = 465
MAIL_USE_TLS = False
MAIL_USE_SSL = True
# gmail authentication
MAIL_USERNAME = os.environ['APP_MAIL_USERNAME']
MAIL_PASSWORD = os.environ['APP_MAIL_PASSWORD']
# mail accounts
MAIL_DEFAULT_SENDER = '[email protected]'
Xem tài liệu Flask-Mail chính thức để biết thêm thông tin.
Nếu bạn đã có tài khoản GMAIL thì bạn có thể sử dụng tài khoản đó hoặc đăng ký tài khoản GMAIL thử nghiệm. Sau đó, đặt các biến môi trường tạm thời trong phiên trình bao hiện tại:
$ export APP_MAIL_USERNAME="foo"
$ export APP_MAIL_PASSWORD="bar"
Nếu tài khoản GMAIL của bạn có xác thực 2 bước, Google sẽ chặn nỗ lực này.
Bây giờ chúng ta hãy thử nghiệm!
Thử nghiệm đầu tiên
Khởi động ứng dụng và điều hướng đến http:// localhost:5000 / register. Sau đó đăng ký bằng một địa chỉ email mà bạn có quyền truy cập. Nếu mọi việc suôn sẻ, bạn sẽ có một email trong hộp thư đến của mình trông giống như sau:
Nhấp vào URL và bạn sẽ được đưa đến http:// localhost:5000 /. Đảm bảo rằng người dùng đang ở trong cơ sở dữ liệu, trường ‘đã xác nhận’ là True
và có datetime
được liên kết với confirmed_on
trường.
Tốt!
Xử lý quyền
Nếu bạn còn nhớ, ở phần đầu của hướng dẫn này, chúng tôi đã quyết định rằng “người dùng chưa được xác nhận có thể đăng nhập nhưng họ sẽ được chuyển hướng ngay đến một trang - hãy gọi tuyến đường là /unconfirmed
- nhắc nhở người dùng rằng họ cần xác nhận tài khoản của mình trước khi có thể truy cập ứng dụng ”.
Vì vậy, chúng ta cần-
- Thêm
/unconfirmed
tuyến đường - Thêm một chưa được xác nhận.html mẫu
- Cập nhật
register()
chức năng xem - Tạo một người trang trí
- Cập nhật navigation.html mẫu
Thêm /unconfirmed
tuyến đường
Thêm tuyến đường sau vào project / user / views.py :
@user_blueprint.route('/unconfirmed')
@login_required
def unconfirmed():
if current_user.confirmed:
return redirect('main.home')
flash('Please confirm your account!', 'warning')
return render_template('user/unconfirmed.html')
Bạn đã từng thấy mã tương tự trước đây, vì vậy hãy tiếp tục.
Thêm chưa được xác nhận.html mẫu
{% extends "_base.html" %}
{% block content %}
<h1>Welcome!</h1>
<br>
<p>You have not confirmed your account. Please check your inbox (and your spam folder) - you should have received an email with a confirmation link.</p>
<p>Didn't get the email? <a href="/">Resend</a>.</p>
{% endblock %}
Lưu cái này dưới dạng chưa được xác nhận.html trong “dự án / mẫu / người dùng”. Một lần nữa, tất cả điều này nên được thẳng thắn. Hiện tại, chúng tôi vừa thêm một URL giả để gửi lại email xác nhận. Chúng tôi sẽ giải quyết vấn đề này kỹ hơn.
Cập nhật register()
chức năng xem
Bây giờ chỉ cần thay đổi:
return redirect(url_for("main.home"))
Tới:
return redirect(url_for("user.unconfirmed"))
Vì vậy, sau khi email xác nhận được gửi, người dùng hiện được chuyển hướng đến /unconfirmed
tuyến đường.
Tạo người trang trí
# project/decorators.py
from functools import wraps
from flask import flash, redirect, url_for
from flask.ext.login import current_user
def check_confirmed(func):
@wraps(func)
def decorated_function(*args, **kwargs):
if current_user.confirmed is False:
flash('Please confirm your account!', 'warning')
return redirect(url_for('user.unconfirmed'))
return func(*args, **kwargs)
return decorated_function
Ở đây chúng ta có một chức năng cơ bản để kiểm tra xem người dùng có được xác nhận hay không. Nếu chưa được xác nhận, người dùng sẽ được chuyển hướng đến /unconfirmed
tuyến đường. Lưu cái này dưới dạng decorators.py trong thư mục "dự án".
Bây giờ, hãy trang trí profile()
chức năng xem:
@user_blueprint.route('/profile', methods=['GET', 'POST'])
@login_required
@check_confirmed
def profile():
# ... snip ...
Đảm bảo nhập trình trang trí:
from project.decorators import check_confirmed
Cập nhật navigation.html mẫu
Cuối cùng, cập nhật phần sau của navigation.html mẫu-
Thay đổi:
<ul class="nav navbar-nav">
{% if current_user.is_authenticated() %}
<li><a href="{{ url_for('user.profile') }}">Profile</a></li>
{% endif %}
</ul>
Tới:
<ul class="nav navbar-nav">
{% if current_user.confirmed and current_user.is_authenticated() %}
<li><a href="{{ url_for('user.profile') }}">Profile</a></li>
{% elif current_user.is_authenticated() %}
<li><a href="{{ url_for('user.unconfirmed') }}">Confirm</a></li>
{% endif %}
</ul>
Đã đến lúc kiểm tra lại!
Thử nghiệm thứ hai
Khởi động ứng dụng và đăng ký lại bằng địa chỉ email mà bạn có quyền truy cập. (Vui lòng xóa người dùng cũ mà bạn đã đăng ký trước đó khỏi cơ sở dữ liệu để sử dụng lại.) Bây giờ, bạn sẽ được chuyển hướng đến http:// localhost:5000 / chưa được xác nhận sau khi đăng ký.
Đảm bảo kiểm tra tuyến đường http:// localhost:5000 / profile. Điều này sẽ chuyển hướng bạn đến http:// localhost:5000 / chưa được xác nhận.
Hãy tiếp tục và xác nhận email, và bạn sẽ có quyền truy cập vào tất cả các trang. Bùng nổ!
Gửi lại email
Cuối cùng, hãy làm cho liên kết gửi lại hoạt động. Thêm chức năng xem sau vào project / user / views.py :
@user_blueprint.route('/resend')
@login_required
def resend_confirmation():
token = generate_confirmation_token(current_user.email)
confirm_url = url_for('user.confirm_email', token=token, _external=True)
html = render_template('user/activate.html', confirm_url=confirm_url)
subject = "Please confirm your email"
send_email(current_user.email, subject, html)
flash('A new confirmation email has been sent.', 'success')
return redirect(url_for('user.unconfirmed'))
Bây giờ, hãy cập nhật chưa được xác nhận.html mẫu:
{% extends "_base.html" %}
{% block content %}
<h1>Welcome!</h1>
<br>
<p>You have not confirmed your account. Please check your inbox (and your spam folder) - you should have received an email with a confirmation link.</p>
<p>Didn't get the email? <a href="{{ url_for('user.resend_confirmation') }}">Resend</a>.</p>
{% endblock %}
Thử nghiệm thứ ba
Bạn biết phải làm gì rồi đấy. Lần này, hãy nhớ gửi lại email xác nhận mới và kiểm tra liên kết. Nó sẽ hoạt động.
Cuối cùng, điều gì sẽ xảy ra nếu bạn gửi cho mình một vài liên kết xác nhận? Mỗi cái có hợp lệ không? Kiểm tra nó ra. Đăng ký người dùng mới, sau đó gửi một vài email xác nhận mới. Cố gắng xác nhận bằng email đầu tiên. Nó đã hoạt động? Nó nên. Điều này có ổn không? Bạn có nghĩ rằng những email khác đó sẽ hết hạn nếu một email mới được gửi đi không?
Thực hiện một số nghiên cứu về điều này. Và thử nghiệm các ứng dụng web khác mà bạn sử dụng. Họ xử lý hành vi đó như thế nào?
Cập nhật bộ thử nghiệm
Ổn thỏa. Vì vậy, đó là nó cho chức năng chính. Còn chúng tôi thì sao, chúng tôi cập nhật bộ thử nghiệm hiện tại vì nó bị hỏng.
Chạy các bài kiểm tra:
$ python manage.py test
Bạn sẽ thấy lỗi sau:
TypeError: __init__() takes at least 4 arguments (3 given)
Để sửa lỗi này, chúng tôi chỉ cần cập nhật setUp()
phương thức trong project / use.py :
def setUp(self):
db.create_all()
user = User(email="[email protected]", password="admin_user", confirmed=False)
db.session.add(user)
db.session.commit()
Bây giờ hãy chạy lại các bài kiểm tra. Tất cả sẽ trôi qua!
Kết luận
Rõ ràng là còn rất nhiều điều chúng ta có thể làm:
- Email văn bản phong phú và đơn giản - Chúng tôi nên gửi cả hai.
- Đặt lại email mật khẩu - Những email này sẽ được gửi đi cho những người dùng quên mật khẩu của họ.
- Quản lý người dùng - Chúng tôi nên cho phép người dùng cập nhật email và mật khẩu của họ và khi một email được thay đổi, email đó phải được xác nhận lại.
- Thử nghiệm - Chúng tôi cần viết nhiều thử nghiệm hơn để bao gồm các tính năng mới.
Tải xuống toàn bộ mã nguồn từ kho Github. Bình luận bên dưới nếu có câu hỏi. Xem phần 2.
Chúc bạn ngày lễ vui vẻ!