用户认证章节真够长的,不过涉及到的内容确实多,继续上一章节
这部分讲到的是新用户注册以后,需要在邮箱里面点击链接进行确认,这个在我们平时网站注册新用户的时候,经常会碰到
确认邮件中最简单的确认链接是http://www.example.com/auth/confirm/<id> 这种形式的URL,其中id 是数据库分配给用户的数字id。用户点击链接后,处理这个路由的视图函
数就将收到的用户id 作为参数进行确认,然后将用户状态更新为已确认。但这种实现方式显然不是很安全,只要用户能判断确认链接的格式,就可以随便指定URL中的数字,从而确认任意账户。解决方法是把URL 中的id 换成将相同信息安全加密后得到的令牌。
(venv) $ python manage.py shell
>>> from manage import app
>>> from itsdangerous import
TimedJSONWebSignatureSerializer as Serializer
>>> s = Serializer(app.config['SECRET_KEY'], expires_in = 3600) #expires_in表示这个秘钥令牌的有限时间是多久,秒为单位
>>> token = s.dumps({ 'confirm': 23 }) #dumps表示将{'confirm':23}转化成安全令牌
>>> token
'eyJhbGciOiJIUzI1NiIsImV4cCI6MTM4MTcxODU1OCwiaWF0IjoxMzgxNzE0OTU4fQ.ey ...'
>>> data = s.loads(token) #loads则表示解析这个令牌,等于反向解码
>>> data
{u'confirm': 23}
接着,有了这个验证的东西,我们起码要为我们的模型添加这样一个属性
class User(UserMixin,db.Model):
__tablename__='users'
id=db.Column(db.Integer,primary_key=True)
email=db.Column(db.String(64),unique=True,index=True)
username=db.Column(db.String(64),unique=True,index=True)
role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))
password_hash=db.Column(db.String(128))
confirmed = db.Column(db.Boolean,default=False) #默认属性是False,也就是未认证
def generate_confirmation_token(self,expiration=3600):
s=Serializer(current_app.config['SECRET_KEY'],expiration) #这句的作用不是很明白,为什么要导入秘钥,是不是使用这个功能必须要通过秘钥?
return s.dumps({'confirm':self.id})
def confirm(self,token):
s=Serializer(current_app.config['SECRET_KEY'])
try:
data=s.loads(token) #尝试赋值给data......始终没明白什么情况下赋值会失败?token没有内容???
except:
return False
if data.get('confirm') !=self.id: #如果数据内confirm对应的confirm值不是用户的唯一id号,则返回False
return False
self.confirmed = True
db.session.add(self)
return True
由于更新过数据库了,所以,需要新建一个脚本,并更新数据库
既然有了需要确认的这个新属性,那在注册之后,一般网站都会自动向你的邮箱发一份确认邮件,那这个功能就要添加在路由里面了
from ..email import send_email
@auth.route('/register',methods=['GET','POST'])
def register():
form=RegistrationForm()
if form.validate_on_submit():
user=User(email=form.email.data,username=form.username.data,password=form.password.data)
db.session.add(user) #这里就要把user添加进数据库了,因为只有这样,才能有唯一的id生成
db.session.commit()
token = user.generate_confirmation_token()
send_email(user.email,'Confirm Your Account','auth/email/confirm',user=user,token=token)
flash('A Confirmation email has been sent to you by email.')
return redirect(url_for('main.index'))
return render_template('auth/register.html',form=form)
之后,需要做的是,编辑邮件的内容,也就是你收到的邮件内容 auth/mail/confirm.html
<p>Dear {{ user.username }},</p>
<p>Welcome to <b>Flasky</b>!</p>
<p>To confirm your account please <a href="{{ url_for('auth.confirm', token=token, _external=True) }}">click here</a>.</p> #这里生成的是超链接按钮
<p>Alternatively, you can paste the following link in your browser's address bar:</p>
<p>{{ url_for('auth.confirm', token=token, _external=True) }}</p> #一般来说url_for生成的是相对路径,有了_external=True以后,就可以生成包括http的全部路径了
<p>Sincerely,</p> #然后上一行,通过<p>来将整段验证代码显示出来,和平时我们注册网站时收到的邮件一模一样
<p>The Flasky Team</p>
<p><small>Note: replies to this email address are not monitored.</small></p>
邮件内容也编辑好了,我们再来看一下,如果我们点击了确认连接,返回的页面应该是什么样的
由于每个用户的id不一样,所以说,生成的token不一样,故这里的连接肯定是动态地址的
我们来编辑auth/views.py
@auth.route('/confirm/<token>')
@login_required #保护路由,要求你必须是在登陆状态才能访问这个页面
def confirm(token):
if current_user.confirmed: #如果用户的状态已经是confirmed的了,那直接范围首页了
return redirect(url_for('main.index'))
if current_user.confirm: #如果用户通过这个页面的访问,调用confirm函数并返回True了,那成功验证
flash('You have confirmed your account. Thanks!')
else: #其他情况下,验证失败,并返回主页
flash('The confirmation link is invalid or has expired.')
return redirect(url_for('main.index'))
如我们平时访问的一些网站一样,当你还是个新手上路状态的账号时,你可能只能访问一些特定的区域,也做不了任何事。
这里就要用到前面章节说的钩子了,说实话,我还不是很理解钩子的原理,感觉需要去看上下文的源代码,不过先这么用起来吧。
对蓝本来说,before_request 钩子只能应用到属于蓝本的请求上。若想在蓝本中使用针对程序全局请求的钩子,必须使用before_app_request 修饰器。
@auth.before_app_request #这句的意思,字面上理解是,在app的request响应之前,先如何如何............
def before_request():
if current_user.is_authenticated \ #如果用户是认证过的
and not current_user.confirmed \ #但是confirmed属性是False的
and request.endpoint[:5] !='auth.'and request.endpoint !='static': #而且,request的网址不是以auth.和static开头的
return redirect(url_for('auth.unconfirmed')) #就返回到未确认的一个路由
@auth.route('/unconfirmed')
def unconfirmed():
if current_user.is_anonymous or current_user.confirmed: #如果用户是非普通用户(is_anonymous对普通用户返回False),或者已确认的,则返回主页
return redirect(url_for('main.index'))
return render_template('auth/unconfirmed.html') #不然,则进入未确认页面
同时满足以下3 个条件时,before_app_request 处理程序会拦截请求。
(1) 用户已登录(current_user.is_authenticated() 必须返回True)。
(2) 用户的账户还未确认。
(3) 请求的端点(使用request.endpoint 获取)不在认证蓝本中。访问认证路由要获取权
限,因为这些路由的作用是让用户确认账户或执行其他账户管理操作。
再来看一下未确认的页面unconfirmed.html如何渲染
{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% block title %}Flasky - Login{% endblock %}
{% block page_content %}
<div class="page-header">
<h1>You have not confirmed your account yet.</h1>
</div>
<p>
Before you can access this site you need to confirm your account . Check your inbox , you should have received an email with a confirmation link.
</p>
Need another confirmation email?
<a href = "{{url_for('auth.resend_confirmation',token=token, _external=True)}}">Click here to send again</a> #注意这里的写法,很容易写错的
{% endblock %}
效果如图
有时候,如果你的验证令牌已经过期了,那我们需要重新发送一份邮件来确认,那怎么办呢?
再做一个路由......来重新发送邮件
@auth.route('/confirm')
@login_required
def resend_confirmation():
token = current_user.generate_confirmation_token()
send_email(current_user.email, 'Confirm Your Account','auth/email/confirm', user=current_user, token=token)
flash('A new confirmation email has been sent to you by email')
return redirect(url_for('main.index'))
收到的Email效果如下,还挺有那么回事
确认完的邮件是这样的,不过页面内容还写的是未完成确认,不过应该不是大问题,渲染文件里面加个if语句应该就可以,后面再改吧,先这样。
这篇章终于快要结束了,还有最后一点管理用户,另外篇幅讲......
这认证用户一章节内容实在是太饱满了.....................................