Flask认证扩展
- Flask-Login:管理已登录用户的用户会话。
- Werkzeug:计算密码散列值并进行核对。
- itsdangerous:生成并核对加密安全令牌。
登陆会话
安装Flask扩展
pip install flask-login
若想使用Flask-Login扩展,必须实现以下方法:
Flask-Login要求实现的用户方法
方 法 | 说 明 |
---|---|
is_authenticated() | 如果用户已经登录,必须返回 True ,否则返回 False |
is_active() | 如果允许用户登录,必须返回 True ,否则返回 False 。如果要禁用账户,可以返回 False |
is_anonymous() | 对普通用户必须返回 False |
get_id() | 必须返回用户的唯一标识符,使用 Unicode 编码字符串 |
But**Flask-Login 提供了一个 UserMixin 类,其中包含这些方法的默认实现,且能满足大多数需求。同时上述的方法变成了实例的属性**(no parentheses)。
from flask.ext.login import LoginManager
#创建实例
login_manager = LoginManager()
#LoginManager 对象的 session_protection 属性可以设为 None 、 'basic' 或 'strong'设为 'strong' 时,Flask-Login 会记录客户端 IP地址和浏览器的用户代理信息,如果发现异动就登出用户。
login_manager.session_protection = 'strong'
#设置登录页面的端点
login_manager.login_view = 'auth.login'
#... ...
#注册
login_manager.init_app(app)
from flask.ext.login import UserMixin
#... ...
class User(UserMixin, db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
#必须指定回调函数
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))#should return None not raise an exception
视图函数
#“Post/ 重定向 /Get 模式”,提交登录密令的 POST 请求最后也做了重定向
#重定向的地址有两种:
#1、用户访问一个为授权的页面,会显示登录表单,Flask-login会把原地址保存在查询字符串的next参数中,通过flask.request.args.get('next')访问。
#2、回到主页面
@app.route('/login', methods=['GET', 'POST'])
def login():
# Here we use a class of some kind to represent and validate our
# client-side form data. For example, WTForms is a library that will
# handle this for us, and we use a custom LoginForm to validate.
form = LoginForm()
if form.validate_on_submit():
# Login and validate the user.
# user should be an instance of your `User` class
login_user(user)#flask-login中的函数,在用户会话中把用户标记为已登录。如果想要实现"Remember me"功能,只需要传递remembeer=True即可。此时:一个cokkie即可保存到用户的电脑上,并且,如果userID没有在会话时,Flask-Login将自动从cookie中恢复。并且这个cookie是防修改的,如果用户尝试修改,这个cookie会被服务器丢弃。
flask.flash('Logged in successfully.')
next = flask.request.args.get('next')
# next_is_valid should check if the user has valid
# permission to access the `next` url
if not next_is_valid(next):#检查用户是否有权限访问该next页面
return flask.abort(400)
return flask.redirect(next or flask.url_for('index'))
return flask.render_template('login.html', form=form)
一些视图函数需要登陆之后方可访问,可以使用修饰器:login_required
@app.route('/settings')
@login_required
def settings():
pass
模板中使用
可以使用代理:current_user来访问登陆的用户,且这个可以在任何模板中被访问。
{% if current_user.is_authenticated %}
Hi {{ current_user.name }}!
{% endif %}
定制登陆过程
默认情况,当用户在没有登陆企图访问login_required修饰的view时,Flask-login将flash消息(需要在模板中get_flashed_messages()获取)并重定向到login视图。如果没有设置,将会报401错误。
定制login的视图函数
login_manager.login_view = 'auth.login'
定制flash消息
login_manager.login_message = "请登录"
定制消息类型
login_manager.login_message_category = "info"
如果想要更深层的定制,需要实现修饰器函数* LoginManager.unauthorized_handler*
@login_manager.unauthorized_handler
def unauthorized():
# do stuff
return a_response
登出视图
from flask.ext.login import logout_user, login_required
@auth.route('/logout')
@login_required
def logout():
logout_user()#删除并重置用户会话,随后显示一条Flash消息
flash('You have been logged out.')
return redirect(url_for('main.index'))
密码安全性
Werkzeug 中的 security 模块能够很方便地实现密码散列值的计算。这一功能的实现只需要两个函数,分别用在注册用户和验证用户阶段。
• generate_password_hash(password, method= pbkdf2:sha1 ,salt_length=8) :这个函数将原始密码作为输入,以字符串形式输出密码的散列值,输出的值可保存在用户数据库中。method 和 salt_length 的默认值就能满足大多数需求。
• check_password_hash(hash, password) :这个函数的参数是从数据库中取回的密码散列值和用户输入的密码。返回值为 True 表明密码正确。
from werkzeug.security import generate_password_hash, check_password_hash
class User(db.Model):
# ...
password_hash = db.Column(db.String(128))
@property
def password(self):
raise AttributeError('password is not a readable attribute')
@password.setter
def password(self, password):
self.password_hash = generate_password_hash(password)
def verify_password(self, password):
return check_password_hash(self.password_hash, password)
加密令牌
对于某些特定类型的程序,有必要确认注册时用户提供的信息是否正确。常见要求是能通过提供的电子邮件地址与用户取得联系。
为验证电子邮件地址,用户注册后,程序会立即发送一封确认邮件。新账户先被标记成待确认状态,用户按照邮件中的说明操作后,才能证明自己可以被联系上。账户确认过程中,往往会要求用户点击一个包含确认令牌的特殊 URL 链接。
使用 itsdangerous 生成确认令牌
一般确认邮件的链接为:http://www.example.com/auth/confirm/<id>
其中 id 是数据库分配给用户的数字 id.用户点击链接后,处理这个路由的视图函数就将收到的用户 id 作为参数进行确认,然后将用户状态更新为已确认。但这种实现方式显然不是很安全.解决方法是把 URL 中的 id 换成将相同信息安全加密后得到的令牌。
Flask 使用加密的签名 cookie 保护用户会话,防止被篡改。这种安全的 cookie 使用 itsdangerous 包签名。同样的方法也可用于确认令牌上。
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
from flask import current_app
from . import db
class User(UserMixin, db.Model):
# ...
confirmed = db.Column(db.Boolean, default=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)
except:
return False
if data.get('confirm') != self.id:
return False
self.confirmed = True
db.session.add(self)
return True