日韩久久久精品,亚洲精品久久久久久久久久久,亚洲欧美一区二区三区国产精品 ,一区二区福利

在Python的Flask框架中驗(yàn)證注冊(cè)用戶(hù)的Email的方法

系統(tǒng) 1880 0

本教程詳細(xì)介紹在用戶(hù)注冊(cè)過(guò)程中如何去驗(yàn)證他們的email地址。

工作流程上來(lái)講,在用戶(hù)注冊(cè)一個(gè)新賬戶(hù)后會(huì)寄送一個(gè)確認(rèn)信。直到用戶(hù)按指示完成了郵件中的“驗(yàn)證”,否則他們的賬戶(hù)會(huì)一直處于“未驗(yàn)證”狀態(tài)。這是大多數(shù)網(wǎng)絡(luò)應(yīng)用會(huì)采用的工作流程。

這當(dāng)中很重要的一件事就是,未驗(yàn)證的用戶(hù)有什么權(quán)限?或者說(shuō),對(duì)于你的應(yīng)用,他們是有全部權(quán)限呢,還是被限制的權(quán)限呢,還是根本沒(méi)有權(quán)限?對(duì)于本教程中的應(yīng)用,未驗(yàn)證用戶(hù)會(huì)在登錄后進(jìn)到一個(gè)頁(yè)面,會(huì)提醒他們只有驗(yàn)證了賬戶(hù)才可以進(jìn)入應(yīng)用。

開(kāi)始前說(shuō)明一下,很多我們要增加的功能是Flask-用戶(hù)和Flask-安全的擴(kuò)展部分――問(wèn)題來(lái)了,為什么不直接用這兩個(gè)擴(kuò)展呢?嗯,首先,這是個(gè)學(xué)習(xí)機(jī)會(huì)。同時(shí),這倆擴(kuò)展都有局限性,比如支持的數(shù)據(jù)庫(kù)。要是你想用RethinkDB怎么辦呢?
我們開(kāi)始吧

Flask基本注冊(cè)

我們將會(huì)要開(kāi)始一Flask樣例,這包括了用戶(hù)基本注冊(cè)。從這個(gè)github倉(cāng)庫(kù)獲取代碼庫(kù)。一旦你創(chuàng)建和激活了virtualenv,運(yùn)行下面的命令來(lái)快速開(kāi)始:

            
$ 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

          

在應(yīng)用運(yùn)行的狀態(tài)下,訪問(wèn)http://localhost:5000/register頁(yè)面,注冊(cè)一個(gè)新用戶(hù)。注意,注冊(cè)之后應(yīng)用會(huì)自動(dòng)登陸,引導(dǎo)你進(jìn)入主頁(yè)面。大概看一下,然后運(yùn)行代碼――尤其是user這個(gè)藍(lán)圖(Blueprint是flask的一個(gè)概念)。

完成時(shí)停止服務(wù)器。

更新當(dāng)前應(yīng)用

模型

首先,我們來(lái)在我們project/models.py中的User模型里添加上confirmed字段:

            
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

          

注意此區(qū)域是怎樣默認(rèn)成“False”的。還要添加個(gè)confirmed_on字段,那是個(gè)datetime。為了用隊(duì)列分析來(lái)分析registered_on和confirmed_on日期的不同,所以我想包含這個(gè)datetime。

讓我們完全從頭開(kāi)始創(chuàng)建數(shù)據(jù)庫(kù)并遷移吧!所以,先刪除數(shù)據(jù)庫(kù)dev.sqlite,以及“遷移”文件夾。

控制命令

接下來(lái),在manage.py中,更新create_admin命令,使新的數(shù)據(jù)庫(kù)字段生效:

            
@manager.command
def create_admin():
 """Creates the admin user."""
 db.session.add(User(
 email="ad@min.com",
 password="admin",
 admin=True,
 confirmed=True,
 confirmed_on=datetime.datetime.now())
 )
 db.session.commit()

          

要確保導(dǎo)入datetime。現(xiàn)在,先再一次運(yùn)行下面的指令:

            
$ python manage.py create_db
$ python manage.py db init
$ python manage.py db migrate
$ python manage.py create_admin

          

register()視圖函數(shù)

最后,在我們?cè)俅巫?cè)用戶(hù)之前,我們需要改一下project/user/views.py中的register()視圖函數(shù)……

            
user = User(
 email=form.email.data,
 password=form.password.data
)

          

改成下面的:

            
user = User(
 email=form.email.data,
 password=form.password.data,
 confirmed=False
)

          

明白了嗎?思考下為什么要把confirmed默認(rèn)成False。

嗯好。再運(yùn)行一遍應(yīng)用。轉(zhuǎn)入到http://localhost:5000/register,再注冊(cè)一個(gè)新用戶(hù)。如果你在SQLite瀏覽器中打開(kāi)了你的SQLite數(shù)據(jù)庫(kù),你會(huì)看到:

在Python的Flask框架中驗(yàn)證注冊(cè)用戶(hù)的Email的方法_第1張圖片

那么,這個(gè)我新注冊(cè)的用戶(hù),michael@realpython.com沒(méi)有被驗(yàn)證。讓我們驗(yàn)證它。

添加email驗(yàn)證

產(chǎn)生驗(yàn)證令牌

郵件驗(yàn)證應(yīng)包含一個(gè)特殊URL,使得用戶(hù)只需簡(jiǎn)單地點(diǎn)擊它,即可驗(yàn)證他/她的賬戶(hù)。理想情況下,這個(gè)URL應(yīng)該看起來(lái)像這樣?Chttp://yourapp.com/confirm/ 。這里的關(guān)鍵是它id。在這個(gè)id中用itsdangerous包,編碼用戶(hù)的郵件(包含時(shí)間戳)。

創(chuàng)建一個(gè)叫project/token.py的文件,添加下面的代碼:

            
# 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

          

所以在generate_confirmation_token()函數(shù)中,通過(guò)URLSafeTimedSerializer用在用戶(hù)注冊(cè)時(shí)得到的email地址生成一個(gè)令牌。那個(gè)_真實(shí)的_email在令牌中被編了碼。確認(rèn)令牌之后,在confirm_token()函數(shù)中,我們可以用loads()方法,它接管令牌和其過(guò)期時(shí)間――一個(gè)小時(shí)(3600秒)內(nèi)有效――作為參數(shù)。只要令牌沒(méi)過(guò)期,那它就會(huì)返回一個(gè)email。

在應(yīng)用的配置中(BaseConfig()),確保添加SECURITY_PASSWORD_SALT:

            
SECURITY_PASSWORD_SALT = 'my_precious_two'

          

更新register()視圖函數(shù)

現(xiàn)在再?gòu)膒roject/user/views.py更新下register()視圖函數(shù):

            
@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)

          

也是,要確保更新了這些導(dǎo)入模塊:

            
from project.token import generate_confirmation_token, confirm_token

          

處理Email驗(yàn)證

接下來(lái),添加個(gè)新的視圖來(lái)解決email驗(yàn)證:

            
@user_blueprint.route('/confirm/
            
              ')
@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'))

            
          

把這個(gè)添加到project/user/views.py。同樣,確保更新了這些導(dǎo)入:

import datetime
現(xiàn)在我們通過(guò)令牌調(diào)用confirm_token()函數(shù)。如果成功,我們更新用戶(hù),把email_confirmed屬性改成True, 設(shè)置datetime為驗(yàn)證發(fā)生的時(shí)間。還有,要是用戶(hù)已經(jīng)進(jìn)行過(guò)一遍驗(yàn)證過(guò)程了――而且已經(jīng)驗(yàn)證了――我們要提醒用戶(hù)這點(diǎn)。

創(chuàng)建email模板

接著,添加一個(gè)基礎(chǔ)email模板:

            

Welcome! Thanks for signing up. Please follow this link to activate your account:

{{ confirm_url }}


Cheers!

把這個(gè)在“project/templates/user”中存為activate.html。這用了一個(gè)簡(jiǎn)單叫confirm_url的變量,它會(huì)在register()視圖函數(shù)中被創(chuàng)建。

發(fā)送郵件

通過(guò)已經(jīng)安裝了而且設(shè)置在project/__init__.py中的Flask-Mail的一點(diǎn)兒幫助來(lái)創(chuàng)建一個(gè)基本函數(shù)來(lái)發(fā)送郵件。

創(chuàng)建一個(gè)叫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)

          

在“project”文件夾中存一下。

所以,我們只要簡(jiǎn)單地去處理收件人列表,主題,模板即可。我們會(huì)一點(diǎn)一點(diǎn)處理郵件配置的設(shè)置。

在project/user/views.py中(再次!)更新register()視圖函數(shù)

            
@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)

          

還要添加下面的導(dǎo)入模塊:

from project.email import send_email
我們?cè)谶@里將所有的東西整合到一起。這個(gè)函數(shù)基本功能是作為控制器(直接或間接):

  • 處理最初注冊(cè),
  • 產(chǎn)生令牌和確認(rèn)URL,
  • 寄送確認(rèn)email,
  • 快速驗(yàn)證,
  • 用戶(hù)登入,
  • 更改用戶(hù)。

你注意到_external=True參數(shù)了嗎?這增加了包含了hostname和port(在我們情況中,http://localhost:5000)的完整URL。

在我們測(cè)試這個(gè)之前,我們要去設(shè)定郵件設(shè)置。

郵件

在project/config.py中更新BaseConfig():

            
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 = 'from@example.com'

          

查看 official Flask-Mail documentation 以得到更多信息
如果你已經(jīng)有了GMAIL賬號(hào),那你可以用它或者注冊(cè)一個(gè)測(cè)試用GMAIL賬戶(hù)。然后把環(huán)境變量暫時(shí)設(shè)置在當(dāng)前shell中:

            
$ export APP_MAIL_USERNAME="foo"
$ export APP_MAIL_PASSWORD="bar"

          

如果你GMAIL賬號(hào)有兩步授權(quán), Google會(huì)屏蔽掉它。
現(xiàn)在開(kāi)始測(cè)試!

第一個(gè)測(cè)試

打開(kāi)應(yīng)用,轉(zhuǎn)入到 http://localhost:5000/register。然后用你能登陸的email地址注冊(cè)。順利的話,你應(yīng)該會(huì)收到封email,看起來(lái)像這樣:

在Python的Flask框架中驗(yàn)證注冊(cè)用戶(hù)的Email的方法_第2張圖片

點(diǎn)擊URL,你會(huì)轉(zhuǎn)到 http://localhost:5000/。保證用戶(hù)在數(shù)據(jù)庫(kù)里,那‘confirmed'字段是True,有個(gè)datetime和confirmed_on字段綁定在一起。

好!

處理許可

如果你記得,在教程的開(kāi)始部分,我們決定了“未驗(yàn)證用戶(hù)可以登錄但是他們會(huì)立刻被轉(zhuǎn)入一個(gè)頁(yè)面――我們稱(chēng)之為/unconfirmed路徑――提醒用戶(hù)需要驗(yàn)證賬戶(hù)才能使用應(yīng)用”。

所以,我們要――

  • 添加/unconfirmed路徑
  • 添加unconfirmed.html模板
  • 更新register()視圖函數(shù)
  • 創(chuàng)建裝飾器
  • 更新navigation.html模板
  • 添加/unconfirmed路徑

添加下面的路徑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')

          

你看過(guò)類(lèi)似的代碼,所以我們繼續(xù)。

添加unconfirmed.html模板

            
{% extends "_base.html" %}
 
{% block content %}
 

            

Welcome!


You have not confirmed your account. Please check your inbox (and your spam folder) - you should have received an email with a confirmation link.

Didn't get the email? Resend .

{% endblock %}

在“project/templates/user”中,把這個(gè)存為 unconfirmed.html 。這次應(yīng)該直截了當(dāng)。現(xiàn)在,為了重新寄送驗(yàn)證email,就只加了一個(gè)假的URL。我們接下來(lái)會(huì)解決它的。

更新register()視圖函數(shù)

現(xiàn)在簡(jiǎn)單地把:

            
return redirect(url_for("main.home"))

          

變成:

            
return redirect(url_for("user.unconfirmed"))

          

所以,送完驗(yàn)證email后,用戶(hù)會(huì)進(jìn)入/unconfirmed路徑。

創(chuàng)建裝飾器

            
# 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

          

這里我們用一個(gè)基本函數(shù)去檢查用戶(hù)是否驗(yàn)證。如果未驗(yàn)證,用戶(hù)會(huì)進(jìn)入/unconfirmed路徑。在“project”目錄中,把這個(gè)存為decorators.py。

現(xiàn)在裝飾 profile() 視圖函數(shù):

            
@user_blueprint.route('/profile', methods=['GET', 'POST'])
@login_required
@check_confirmed
def profile():
 ... snip ...

          

確保導(dǎo)入裝飾器:

            
from project.decorators import check_confirmed

          

更新 navigation.html 模板

最后,更新navigation.html模板接下來(lái)的部分――

把:

            
          

變成:

            
          

又該測(cè)試了!

第二次測(cè)試

打開(kāi)應(yīng)用,再次用能登陸的email地址注冊(cè)。(可以隨便從數(shù)據(jù)庫(kù)刪除你之前注冊(cè)的老用戶(hù),這樣可以再用一遍)現(xiàn)在,注冊(cè)完會(huì)轉(zhuǎn)入http://localhost:5000/unconfirmed。

確保測(cè)試了http://localhost:5000/profile路徑。這會(huì)使你轉(zhuǎn)到http://localhost:5000/unconfirmed。

驗(yàn)證email,你就會(huì)有完整頁(yè)面的權(quán)限了。快去把!

重寄送email

最后,來(lái)做重新寄送的鏈接。增加下面的視圖函數(shù)到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'))

          

現(xiàn)在更新unconfirmed.html 模板:

            
{% extends "_base.html" %}
 
{% block content %}
 

            

Welcome!


You have not confirmed your account. Please check your inbox (and your spam folder) - you should have received an email with a confirmation link.

Didn't get the email? Resend .

{% endblock %}

第三次測(cè)試

你知道這次的演練內(nèi)容了,這次保證去重新寄送一個(gè)新的確認(rèn)email,測(cè)試鏈接。應(yīng)該沒(méi)問(wèn)題。

最后,如果你給自己發(fā)好幾個(gè)驗(yàn)證信,會(huì)發(fā)生什么? 每封都有效嗎?測(cè)試一下。注冊(cè)個(gè)新用戶(hù),寄送一些新的驗(yàn)證信。驗(yàn)證第一封試試。它可以嗎?它應(yīng)該可以。這樣行嗎?你是否認(rèn)為要是新的寄出,其他email應(yīng)該失效呢?

對(duì)這種事做些調(diào)查。測(cè)試下你用的其他網(wǎng)頁(yè)應(yīng)用。它們是怎么處理這種行為的?

更新測(cè)試套件

好的。這是為了主要功能。我們更新當(dāng)前測(cè)試套件怎么樣?因?yàn)樗牛袉?wèn)題。

運(yùn)行測(cè)試:

            
$ python manage.py test

          

你會(huì)看到下面的錯(cuò)誤:

            
TypeError: __init__() takes at least 4 arguments (3 given)

          

要去改正它,只需要在project/util.py中更新setUp()方法:

            
def setUp(self):
 db.create_all()
 user = User(email="ad@min.com", password="admin_user", confirmed=False)
 db.session.add(user)
 db.session.commit()
          


測(cè)試之前,在tests/test_models.py中,注釋一下test_user_registration()測(cè)試,因?yàn)槲覀儾幌霝榱诉@個(gè)測(cè)試真 的發(fā)送一封郵件。

現(xiàn)在再測(cè)試一下。應(yīng)該全能通過(guò)!

結(jié)論

還可以做更多的:

  • 富文本 vs. 純文本email――應(yīng)該都寄出。
  • 重設(shè)定密碼email――當(dāng)用戶(hù)忘記密碼時(shí),這些應(yīng)該被寄出。
  • 用戶(hù)管理――應(yīng)該允許用戶(hù)更新email和密碼,當(dāng)email變了的話,應(yīng)該重新驗(yàn)證一遍。
  • 測(cè)試――需要寫(xiě)更多測(cè)試來(lái)涉及更多新功能,包含用mock/patch更新的test_user_registration(),來(lái)防止實(shí)際email被發(fā)送。


更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號(hào)聯(lián)系: 360901061

您的支持是博主寫(xiě)作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對(duì)您有幫助就好】

您的支持是博主寫(xiě)作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長(zhǎng)會(huì)非常 感謝您的哦!!!

發(fā)表我的評(píng)論
最新評(píng)論 總共0條評(píng)論
主站蜘蛛池模板: 沁阳市| 山阴县| 辰溪县| 西盟| 手游| 廉江市| 安丘市| 武隆县| 南康市| 东阿县| 麦盖提县| 砚山县| 洛扎县| 博罗县| 林甸县| 班戈县| 黑河市| 汤原县| 青浦区| 苍山县| 江门市| 阳朔县| 东莞市| 禄劝| 革吉县| 江口县| 焉耆| 商丘市| 武威市| 巴楚县| 会理县| 达州市| 高州市| 穆棱市| 永平县| 大悟县| 山阴县| 濮阳市| 安福县| 洛南县| 资溪县|