Flask-Login
概念
Flask-Login 为 Flask 提供了用户会话管理。处理了日常的登入,登出并且长时间记住用户的会话。
会:
- 在会话中存储当前活跃的用户 ID,让你能够自由地登入和登出。
- 能够限制登入(或者登出)用户可以访问的视图。
- 处理让人棘手的 “记住我” 功能。
- 能够帮助保护用户会话免遭 cookie 被盗的牵连。
- 可以与以后可能使用的 Flask-Principal 或其它认证扩展集成。
但是,不会:
- 限制使用特定的数据库或其它存储方法。如何加载用户完全由你决定。
- 限制使用用户名和密码,OpenIDs,或者其它的认证方法。
- 处理超越 “登入或者登出” 之外的权限。
- 处理用户注册或者账号恢复。
安装
pip install flask-login
配置
from flask import Flask
from flask_login import LoginManager
app = Flask(__name__)
# 实例化对象
login_manager = LoginManager()
# 懒加载
login_manager.init_app(app)
# 设置登录视图函数
login_manager.login_view = "login"
if __name__ == "__main__":
app.run()
登录管理对象 login_manager
的 login_view
属性,指定登录页面的视图函数 (登录页面的 endpoint
),即验证失败时要跳转的页面,设置为登录页。
用户模块
构造数据
要做用户验证,需要维护用户记录,为了方便演示,使用一个全局列表 USERS
来记录用户信息,并且初始化了两个用户信息:
# 用户信息
USERS = [
{
"id": 1,
"name": '123456',
"password": generate_password_hash('123456')
},
{
"id": 2,
"name": '654321',
"password": generate_password_hash('654321')
}
]
password
为登录密码,通过模块 werkzeug.security
提供了 generate_password_hash
方法,使用 sha256 加密算法将字符串变为密文。
不要在系统中存放用户密码的明文
基于用户信息,定义两方法,用来创建( create_user
)和获取( get_user
)用户信息:
# 创建一个用户
def create_user(username, password):
user = {
"id": uuid.uuid4()
"name": username,
"password": generate_password_hash(password),
}
USERS.append(user)
# 根据用户名获得用户记录
def get_user(username):
for user in USERS:
if user.get("name") == username:
return user
return None
用户类
下面创建一个用户类,类维护用户的登录状态,是生成 Session
的基础,Flask-Login
提供了用户基类 UserMixin
,方便定义自己的用户类,定义一个 User
:
from flask_login import LoginManager,UserMixin
from werkzeug.security import generate_password_hash,check_password_hash
...
class User(UserMixin):
"""用户类"""
def __init__(self, user):
self.username = user.get("name")
self.password_hash = user.get("password")
self.id = user.get("id")
def verify_password(self, password):
"""密码验证"""
if self.password_hash is None:
return False
return check_password_hash(self.password_hash, password)
def get_id(self):
"""获取用户ID"""
return self.id
@staticmethod
def get(user_id):
"""根据用户ID获取用户实体,为 login_user 方法提供支持"""
if not user_id:
return None
for user in USERS:
if user.get('id') == user_id:
return User(user)
return None
加载登录用户
有了用户类,并且实现了 get
方法,就可以实现 login_manager
的 user_loader
回调函数了,user_loader
的作用是根据 Session
信息加载登录用户,它根据用户 ID
,返回一个用户实例:
@login_manager.user_loader # 定义获取登录用户的方法
def load_user(user_id):
return User.get(user_id)
登录页面
定义表单类
class UserForm(FlaskForm):
username = StringField("用户名",validators=[Length(min=1,max=20,message="请输入的字符长度在1~20个字符之间")])
password = PasswordField("密码",validators=[Length(min=1,max=20,message="请输入的字符长度在1~20个字符之间")])
视图函数
...
from flask_login import login_user
...
@app.route('/login/', methods=["GET", "POST"])
def login():
form = UserForm(request.form)
msg = None
# 如果提交通过验证则返回True
if request.method == "POST" and form.validate():
# request.method == "POST" and form.validate()
# <==> form.validate_on_submit()
username = form.username.data
password = form.password.data
user_info = get_user(username) # 从用户数据中查找用户记录
if user_info is None:
msg = "该用户不存在"
else:
# 创建用户实体
user = User(user_info)
# 验证密码
if user.verify_password(password):
# 创建用户session
login_user(user)
print("重定向url:{}".format(url_for("index")))
return redirect(url_for("index"))
else:
msg = "用户名或密码密码有误"
return render_template("login.html", form=form, msg=msg)
- 视图函数同时支持
GET
和POST
方法 form.validate_on_submit()
可以判断用户是否完整的提交了表单,只对POST
有效,所以可以用来判断请求方式- 如果是
POST
请求,获取提交数据,通过get_user
方法查找是否存在该用户 - 如果用户存在,则创建用户实体,并校验登录密码
- 校验通过后,调用
login_user
方法创建用户Session
,然后跳转到请求参数中next
所指定的地址或者首页 (不用担心如何设置next
,还记得上面设置的login_manager.login_view = 'login'
吗? 对,未登录访问时,会跳转到login
,并且带上next
查询参数) - 非
POST
请求,或者未经过验证,会显示login.html
模板渲染后的结果
模板
<form action="{{ url_for("login") }}" method="post">
{{ form.csrf_token }}
<table>
<tbody>
<tr>
<td>{{ form.username.label }}</td>
<td>{{ form.username() }}</td>
</tr>
<tr>
<td>{{ form.password.label }}</td>
<td>{{ form.password() }}</td>
</tr>
<tr>
<td></td>
<td><input id="submit" type="submit" value="提交"/></td>
</tr>
</tbody>
</table>
</form>
需要验证的页面
为了方便演示,将首页作为需要验证的页面,通过验证将看到登录者欢迎信息,页面上还有个登出链接
首页视图函数 index
:
from flask_login import login_required,current_user
@app.route('/') # 首页
@login_required # 需要登录才能访问
def index():
return render_template("index.html",username=current_user.username)
- 注解
@login_required
会做用户登录检测,如果没有登录要方法此视图函数,就被跳转到login
接入点(endpoint
)。 current_user
是当前登录者,是User
的实例,是Flask-Login
提供全局变量( 类似于全局变量g
)。username
是模板中的变量,可以将当前登录者的用户名传入index.html
模板。
登出视图函数 logout
:
from flask_login import logout_user
@app.route('/logout') # 登出
@login_required
def logout():
logout_user()
return redirect(url_for('login'))
- 只有登录了才有必要登出,所以加上注解
@login_required。
logout_user
方法和login_user
相反,由于注销用户的Session
。- 登出视图不需要模板,直接跳转到登录页,实际项目中可以增加一个登出页,展示些有趣的东西。
用户注册
上面的演示了,已存在用户登录的情况,不存在用户需要完成注册才能登录。
注册功能和登录很类似,页面上多了密码确认字段,并且需要验证两次输入的密码是否一致,后台逻辑是:如果用户不存在,且通过检验,将用户数据保存到 USERS
列表中,跳转到 login
页面。
配合sqlalchemy使用
示范
直接让用户模型继承 flask.ext.login.UserMixin类,类中有上面4个方法的默认实现 如:
flask-sqlachemy和sqlchemy这里都一样:
from flask.ext.login import UserMixinclass User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64),unique=True)
Flask-Login要求实现一个回调函数,使用 get_id()方法返回的唯一标识用户的Unicode字符串 作为参数 返回这个用户对象.
如果是继承的UserMixin类, get_id()方法默认返回的用户的id. 如果用户不存在,应该返回None.
此外如果需要定制数据库的nid时,可在上方添加:
def get_id(self):
return self.nid
默认情况下是查看id的:如果数据库的user表的主键为id,则可不定制get_id()
flask-sqlachemy的语法如下:
from . import loginManager
@loginManager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
获取当前登陆的用户,
from flask.ext.login import current_user
#判断当前用户是否是匿名用户
current_user.is_anonymous()
也可以在模版中使用 {% if current_user.is_authenticated %}
判断
在模版中使用,如果用户已认证就显示他的名字
{% if current_user.is_authenticated %}
Hi {{ current_user.name }}!
{% endif %}
current_user.name的name是user表的name
如何控制Flask-Login的session过期时间
在 Flask-Login 中,如果不特殊处理的话,session 是在你关闭浏览器之后就失效的。也就是说每次重新打开页面都是需要重新登录的。
如果需要自己控制 session 的过期时间的话,
- 首先需要设置 login_manager 的 session类型为永久的,
- 然后再设置 session 的过期时间
from flask import session
...
session.permanent = True
app.permanent_session_lifetime = timedelta(minutes=5)
还需要注意的是 cookie 的默认有效期:
login_manager.remember_cookie_duration=timedelta(days=1)