用 Flask 来写个轻博客 (24) — 使用 Flask-Login 来保护应用安全
Blog 项目源码:https://github.com/JmilkFan/JmilkFan-s-Blog
目录
前文列表
用 Flask 来写个轻博客 (1) — 创建项目
用 Flask 来写个轻博客 (2) — Hello World!
用 Flask 来写个轻博客 (3) — (M)VC_连接 MySQL 和 SQLAlchemy
用 Flask 来写个轻博客 (4) — (M)VC_创建数据模型和表
用 Flask 来写个轻博客 (5) — (M)VC_SQLAlchemy 的 CRUD 详解
用 Flask 来写个轻博客 (6) — (M)VC_models 的关系(one to many)
用 Flask 来写个轻博客 (7) — (M)VC_models 的关系(many to many)
用 Flask 来写个轻博客 (8) — (M)VC_Alembic 管理数据库结构的升级和降级
用 Flask 来写个轻博客 (9) — M(V)C_Jinja 语法基础快速概览
用 Flask 来写个轻博客 (10) — M(V)C_Jinja 常用过滤器与 Flask 特殊变量及方法
用 Flask 来写个轻博客 (11) — M(V)C_创建视图函数
用 Flask 来写个轻博客 (12) — M(V)C_编写和继承 Jinja 模板
用 Flask 来写个轻博客 (13) — M(V)C_WTForms 服务端表单检验
用 Flask 来写个轻博客 (14) — M(V)C_实现项目首页的模板
用 Flask 来写个轻博客 (15) — M(V)C_实现博文页面评论表单
用 Flask 来写个轻博客 (16) — MV(C)_Flask Blueprint 蓝图
用 Flask 来写个轻博客 (17) — MV(C)_应用蓝图来重构项目
用 Flask 来写个轻博客 (18) — 使用工厂模式来生成应用对象
用 Flask 来写个轻博客 (19) — 以 Bcrypt 密文存储账户信息与实现用户登陆表单
用 Flask 来写个轻博客 (20) — 实现注册表单与应用 reCAPTCHA 来实现验证码
用 Flask 来写个轻博客 (21) — 结合 reCAPTCHA 验证码实现用户注册与登录
用 Flask 来写个轻博客 (22) — 实现博客文章的添加和编辑页面
用 Flask 来写个轻博客 (23) — 应用 OAuth 来实现 Facebook 第三方登录
扩展阅读
用户登录帐号
Web上的用户登录功能应该是最基本的功能了,但这个功能可能并没有你所想像的那么简单,这是一个关系到用户安全的功能. 在现代这样速度的计算速度下,用穷举法破解账户的密码会是一件很轻松的事。所以在设计用户口令登录功能的时候需要注意下列几点:
- 用正则表达式限制用户输入一些非常容易被破解的口令
- 密文保存用户的口令
- 不让浏览器记住你的密码
- 使用 HTTPS 在网上传输你的密码
上述的手段都可以帮助我们的应用程序更好的保护用户的账户信息, 后两点是否实现, 视乎于应用的安全级别是否严格, 我们在这里仅实现前面两点. 除此之外我们还要解决一个非常重要的问题, 就是用户登录状态的处理.
用户登录状态
因为 HTTP 是无状态的协议,也就是说,这个协议是无法记录用户访问状态的,所以用户的每次请求都是独立的无关联的. 而我们的网站都是设计成多个页面的,所在页面跳转过程中我们需要知道用户的状态,尤其是用户登录的状态,这样我们在页面跳转后我们才知道用户是否有权限来操作该页面上的一些功能或是查看一些数据。
所以,我们每个页面都需要对用户的身份进行认证。在这样的应用场景下, 保存用户的登录状态的功能就显得非常重要了. 为了实现这一功能:
- 第一种方法, 用得最多的技术就是 session 和 cookie,我们会把用户登录的信息存放在客户端的 cookie 里,这样,我们每个页面都从这个 cookie 里获得用户是否已经登录的信息,从而达到记录状态,验证用户的目的.
- 第二种方法, 我们这里会使用 Flask-Login 扩展是提供支撑.
NOTE: 两种方法是不能够共存的.
Flask-Login
Flask-Login 为 Flask 提供用户 session 的管理机制。它可以处理 Login、Logout 和 session 等服务。
作用:
- 将用户的 id 储存在 session 中,方便用于 Login/Logout 等流程。
- 让你能够约束用户 Login/Logout 的视图
- 提供 remember me 功能
- 保护 cookies 不被篡改
使用 Flask-Login 来保护应用安全
- 安装
pip install flask-login
pip freeze . requirements.txt
- 初始化 LoginManager 对象
vim extensions.py
from flask.ext.login import LoginManager
# Create the Flask-Login's instance
login_manager = LoginManager()
- 设置 LoginManager 对象的参数
vim extensions.py
# Setup the configuration for login manager.
# 1. Set the login page.
# 2. Set the more stronger auth-protection.
# 3. Show the information when you are logging.
# 4. Set the Login Messages type as `information`.
login_manager.login_view = "main.login"
login_manager.session_protection = "strong"
login_manager.login_message = "Please login to access this page."
login_manager.login_message_category = "info"
@login_manager.user_loader
def load_user(user_id):
"""Load the user's info."""
from models import User
return User.query.filter_by(id=user_id).first()
NOTE 1: login_view 指定了登录页面的视图函数
NOTE 2: session_protection 能够更好的防止恶意用户篡改 cookies, 当发现 cookies 被篡改时, 该用户的 session 对象会被立即删除, 导致强制重新登录.
NOTE 3: login_message 指定了提供用户登录的文案
NOTE 4: login_category 指定了登录信息的类别为 info
NOTE 5: 我们需要定义一个 LoginManager.user_loader 回调函数,它的作用是在用户登录并调用 login_user() 的时候, 根据 user_id 找到对应的 user, 如果没有找到,返回None, 此时的 user_id 将会自动从 session 中移除, 若能找到 user ,则 user_id 会被继续保存.
- 修改 User models, 以更好支持 Flask-Login 的用户状态检验
class User(db.Model):
"""Represents Proected users."""
# Set the name for table
__tablename__ = 'users'
id = db.Column(db.String(45), primary_key=True)
username = db.Column(db.String(255))
password = db.Column(db.String(255))
# one to many: User ==> Post
# Establish contact with Post's ForeignKey: user_id
posts = db.relationship(
'Post',
backref='users',
lazy='dynamic')
roles = db.relationship(
'Role',
secondary=users_roles,
backref=db.backref('users', lazy='dynamic'))
def __init__(self, id, username, password):
self.id = id
self.username = username
self.password = self.set_password(password)
# Setup the default-role for user.
default = Role.query.filter_by(name="default").one()
self.roles.append(default)
def __repr__(self):
"""Define the string format for instance of User."""
return "<Model User `{}`>".format(self.username)
def set_password(self, password):
"""Convert the password to cryptograph via flask-bcrypt"""
return bcrypt.generate_password_hash(password)
def check_password(self, password):
return bcrypt.check_password_hash(self.password, password)
def is_authenticated(self):
"""Check the user whether logged in."""
# Check the User's instance whether Class AnonymousUserMixin's instance.
if isinstance(self, AnonymousUserMixin):
return False
else:
return True
def is_active():
"""Check the user whether pass the activation process."""
return True
def is_anonymous(self):
"""Check the user's login status whether is anonymous."""
if isinstance(self, AnonymousUserMixin):
return True
else:
return False
def get_id(self):
"""Get the user's uuid from database."""
return unicode(self.id)
NOTE 1: is_authenticated() 检验 User 的实例化对象是否登录了.
NOTE 2: is_active() 检验用户是否通过某些验证
NOTE 3: is_anonymous() 检验用户是否为匿名用户
NOTE 4: get_id() 返回 User 实例化对象的唯一标识 id
在完成这些准备之后, 我们可以将 Flask-Login 应用到 Login/Logout 的功能模块中.
- 在 LoginForm 加入 Remember Me 可选框
class LoginForm(Form):
"""Login Form"""
username = StringField('Usermame', [DataRequired(), Length(max=255)])
password = PasswordField('Password', [DataRequired()])
remember = BooleanField("Remember Me")
def validate(self):
"""Validator for check the account information."""
check_validata = super(LoginForm, self).validate()
# If validator no pass
if not check_validata:
return False
# Check the user whether exist.
user = User.query.filter_by(username=self.username.data).first()
if not user:
self.username.errors.append('Invalid username or password.')
return False
# Check the password whether right.
if not user.check_password(self.password.data):
self.password.errors.append('Invalid username or password.')
return False
return True
- jmilkfansblog.controllers.main.py
@main_blueprint.route('/login', methods=['GET', 'POST'])
@openid.loginhandler
def login():
"""View function for login.
Flask-OpenID will be receive the Authentication-information
from relay party.
"""
# Create the object for LoginForm
form = LoginForm()
# Create the object for OpenIDForm
openid_form = OpenIDForm()
# Send the request for login to relay party(URL).
if openid_form.validate_on_submit():
return openid.trg_login(
openid_form.openid_url.data,
ask_for=['nickname', 'email'],
ask_for_optional=['fullname'])
# Try to login the relay party failed.
openid_errors = openid.fetch_error()
if openid_errors:
flash(openid_errors, category="danger")
# Will be check the account whether rigjt.
if form.validate_on_submit():
# Using session to check the user's login status
# Add the user's name to cookie.
# session['username'] = form.username.data
user = User.query.filter_by(username=form.username.data).one()
# Using the Flask-Login to processing and check the login status for user
# Remember the user's login status.
login_user(user, remember=form.remember.data)
identity_changed.send(
current_app._get_current_object(),
identity=Identity(user.id))
flash("You have been logged in.", category="success")
return redirect(url_for('blog.home'))
return render_template('login.html',
form=form,
openid_form=openid_form)
NOTE 1: login_user() 能够将已登录并通过 load_user() 的用户对应的 User 对象, 保存在 session 中, 所以该用户在访问不同的页面的时候不需要重复登录.
NOTE 2: 如果希望应用记住用户的登录状态, 只需要为 login_user()的形参 remember 传入 True 实参就可以了.
- jmilkfansblog.controllers.main.py
@main_blueprint.route('/logout', methods=['GET', 'POST'])
def logout():
"""View function for logout."""
# Remove the username from the cookie.
# session.pop('username', None)
# Using the Flask-Login to processing and check the logout status for user.
logout_user()
identity_changed.send(
current_app._get_current_object(),
identity=AnonymousIdentity())
flash("You have been logged out.", category="success")
return redirect(url_for('main.login'))
NOTE 1 Logout 时, 使用 logout_user 来将用户从 session 中删除.
- jmilkfansblog.controllers.blog.py
如果我们希望网站的某些页面不能够被匿名用户查看, 并且跳转到登录页面时, 我们可以使用login_required装饰器
from flask.ext.login import login_required, current_user
@blog_blueprint.route('/new', methods=['GET', 'POST'])
@login_required
def new_post():
"""View function for new_port."""
form = PostForm()
# Ensure the user logged in.
# Flask-Login.current_user can be access current user.
if not current_user:
return redirect(url_for('main.login'))
# Will be execute when click the submit in the create a new post page.
if form.validate_on_submit():
new_post = Post(id=str(uuid4()), title=form.title.data)
new_post.text = form.text.data
new_post.publish_date = datetime.now()
new_post.users = current_user
db.session.add(new_post)
db.session.commit()
return redirect(url_for('blog.home'))
return render_template('new_post.html',
form=form)
引用了这个装饰器之后, 当匿名用户像创建文章时, 就会跳转到登录页面.
NOTE 1: Flask-Login 提供了一个代理对象 current_user 来访问和表示当前登录的对象, 这个对象在视图或模板中都是能够被访问的. 所以我们常在需要判断用户是否为当前用户时使用(EG. 用户登录后希望修改自己创建的文章).
小结
Flask-Login 为我们的应用提供了非常重要的功能, 让应用清楚的知道了当前登录的用户是谁和该用户的登录状态是怎么样的. 这就让我们为不同的用户设定特定的权限和功能提供了可能. 如果没有这个功能的话, 那么所有登录的用户都可以任意的使用应用的资源, 这是非常不合理的.
用 Flask 来写个轻博客 (24) — 使用 Flask-Login 来保护应用安全的更多相关文章
- 用 Flask 来写个轻博客
用 Flask 来写个轻博客 用 Flask 来写个轻博客 (1) — 创建项目 用 Flask 来写个轻博客 (2) — Hello World! 用 Flask 来写个轻博客 (3) — (M)V ...
- 用 Flask 来写个轻博客 (37) — 在 Github 上为第一阶段的版本打 Tag
Blog 项目源码:https://github.com/JmilkFan/JmilkFan-s-Blog 目录 目录 前文列表 第一阶段结语 打 Tag 前文列表 用 Flask 来写个轻博客 (1 ...
- 用 Flask 来写个轻博客 (36) — 使用 Flask-RESTful 来构建 RESTful API 之五
目录 目录 前文列表 PUT 请求 DELETE 请求 测试 对一条已经存在的 posts 记录进行 update 操作 删除一条记录 前文列表 用 Flask 来写个轻博客 (1) - 创建项目 用 ...
- 用 Flask 来写个轻博客 (35) — 使用 Flask-RESTful 来构建 RESTful API 之四
Blog 项目源码:https://github.com/JmilkFan/JmilkFan-s-Blog 目录 目录 前文列表 POST 请求 身份认证 测试 前文列表 用 Flask 来写个轻博客 ...
- 用 Flask 来写个轻博客 (34) — 使用 Flask-RESTful 来构建 RESTful API 之三
目录 目录 前文列表 应用请求中的参数实现 API 分页 测试 前文列表 用 Flask 来写个轻博客 (1) - 创建项目 用 Flask 来写个轻博客 (2) - Hello World! 用 F ...
- 用 Flask 来写个轻博客 (33) — 使用 Flask-RESTful 来构建 RESTful API 之二
Blog 项目源码:https://github.com/JmilkFan/JmilkFan-s-Blog 目录 目录 前文列表 扩展阅读 构建 RESTful Flask API 定义资源路由 格式 ...
- 用 Flask 来写个轻博客 (32) — 使用 Flask-RESTful 来构建 RESTful API 之一
目录 目录 前文列表 扩展阅读 RESTful API REST 原则 无状态原则 面向资源 RESTful API 的优势 REST 约束 前文列表 用 Flask 来写个轻博客 (1) - 创建项 ...
- 用 Flask 来写个轻博客 (31) — 使用 Flask-Admin 实现 FileSystem 管理
Blog 项目源码:https://github.com/JmilkFan/JmilkFan-s-Blog 目录 目录 前文列表 扩展阅读 编写 FileSystem Admin 页面 Flask-A ...
- 用 Flask 来写个轻博客 (30) — 使用 Flask-Admin 增强文章管理功能
Blog 项目源码:https://github.com/JmilkFan/JmilkFan-s-Blog 目录 目录 前文列表 扩展阅读 实现文章管理功能 实现效果 前文列表 用 Flask 来写个 ...
随机推荐
- 20190815 On Java8 第五章 控制流
第五章 控制流 迭代语句 逗号操作符 在 Java 中逗号运算符(这里并非指我们平常用于分隔定义和方法参数的逗号分隔符)仅有一种用法:在 for 循环的初始化和步进控制中定义多个变量.我们可以使用逗号 ...
- Vue-实现简单拖拽(自定义属性)
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"& ...
- 关于this、Echarts中的data
this是指当前对象 移除class的jQuery代码:$('ur.nav li:eq(0)').removeClass('active') 添加class的jQuery代码:$('ur.nav li ...
- jQuery基础--创建节点和 添加节点
创建节点 $(function () { // var box = document.getElementById("box"); // // var a = document.c ...
- MySQL-极恶安装
1.官网下载地址:https://dev.mysql.com/downloads/mysql/ 2.安装包下载后解压,并创建my.ini配置文件 内容如下,注意两个第三个#:MySQL的安装目录,第四 ...
- jmeter 把返回数据写到文件
jmeter如何把返回数据写入到文件 作者:WhoisTester 2015-10-20 20:11 1. 首先我们可以使用 regular expression extractor 正则表达式 ...
- 在没有iis的情况下,webApi自托管(转自momo314)
第一步 新建一个控制台应用程序 并添加WebApi相关引用,注意,添加之后会默认帮你添加 System.Web.Http.WebHost 的引用,不过,折并没有什么鸟用,干掉他,然后手动添加引用 Sy ...
- LeetCode #1021. Remove Outermost Parentheses 删除最外层的括号
https://leetcode-cn.com/problems/remove-outermost-parentheses/ Java Solution class Solution { public ...
- Codeforces 1172B(组合数学)
题面 给出一棵n个点的树,要求把它画在圆上,且边不相交,画法与排列一一对应(即旋转后相同的算不同种),求方案数.如下图是4个点的树\(T:V=\{1,2,3,4\},E=\{(1,2),(1,3),( ...
- go 文件读写
go 文件读写有很多方式 ioutil读文件 package main import ( "io/ioutil" "fmt" ) func main() { d ...