flask有关认证的扩展
werkzeug计算密码散列值
在数据库中保存用户密码时,处于安全考虑,我们储存的用户密码加密后的密码散列值.
werkzeug中的security模块中提供的两个方法可以很方便的实现这个功能.
- generate_password_hash(password, method=pdkdf2:sha1, salt_length=8), 该方法输入为原始密码,输出是字符串形式的密码散列值.可以用在用户注册的时候,数据库中保存用户密码散列值.method, salt_length的默认值就能满足大部分需求.
- check_password_hash(hash, password),这个函数的参数是数据库中存储的密码散列值和用户提供的密码,两者进行核对,如果密码正确则返回True,否则,返回False.
flask-login认证用户
flask-ligin使用方法
- 要想使用flask-login扩展,user模型中必须要实现四个方法:
这四个方法可以在User模型中实现,也可以继承flask-login中的UserMixin类来实现,UserMixin类中包含了以上四种方法的默认实现,能够满足大多数
需求.在UserMixin类中,这四种方法都添加了@property装饰器,将方法转化成了属性.例如:user.is_authenticated而不用user.is_authenticated()
flask-login要求程序实现一个回调函数,使用指定的标识符加载用户.函数的实现如下:
from . import login_manager @login_manager.user_loader def load_user(user_id): return User.query.ger(int(user_id))
加载用户的回调函数接受以Unicode字符串形式表示的用户标识符.如果能找到用户,则返回用户对象,否则返回None.
flask-login 在工厂函数中的初始化方法:
from flask import Flask from flask.ext.login import LoginManager # 创建LoginManager实例对象 login_manager = LoginManager() # 设置用户会话保护等级.strong为记录客户端ip和浏览器用户代理信息,如发现异常就退出登录 login_manager.session_protection = 'strong' # 设置登录页面的端点.'蓝本名.视图函数名' login_manager.login_view = 'auth.login'
def create_app('config_name'): app = Flask() app.config.from_object('config_name') login_manager.init_app(app) # 注册蓝图 ... return app
flask-login模块相关方法
- login_user(user, remenber=False, force=False, fresh=True)
当用户登录以后,如果需要记录用户的状态则则可以调用 login_user函数记录当前登录的用户。
login_user主要的作用就是将user的id加入到session中。remember参数是关闭浏览器是否需要重新登录.
当用户访问未授权的URL时会显示登录表单,Flask-Login会把原地址保存在查询字符串的next参数中,这个参数可从request.args字典中读取.
- logout_user()
logout_user()函数,删除并重设用户会话.
当中某些操作需要用户登录的时候,就需要用到装饰器login_required。
@main.route('/post') @login_required def post(): pass
current_user记录了当前登录(认证)用户的信息,提供了一个访问和表示当前登录的对象。
current_user对用户实例进行了封装,如果想获得真正的用户对象,可以通过current_user._get_current_object()获取.
使用itsdangerous生成确认令牌
itsdangerous提供了多种令牌生成的方法,其中TimedJSONWebSignatureSerializer类可以生成一个具有过期时间的JSON WEB签名.这个类的构造函数的参数是一个密钥.
该类提供了dumps()和loads()方法:
dumps()方法为指定的数据生成一个加密签名,然后对数据和签名进行序列化,生成令牌字符串.该方法包含两个参数,第一个是要加密的数据,第二个是过期时间expires_in.单位是秒.
loads()方法,其唯一的参数是令牌字符串,起作用是,将加密的令牌字符串转化为原始字符串.其中可以检测签名及过期时间.如果令牌不正确或者过期,则抛出异常.
确认用户账户的示例如下:
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer from flask import current_app Class User(db.Model): ... def generate_confirm_token(self, expiration=3600): s = Serializer('secret key', expiration) return s.dumps({'confirm': self.id) def confirm(self, token) s = Serializer('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) db.session.commit() return True
flask-mail发送邮件
在用户确认系统中,可以利用flask-mail给注册的用户发送确认邮件,只有在用户确认后,才算是真正的注册完成.
初始化flask-mail(在函数工厂中初始化)
from flask import Flask from flask.ext.mail import Mail mail = Mail() def create_app('config_name'): app = Flask(__name__) ... mail.init_app(app)
配置
flask-mail 连接到简单的邮件传输协议(Simple Mail Transfer Protocol, SMTP)服务器,把邮件交个这个服务器发送.
在发送邮件之前,必须配置SMTP服务器,配置参数如下(以163为例):
MAIL_SERVER = SMTP.163.com # 服务器主机,一般SMTP.xxx.com形式,比如SMTP.qq.com MAIL_PORT = 465 # 163服务器端口,SSL启用端口一般是465,不启用端口是25 # MAIL_USE_TLS = 25 # 启用传输层安全协议(Transport Secrity) MAIL_USE_SSL = 465 # 启用安全套阶层协议(Security Socket) MAIL_USERNAME = xxx.163.com # 邮箱账户用户名 MAIL_PASSWORD = xxxxxx # 163客户端授权码, 不是登录密码, 授权码可以在邮箱设置里查找
发送邮件
from flask.ext.mail import Message from . import mail # subject是邮件的主题, sender是发送方邮箱,recipients是接收方邮箱列表, msg.body是邮件正文,msg.body是邮件的html正文 msg = Message(subject, sender=xxx, recipients=[xxx]) msg.body = 'test body' msg.html = '<b>html</b> body' # 在flask中,flask-mail中的mail()函数使用的current_app,在shell环境中运行,必须激活的程序上下文中执行 with app.app_context(): mail.send(msg)
异步发送邮件
异步发送电子邮件可以将发送电子邮件的程序移到后台线程中;代码如下
from flask.ext.mail import Message from . import mail from threading import Thread def send_async_email(app, msg) with app.app_context(): mail.send(msg) def send_email(subject, sender, recipients): msg = Message(subject, sender, recipients) msg.body = 'test body' msg.html = '<b>html</b> body' thr = Thread(target=send_async_email, args=[app, msg]) thr.start() return thr
很多flask扩展都假设已经存在激活的程序上下文和请求上下文.flask-mail中的send()函数使用current_app, 因此必须有激活的程序上下文,在不同线程中执行mail.send()函数时,程序上下文要用app.app_context()人工创建.
补充
程序在发送大量的电子邮件时,使用专门发送电子邮件作业要比每封电子邮件都新建一个线程要更合适.例如可以吧执行send_async_email()函数的操作发给Celery(http://www.celeryproject.org/)任务队列.关于具体操作,后续继续学习下.
原文:大专栏 flask-用户认证系统