W tym samouczku szczegółowo opisano, jak zweryfikować adresy e-mail podczas rejestracji użytkownika.
Zaktualizowano 30.04.2015 :Dodano obsługę Pythona 3.
W zakresie przepływu pracy, po zarejestrowaniu nowego konta przez użytkownika, wysyłany jest e-mail potwierdzający. Konto użytkownika jest oznaczone jako „niepotwierdzone”, dopóki użytkownik, no cóż, „potwierdzi” konto za pomocą instrukcji w e-mailu. Jest to prosty przepływ pracy, który stosuje większość aplikacji internetowych.
Jedną ważną rzeczą, którą należy wziąć pod uwagę, jest to, co mogą robić niepotwierdzeni użytkownicy. Innymi słowy, czy mają pełny dostęp do Twojej aplikacji, ograniczony/ograniczony dostęp, czy w ogóle nie mają dostępu? W przypadku aplikacji w tym samouczku niepotwierdzeni użytkownicy mogą się zalogować, ale są natychmiast przekierowywani na stronę przypominającą im, że muszą potwierdzić swoje konto, zanim będą mogli uzyskać dostęp do aplikacji.
Zanim zaczniemy, większość funkcjonalności, które będziemy dodawać, jest częścią rozszerzeń Flask-User i Flask-Security - co nasuwa pytanie, dlaczego po prostu nie korzystać z rozszerzeń? Cóż, przede wszystkim jest to okazja do nauki. Ponadto oba te rozszerzenia mają ograniczenia, takie jak obsługiwane bazy danych. Co by było, gdybyś chciał na przykład użyć RethinkDB?
Zacznijmy.
Podstawowa rejestracja kolby
Zaczniemy od schematu Flask, który obejmuje podstawową rejestrację użytkownika. Pobierz kod z repozytorium. Po utworzeniu i aktywowaniu środowiska wirtualnego uruchom następujące polecenia, aby szybko rozpocząć:
$ 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
Sprawdź plik readme, aby uzyskać więcej informacji.
Po uruchomieniu aplikacji przejdź do http://localhost:5000/register i zarejestruj nowego użytkownika. Zauważ, że po rejestracji aplikacja automatycznie loguje Cię i przekierowuje na stronę główną. Rozejrzyj się, a następnie przejrzyj kod – konkretnie schemat „użytkownika”.
Po zakończeniu zabij serwer.
Zaktualizuj bieżącą aplikację
Modele
Najpierw dodajmy confirmed
pole do naszego User
model w 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
Zwróć uwagę, że to pole ma domyślną wartość „Fałsz”. Dodaliśmy również confirmed_on
pole, które jest [datetime
] (https://realpython.com/python-datetime/). Lubię też uwzględnić to pole, aby przeanalizować różnicę między registered_on
i confirmed_on
daty przy użyciu analizy kohortowej.
Zacznijmy całkowicie od nowa z naszą bazą danych i migracjami. Więc śmiało usuń bazę danych dev.sqlite , a także folder „migracje”.
Zarządzaj poleceniem
Następnie w obrębie manage.py , zaktualizuj create_admin
polecenie, aby uwzględnić nowe pola bazy danych:
@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()
Pamiętaj, aby zaimportować datetime
. Teraz kontynuuj i ponownie uruchom następujące polecenia:
$ python manage.py create_db
$ python manage.py db init
$ python manage.py db migrate
$ python manage.py create_admin
register()
funkcja przeglądania
Na koniec, zanim będziemy mogli ponownie zarejestrować użytkownika, musimy dokonać szybkiej zmiany w register()
funkcja przeglądania w project/user/views.py …
Zmień:
user = User(
email=form.email.data,
password=form.password.data
)
Do:
user = User(
email=form.email.data,
password=form.password.data,
confirmed=False
)
Ma sens? Zastanów się, dlaczego chcemy domyślnie confirmed
na False
.
Dobra. Uruchom aplikację ponownie. Przejdź do http://localhost:5000/register i ponownie zarejestruj nowego użytkownika. Jeśli otworzysz bazę danych SQLite w przeglądarce SQLite, powinieneś zobaczyć:
Tak więc nowy użytkownik, którego zarejestrowałem, [email protected]
, nie jest potwierdzone. Zmieńmy to.
Dodaj potwierdzenie e-mail
Wygeneruj token potwierdzenia
Potwierdzenie e-mail powinno zawierać unikalny adres URL, który użytkownik po prostu musi kliknąć, aby potwierdzić swoje konto. Idealnie adres URL powinien wyglądać mniej więcej tak - http://yourapp.com/confirm/<id>
. Kluczem jest tutaj id
. Zamierzamy zakodować adres e-mail użytkownika (wraz ze znacznikiem czasu) w id
używając swojego niebezpiecznego pakietu.
Utwórz plik o nazwie project/token.py i dodaj następujący kod:
# 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
Tak więc w generate_confirmation_token()
używamy funkcji URLSafeTimedSerializer
wygenerowanie tokena z wykorzystaniem adresu e-mail otrzymanego podczas rejestracji użytkownika. rzeczywista e-mail jest zakodowany w tokenie. Następnie, aby potwierdzić token, w ramach confirm_token()
możemy użyć funkcji loads()
metoda, która jako argumenty przyjmuje token i wygaśnięcie - ważne przez godzinę (3600 sekund). Dopóki token nie wygaśnie, zwróci wiadomość e-mail.
Pamiętaj, aby dodać SECURITY_PASSWORD_SALT
do konfiguracji Twojej aplikacji (BaseConfig()
):
SECURITY_PASSWORD_SALT = 'my_precious_two'
Zaktualizuj register()
funkcja przeglądania
Teraz zaktualizujmy register()
wyświetl ponownie funkcję z 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)
Pamiętaj też o zaktualizowaniu importów:
from project.token import generate_confirmation_token, confirm_token
Obsługuj potwierdzenie e-mail
Następnie dodajmy nowy widok do obsługi potwierdzenia e-mailem:
@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'))
Dodaj to do project/user/views.py . Pamiętaj również o zaktualizowaniu importów:
import datetime
Tutaj nazywamy confirm_token()
funkcja, przekazując w tokenie. Jeśli się powiedzie, aktualizujemy użytkownika, zmieniając email_confirmed
atrybut na True
i ustawienie datetime
kiedy nastąpiło potwierdzenie. Ponadto w przypadku, gdy użytkownik przeszedł już proces potwierdzenia – i został potwierdzony – powiadamiamy o tym użytkownika.
Utwórz szablon wiadomości e-mail
Następnie dodajmy podstawowy szablon wiadomości e-mail:
<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>
Zapisz to jako activate.html w „projekt/szablony/użytkownik”. To zajmuje pojedynczą zmienną o nazwie confirm_url
, który zostanie utworzony w register()
funkcja przeglądania.
Wyślij e-mail
Stwórzmy podstawową funkcję do wysyłania e-maili z niewielką pomocą Flask-Mail, który jest już zainstalowany i skonfigurowany w project/__init__.py
.
Utwórz plik o nazwie 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)
Zapisz to w folderze „projekt”.
Po prostu musimy przekazać listę odbiorców, temat i szablon. Za chwilę zajmiemy się ustawieniami konfiguracji poczty.
Zaktualizuj register()
funkcja przeglądania w project/user/views.py (ponownie!)
@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)
Dodaj również następujący import:
from project.email import send_email
Tutaj składamy wszystko razem. Ta funkcja zasadniczo działa jako kontroler (bezpośrednio lub pośrednio) dla całego procesu:
- Obsługa pierwszej rejestracji,
- Wygeneruj token i adres URL potwierdzenia,
- Wyślij e-mail z potwierdzeniem,
- Potwierdzenie Flash,
- Zaloguj się użytkownika i
- Przekieruj użytkownika.
Czy zauważyłeś _external=True
argument? Spowoduje to dodanie pełnego bezwzględnego adresu URL, który zawiera nazwę hosta i port (w naszym przypadku http://localhost:5000).
Zanim będziemy mogli to przetestować, musimy skonfigurować nasze ustawienia poczty.
Poczta
Zacznij od aktualizacji BaseConfig()
w 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]'
Sprawdź oficjalną dokumentację Flask-Mail, aby uzyskać więcej informacji.
Jeśli masz już konto GMAIL, możesz z niego skorzystać lub zarejestrować testowe konto GMAIL. Następnie ustaw tymczasowo zmienne środowiskowe w bieżącej sesji powłoki:
$ export APP_MAIL_USERNAME="foo"
$ export APP_MAIL_PASSWORD="bar"
Jeśli Twoje konto GMAIL ma uwierzytelnianie dwuetapowe, Google zablokuje próbę.
Teraz przetestujmy!
Pierwszy test
Uruchom aplikację i przejdź do http://localhost:5000/register. Następnie zarejestruj się za pomocą adresu e-mail, do którego masz dostęp. Jeśli wszystko poszło dobrze, powinieneś mieć e-mail w swojej skrzynce odbiorczej, który wygląda mniej więcej tak:
Kliknij adres URL i powinieneś przejść do http://localhost:5000/. Upewnij się, że użytkownik jest w bazie danych, pole „potwierdzone” to True
i jest datetime
powiązane z confirmed_on
pole.
Fajnie!
Obsługuj uprawnienia
Jeśli pamiętasz, na początku tego samouczka zdecydowaliśmy, że „niepotwierdzeni użytkownicy mogą się logować, ale powinni zostać natychmiast przekierowani na stronę – nazwijmy trasę /unconfirmed
- przypominanie użytkownikom, że muszą potwierdzić swoje konto, zanim będą mogli uzyskać dostęp do aplikacji”.
Więc musimy...
- Dodaj
/unconfirmed
trasa - Dodaj niepotwierdzony.html szablon
- Zaktualizuj
register()
funkcja przeglądania - Utwórz dekoratora
- Zaktualizuj navigation.html szablon
Dodaj /unconfirmed
trasa
Dodaj następującą trasę do 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')
Widziałeś już podobny kod, więc przejdźmy dalej.
Dodaj niepotwierdzony.html szablon
{% 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 %}
Zapisz to jako unconfirmed.html w „projekt/szablony/użytkownik”. Znowu wszystko powinno być proste. Na razie właśnie dodaliśmy fałszywy adres URL do ponownego wysłania wiadomości e-mail z potwierdzeniem. Zajmiemy się tym dalej.
Zaktualizuj register()
funkcja przeglądania
Teraz po prostu zmień:
return redirect(url_for("main.home"))
Do:
return redirect(url_for("user.unconfirmed"))
Tak więc, po wysłaniu e-maila z potwierdzeniem, użytkownik jest teraz przekierowywany do /unconfirmed
trasa.
Utwórz dekoratora
# 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
Tutaj mamy podstawową funkcję sprawdzania, czy użytkownik nie jest potwierdzony. Jeśli nie zostanie potwierdzony, użytkownik zostanie przekierowany do /unconfirmed
trasa. Zapisz to jako decorators.py w katalogu „projekt”.
Teraz udekoruj profile()
funkcja przeglądania:
@user_blueprint.route('/profile', methods=['GET', 'POST'])
@login_required
@check_confirmed
def profile():
# ... snip ...
Pamiętaj, aby zaimportować dekorator:
from project.decorators import check_confirmed
Zaktualizuj navigation.html szablon
Na koniec zaktualizuj następującą część navigation.html szablon-
Zmień:
<ul class="nav navbar-nav">
{% if current_user.is_authenticated() %}
<li><a href="{{ url_for('user.profile') }}">Profile</a></li>
{% endif %}
</ul>
Do:
<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>
Czas ponownie przetestować!
Drugi test
Uruchom aplikację i zarejestruj się ponownie, używając adresu e-mail, do którego masz dostęp. (Możesz usunąć z bazy danych starego użytkownika, którego zarejestrowałeś wcześniej, aby użyć go ponownie.) Teraz po rejestracji powinieneś zostać przekierowany na stronę http://localhost:5000/unconfirmed.
Przetestuj trasę http://localhost:5000/profile. Powinno to przekierować Cię do http://localhost:5000/unconfirmed.
Śmiało i potwierdź wiadomość e-mail, a będziesz mieć dostęp do wszystkich stron. Bum!
Wyślij ponownie e-mail
Na koniec sprawmy, aby link do ponownego wysłania działał. Dodaj następującą funkcję widoku do 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'))
Teraz zaktualizuj unconfirmed.html szablon:
{% 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 %}
Trzeci test
Znasz zasady. Tym razem wyślij ponownie nowy e-mail z potwierdzeniem i przetestuj link. Powinno działać.
Na koniec, co się stanie, jeśli wyślesz sobie kilka linków potwierdzających? Czy każdy jest ważny? Przetestuj to. Zarejestruj nowego użytkownika, a następnie wyślij kilka nowych e-maili potwierdzających. Spróbuj potwierdzić w pierwszym e-mailu. Zadziałało? Powinno. Czy to w porządku? Czy uważasz, że te inne e-maile powinny wygasnąć, jeśli zostanie wysłany nowy?
Zrób trochę badań na ten temat. Przetestuj inne aplikacje internetowe, z których korzystasz. Jak radzą sobie z takim zachowaniem?
Zaktualizuj pakiet testowy
W porządku. A więc to tyle, jeśli chodzi o główną funkcjonalność. Co powiesz na zaktualizowanie obecnego zestawu testowego, ponieważ jest, no cóż, zepsuty.
Uruchom testy:
$ python manage.py test
Powinieneś zobaczyć następujący błąd:
TypeError: __init__() takes at least 4 arguments (3 given)
Aby to naprawić, wystarczy zaktualizować setUp()
metoda w project/util.py :
def setUp(self):
db.create_all()
user = User(email="[email protected]", password="admin_user", confirmed=False)
db.session.add(user)
db.session.commit()
Teraz ponownie uruchom testy. Wszystko powinno minąć!
Wniosek
Możemy zrobić znacznie więcej:
- E-maile sformatowane w porównaniu ze zwykłym tekstem – powinniśmy wysyłać oba.
- E-mail dotyczący resetowania hasła — powinien zostać wysłany do użytkowników, którzy zapomnieli hasła.
- Zarządzanie użytkownikami — Powinniśmy umożliwić użytkownikom aktualizowanie ich adresów e-mail i haseł, a po zmianie adresu e-mail należy to ponownie potwierdzić.
- Testowanie — musimy napisać więcej testów, aby uwzględnić nowe funkcje.
Pobierz cały kod źródłowy z repozytorium Github. Komentarz poniżej z pytaniami. Sprawdź część 2.
Wesołych świąt!