Flask-Login
官网链接: https://flask-login.readthedocs.io/en/latest/
简介
用于处理Flask的用户会话管理,完成登录,注销,记住用户会话的常见功能。--低级库
可以完成的功能:
- 将活跃用户id存储的session中,方便实现登录,退出
- 为视图函数添加登录 or 注销状态限制
- 实现 记住我 功能
- 防止用户sessions被恶意修改
不支持的功能:
- 必须自己实现数据库连接,数据存储,用户登录
- 限制其不能使用 OpenIDs 等其他验证方式
- 只能处理 是否登录的判定
- 不能处理账号注册 账号恢复
开始使用
配置LoginManager
from flask import Flask
from flask-login import LoginManager
app = Flask(__name__)
app.config["SECRET_KEY"] = "BENNY"
# LoginManager 使用sessions来进行身份验证,所以在配置文件中设置 secret_key
login_manager = LoginManager()
login_manager.init_app(app)
如何工作
1, 必须实现 可调的 user_loader, 用于根据用户id获取用户信息,并存入session内。
# 在定义User表的文件中实现该函数
@login_manager.user_loader
def load_user(user_id):
return User.get(user_id)
2, User 类定义要求
# 必须实现下面方法
is_authenticated:当用户通过了身份认证后,会返回True;只有满足该验证的用户才能访问 login_required修饰的视图函数
is_active: 活跃账户,返回True; 被暂停、禁用的账号返回False
is_anonymous: 匿名用户返回True;注册登录用户返回True
get_id: 该方法必须返回一个唯一标识的用户unicode,可用于从 user_loader返回该用户;该ID必须是unicode,如果是int 或者其他类型,需要转化为 unicode
# 为了实现这样的用户类更方便,可以继承 UserMixin; 该类提供了这些属性与方法的默认实现
example:
class User(UserMixin, db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), unique=True, index=True)
email = db.Column(db.String(64), unique=True, index=True)
# Flask-Login integration
def is_authenticated(self):
return True
def is_active(self):
return True
def is_anonymous(self):
return False
def get_id(self):
return self.id
def __repr__(self):
return '<User %r>' % self.username
def __str__(self):
return self.username
登录实例
需要先验证 next参数的值, 否则,容易受到打开 重定向的影响
@app.route("/login", methods=["GET","POST"])
def login():
form = LoginForm()
if form.validate_on_submit():
login_manager.login_user(user)
flask.flash("Logged in seccessfully")
next = flask.request.args.get("next")
if not is_sage_url(next):
return flask.abort(404)
return falsk.redirect(next or flask.url_for("idnex"))
return flask.render_template("login.html", form=from)
在模板中使用 current_user 代理访问登录用户;该代理在每个模板中都可以使用
{% if current_user.is_authenticated %}
Hi {{ current_user.name }}!
{% endif %}
使用 login_required 装饰器来限定用户登录访问的视图
@app.route("/settings")
@login_required
def settings():
pass
# 注销登录,需要清空会话中的所有Cookie
@app.route("/logout")
@login_required
def logout():
logout_user()
return redirect(somewhere)
自定义登录过程
默认情况下,当未登录用户访问 login_required 装饰的页面的时候, flask-login 将flash一条消息并将其重定向到登录视图(如果没有指定登录视图,将返回401错误)
# 绑定登录视图
login_manager.login_view = "users.login"
# 自定义默认闪烁的消息
login_manager.login_messages = u"该页面需要登录后才能访问"
# 自定义消息级别
login_manager.login_message_category = "info"
# 如果想进一步自定义流程,可以使用下面的装饰器功能
@login_manager.unauthorized_handler
def unauthorized():
return a_response
使用请求加载器自定义登录
不适用Cookie来登录用户,而是使用 header values 或者 api key 作为参数来完成登录,这需要实现 request_loader 回调函数。
@login_manager.request_loader
def load_user_from_request(request):
# 首先,尝试从url里提取api_key
api_key = request.args.get("api_key")
if api_key:
user = User.query.filter_by(api_key=api_key).first()
if user:
return user
# 从请求头中获取参数 Authorization
api_key = request.headers.get("Authorization")
if api_key:
api_key = api_key.replace("Basic", "", 1)
try:
api_key = base654.b64decode(api_key)
except TyperError:
pass
user = user.query.filter_by(api_key=api_key).first()
if user:
return user
return None
匿名用户
未登录用户需要设置为 AnonymousUserMixin 对象,该对象需要实现的属性与方法
- is_active 、is_authenticated 返回False
- is_anonymous: return True
- get_id: return None
自定义匿名用户的类
class AnonymousUser(AnonymousUserMixin):
def can(self, permissions):
return False
def is_administrator(self):
return False
login_manager.anonymous_user = AnonymousUser
remember Me 功能
只需要将 remember=True 传给login_user 调用即可。
通过配置文件设置Cookie过期时间。
REMEMBER_COOKIE_DURATION
Alternative Tokens 备选tokens
当使用用户id作为token,意味着必须改变用户ID才能使其登录session 无效。一个改进的方法是,通过使用一个替代ID。
该替代ID也必须是唯一的,作为第二个用户ID。
该方法优点: 当用户修改密码后,可以直接更新该替代ID,此时,可以保证旧的ID的登录会话无效(已经查找不到该ID)
@login_manager.user_loader
def load_user(user_id):
return User.query.filter_by(alternative_id=user_id).first()
def get_id(self):
return unicode(self.alternative_id)
修改密码,重新登录功能(todo ??)
Cookie设置
REMEMBER_COOKIE_NAME |
存储“记住我”信息的cookie的名称。默认值: remember_token |
---|---|
REMEMBER_COOKIE_DURATION |
Cookie过期之前的时间,以datetime.timedelta 对象或整数秒为单位。 默认值: 365天(非non历公历年) |
REMEMBER_COOKIE_DOMAIN |
如果“记住我” cookie应该跨域,请在此处设置域值(即.example.com 允许cookie在的所有子域上使用example.com )。 默认: None |
REMEMBER_COOKIE_PATH |
将“记住我” cookie限制为特定路径。 默认: / |
REMEMBER_COOKIE_SECURE |
将“记住我” cookie的范围限制为安全通道(通常为HTTPS)。 默认: None |
REMEMBER_COOKIE_HTTPONLY |
防止客户端脚本访问“记住我” cookie。 默认: False |
REMEMBER_COOKIE_REFRESH_EACH_REQUEST |
如果设置为True cookie,则每次请求都会刷新它,这会增加生命周期。像Flask一样SESSION_REFRESH_EACH_REQUEST 。 默认: False |
session 保护
保护session避免被修改。可以设置的保护级别为 None, Basic , Strong。设置的路径,config文件级别优先于login_manager
# 默认情况下,basic被激活
logign_manager.session_protection = "strong"
# 设置None,可以禁用该功能
login_manager.session_protection = None
当使用APIs时候,禁用Session Cookie
当使用authentication去验证API的时候,需要去禁用Flask Sesion Cookie。为了实现这个,需要自定义custom session interface,使用一个标识去跳过会话保存功能。
from flask import g
from flask.sessions import SecureCookieSessionInterface
from flask_login import user_loaded_from_header
class CustomSessionInterface(SecureCookieSessionInterface):
"""Prevent creating session from API requests."""
def save_session(self, *args, **kwargs):
if g.get('login_via_header'):
return
return super(CustomSessionInterface, self).save_session(*args,
**kwargs)
app.session_interface = CustomSessionInterface()
@user_loaded_from_header.connect
def user_loaded_from_header(self, user=None):
g.login_via_header = True