Python的Flask框架中实现简单的登录功能的教程


Posted in Python onApril 20, 2015

 回顾

在前面的系列章节中,我们创建了一个数据库并且学着用用户和邮件来填充,但是到现在我们还没能够植入到我们的程序中。 两章之前,我们已经看到怎么去创建网络表单并且留下了一个实现完全的登陆表单。

在这篇文章中,我们将基于我门所学的网络表单和数据库来构建并实现我们自己的用户登录系统。教程的最后我们小程序会实现新用户注册,登陆和退出的功能。

为了能跟上这章节,你需要前一章节最后部分,我们留下的微博程序。请确保你的程序已经正确安装和运行。

在前面的章节,我们开始配置我们将要用到的Flask扩展。为了登录系统,我们将使用两个扩展,Flask-Login 和 Flask-OpenID. 配置如下所示 (fileapp\__init__.py):
 

import os
from flaskext.login import LoginManager
from flaskext.openid import OpenID
from config import basedir
 
lm = LoginManager()
lm.setup_app(app)
oid = OpenID(app, os.path.join(basedir, 'tmp'))

Flask-OpenID 扩展为了可以存储临时文件,需要一个临时文件夹路径。为此,我们提供了它的位置。

重访我们的用户模型

Flask-Login扩展需要在我们的User类里实现一些方法。除了这些方法以外,类没有被要求实现其它方法。

下面是我们的User类 (fileapp/models.py):
 

class User(db.Model):
 id = db.Column(db.Integer, primary_key = True)
 nickname = db.Column(db.String(64), unique = True)
 email = db.Column(db.String(120), unique = True)
 role = db.Column(db.SmallInteger, default = ROLE_USER)
 posts = db.relationship('Post', backref = 'author', lazy = 'dynamic')
 
 def is_authenticated(self):
  return True
 
 def is_active(self):
  return True
 
 def is_anonymous(self):
  return False
 
 def get_id(self):
  return unicode(self.id)
 
 def __repr__(self):
  return '<User %r>' % (self.name)

is_authenticated方法是一个误导性的名字的方法,通常这个方法应该返回True,除非对象代表一个由于某种原因没有被认证的用户。

is_active方法应该为用户返回True除非用户不是激活的,例如,他们已经被禁了。

is_anonymous方法应该为那些不被获准登录的用户返回True。

最后,get_id方法为用户返回唯一的unicode标识符。我们用数据库层生成唯一的id。
 

用户加载回调

现在我们通过使用Flask-Login和Flask-OpenID扩展来实现登录系统

首先,我们需要写一个方法从数据库加载到一个用户。这个方法会被Flask-Login使用(fileapp/views.py):
 

@lm.user_loader
def load_user(id):
 return User.query.get(int(id))

记住Flask-Login里的user id一直是unicode类型的,所以在我们把id传递给Flask-SQLAlchemy时,有必要把它转化成integer类型。

登录视图函数

接下来我们要更新登录视图函数(fileapp/views.py):
 

from flask import render_template, flash, redirect, session, url_for, request, g
from flaskext.login import login_user, logout_user, current_user, login_required
from app import app, db, lm, oid
from forms import LoginForm
from models import User, ROLE_USER, ROLE_ADMIN
 
@app.route('/login', methods = ['GET', 'POST'])
@oid.loginhandler
def login():
 if g.user is not None and g.user.is_authenticated():
  return redirect(url_for('index'))
 form = LoginForm()
 if form.validate_on_submit():
  session['remember_me'] = form.remember_me.data
  return oid.try_login(form.openid.data, ask_for = ['nickname', 'email'])
 return render_template('login.html',
  title = 'Sign In',
  form = form,
  providers = app.config['OPENID_PROVIDERS'])

注意到我们导入了一些新的模块,其中有些后面会用到。

跟上个版本的变化很小。我们给视图函数添加了一个新的装饰器:oid.loginhandler。它告诉Flask-OpenID这是我们的登录视图函数。
 

在方法体的开头,我们检测是是否用户是已经经过登录认证的,如果是就重定向到index页面。这儿的思路是如果一个用户已经登录了,那么我们不会让它做二次登录。

全局变量g是Flask设置的,在一个request生命周期中,用来存储和共享数据的变量。所以我猜你已经想到了,我们将把已经登录的用户放到g变量里。

我们在调用redirect()时使用的url_for()方法是Flask定义的从给定的view方法获取url。如果你想重定向到index页面,你h很可能使用redirect('/index'),但是我们有很好的理由让Flask为你构造url。
 
当我们从登录表单得到返回数据,接下来要运行的代码也是新写的。这儿我们做两件事。首先我们保存remember_me的布尔值到Flask的session中,别和Flask-SQLAlchemy的db.session混淆了。我们已经知道在一个request的生命周期中用Flask的g对象来保存和共享数据。沿着这条线路Flask的session提供了更多,更复杂的服务。一旦数据被保存到session中,它将在同一客户端发起的这次请求和这次以后的请求中永存而不会消亡。数据将保持在session中直到被明确的移除。为了做到这些,Flask为每个客户端建立各自的session。

下面的oid.try_login是通过Flask-OpenID来执行用户认证。这个方法有两个参数,web表单提供的openid和OpenID provider提供的我们想要的list数据项。由于我们定义了包含nickname和email的User类,所以我们要从找nickname和email这些项。

基于OpenID的认证是异步的。如果认证成功,Flask-OpenID将调用有由oid.after_login装饰器注册的方法。如果认证失败那么用户会被重定向到login页面。

Flask-OpenID登录回调

这是我们实现的after_login方法(app/views.py)
 

@oid.after_login
def after_login(resp):
 if resp.email is None or resp.email == "":
  flash('Invalid login. Please try again.')
  redirect(url_for('login'))
 user = User.query.filter_by(email = resp.email).first()
 if user is None:
  nickname = resp.nickname
  if nickname is None or nickname == "":
   nickname = resp.email.split('@')[0]
  user = User(nickname = nickname, email = resp.email, role = ROLE_USER)
  db.session.add(user)
  db.session.commit()
 remember_me = False
 if 'remember_me' in session:
  remember_me = session['remember_me']
  session.pop('remember_me', None)
 login_user(user, remember = remember_me)
 return redirect(request.args.get('next') or url_for('index'))

传给after_login方法的resp参数包含了OpenID provider返回的一些信息。

第一个if声明仅仅是为了验证。我们要求一个有效的email,所以一个没有没提供的email我们是没法让他登录的。

接下来,我们将根据email查找数据库。如果email没有被找到我们就认为这是一个新的用户,所以我们将在数据库中增加一个新用户,做法就像我们从之前章节学到的一样。注意我们没有处理nickname,因为一些OpenID provider并没有包含这个信息。

做完这些我们将从Flask session中获取remember_me的值,如果它存在,那它是我们之前在login view方法中保存到session中的boolean类型的值。

然后我们调用Flask-Login的login_user方法,来注册这个有效的登录。

最后,在最后一行我们重定向到下一个页面,或者如果在request请求中没有提供下个页面时,我们将重定向到index页面。

跳转到下一页的这个概念很简单。比方说我们需要你登录才能导航到一个页面,但你现在并未登录。在Flask-Login中你可以通过login_required装饰器来限定未登录用户。如果一个用户想连接到一个限定的url,那么他将被自动的重定向到login页面。Flask-Login将保存最初的url作为下一个页面,一旦登录完成我们便跳转到这个页面。

做这个工作Flask-Login需要知道用户当前在那个页面。我们可以在app的初始化组件里配置它(app/__init__.py):
 

lm = LoginManager()
lm.setup_app(app)
lm.login_view = 'login'

全局变量g.user

如果你注意力很集中,那么你应该记得在login view方法中我们通过检查g.user来判断一个用户是否登录了。为了实现这个我们将使用Flask提供的before_request事件。任何一个被before_request装饰器装饰的方法将会在每次request请求被收到时提前与view方法执行。所以在这儿来设置我们的g.user变量(app/views.py):
 

@app.before_request
def before_request():
 g.user = current_user

这就是它要做的一切,current_user全局变量是被Flask-Login设定的,所以我们只需要把它拷贝到更容易被访问的g变量就OK了。这样,所有的请求都能访问这个登录的用户,甚至于内部的模板。

index视图

在之前的章节中我们用假代码遗留了我们的index视图,因为那个时候我们系统里并没有用户和博客文章。现在我们有用户了,所以,让我们来完成它吧:
 

@app.route('/')
@app.route('/index')
@login_required
def index():
 user = g.user
 posts = [
  {
   'author': { 'nickname': 'John' },
   'body': 'Beautiful day in Portland!'
  },
  {
   'author': { 'nickname': 'Susan' },
   'body': 'The Avengers movie was so cool!'
  }
 ]
 return render_template('index.html',
  title = 'Home',
  user = user,
  posts = posts)

在这个方法中只有两处变动。首先,我们增加了login_required装饰器。这样表明了这个页面只有登录用户才能访问。

另一个改动是把g.user传给了模板,替换了之间的假对象。

现在可以运行我们的应用了。

当我们连接到http://localhost:5000你将会看到登陆页面。记着如果你通过OpenID登录那么你必须使用你的提供者提供的OpenID URL。你可以下面URL中的任何一个OpenID provider来为你产生一个正确的URL。

作为登录进程的一部分,你将会被重定向到OpenID提供商的网站,你将在那儿认证和授权你共享给我们应用的一些信息(我们只需要email和nickname,放心,不会有任何密码或者其他个人信息被曝光)。

一旦登录完成你将作为已登录用户被带到index页面。

试试勾选remember_me复选框。有了这个选项当你在浏览器关闭应用后重新打开时,你还是已登录状态。

注销登录

我们已经实现了登录,现在是时候来实现注销登录了。

注销登录的方法灰常简单(file app/views.py):
 

@app.route('/logout')
def logout():
 logout_user()
 return redirect(url_for('index'))

但我们在模板中还没有注销登录的链接。我们将在base.html中的顶部导航栏添加这个链接(file app/templates/base.html):

 

<html>
 <head>
 {% if title %}
 <title>{{title}} - microblog</title>
 {% else %}
 <title>microblog</title>
 {% endif %}
 </head>
 <body>
 <div>Microblog:
  <a href="{{ url_for('index') }}">Home</a>
  {% if g.user.is_authenticated() %}
  | <a href="{{ url_for('logout') }}">Logout</a>
  {% endif %}
 </div>
 <hr>
 {% with messages = get_flashed_messages() %}
 {% if messages %}
 <ul>
 {% for message in messages %}
  <li>{{ message }} </li>
 {% endfor %}
 </ul>
 {% endif %}
 {% endwith %}
 {% block content %}{% endblock %}
 </body>
</html>

这是多么多么简单啊,我们只需要检查一下g.user中是否有一个有效的用户,如果有我们就添加注销链接。在我们的模板中我们再一次使用了url_for方法。

最后的话
我们现在有了一个全功能的用户登录系统。在下一章中,我们将创建用户的个人资料页,并显示用户的头像。

在此期间,这里是更新的应用程序代码,包括在这篇文章中的所有变化:

下载 microblog-0.5.zip 。

Python 相关文章推荐
Python爬虫辅助利器PyQuery模块的安装使用攻略
Apr 24 Python
详解字典树Trie结构及其Python代码实现
Jun 03 Python
Python处理json字符串转化为字典的简单实现
Jul 07 Python
Python脚本实现12306火车票查询系统
Sep 30 Python
Python中断言Assertion的一些改进方案
Oct 27 Python
Python WXPY实现微信监控报警功能的代码
Oct 20 Python
Python简单计算数组元素平均值的方法示例
Dec 26 Python
详解Python并发编程之从性能角度来初探并发编程
Aug 23 Python
Python模拟登录requests.Session应用详解
Nov 17 Python
python 用Matplotlib作图中有多个Y轴
Nov 28 Python
Python 如何安装Selenium
May 06 Python
PyTorch的Debug指南
May 07 Python
Python的Flask框架与数据库连接的教程
Apr 20 #Python
Python的Flask框架中web表单的教程
Apr 20 #Python
在Python的Flask框架中使用模版的入门教程
Apr 20 #Python
使用Node.js和Socket.IO扩展Django的实时处理功能
Apr 20 #Python
利用Python的Django框架中的ORM建立查询API
Apr 20 #Python
对于Python的框架中一些会话程序的管理
Apr 20 #Python
介绍Python的Django框架中的QuerySets
Apr 20 #Python
You might like
浅析Laravel5中队列的配置及使用
2016/08/04 PHP
Laravel 实现Controller向blade前台模板赋值的四种方式小结
2019/10/22 PHP
设定php简写功能的方法
2019/11/28 PHP
几款极品的javascript压缩混淆工具
2007/05/16 Javascript
stream.js 一个很小、完全独立的Javascript类库
2011/10/28 Javascript
JQuery实现倒计时按钮具体方法
2013/11/14 Javascript
wap浏览自动跳转到wap页面的js代码
2014/05/17 Javascript
JavaScript Function函数类型介绍
2015/04/08 Javascript
纯javascript响应式树形菜单效果
2015/11/10 Javascript
微信小程序图表插件(wx-charts)实例代码
2017/01/17 Javascript
jQuery插件FusionWidgets实现的Bulb图效果示例【附demo源码下载】
2017/03/23 jQuery
JS去掉字符串中所有的逗号
2017/10/18 Javascript
VUE2.0+Element-UI+Echarts封装的组件实例
2018/03/02 Javascript
create-react-app修改为多页面支持的方法
2018/05/17 Javascript
element-ui上传一张图片后隐藏上传按钮功能
2019/05/22 Javascript
java实现单链表增删改查的实例代码详解
2019/08/30 Javascript
修改vue源码实现动态路由缓存的方法
2020/01/21 Javascript
vue打开新窗口并实现传参的图文实例
2021/03/04 Vue.js
python文件操作之目录遍历实例分析
2015/05/20 Python
分析用Python脚本关闭文件操作的机制
2015/06/28 Python
浅析Python中yield关键词的作用与用法
2016/11/29 Python
Python实现的逻辑回归算法示例【附测试csv文件下载】
2018/12/28 Python
python+selenium实现简历自动刷新的示例代码
2019/05/20 Python
Python 文件数据读写的具体实现
2020/01/24 Python
Python验证码截取识别代码实例
2020/05/16 Python
tensorflow 20:搭网络,导出模型,运行模型的实例
2020/05/26 Python
详解python logging日志传输
2020/07/01 Python
深入浅析css3 中display box使用方法
2015/11/25 HTML / CSS
详解使用canvas保存网页为pdf文件支持跨域
2018/11/23 HTML / CSS
学校安全检查制度
2014/01/27 职场文书
小学生评语大全
2014/04/18 职场文书
2014年毕业演讲稿范文
2014/05/13 职场文书
群众路线班子对照检查材料
2014/09/25 职场文书
2014年政务公开工作总结
2014/12/09 职场文书
烈士陵园观后感
2015/06/08 职场文书
欠条范文
2015/07/03 职场文书