用户认证的原理

在了解使用Flask来实现用户认证之前,我们首先要明白用户认证的原理。假设现在我们要自己去实现用户认证,需要做哪些事情呢?

  1. 首先,用户要能够输入用户名和密码,所以需要网页和表单,用以实现用户输入和提交的过程。
  2. 用户提交了用户名和密码,我们就需要比对用户名,密码是否正确,而要想比对,首先我们的系统中就要有存储用户名,密码的地方,大多数后台系统会通过数据库来存储,但是实际上我们也可以简单的存储到文件当中。(为简明起见,本文将用户信息存储到json文件当中)
  3. 登录之后,我们需要维持用户登录状态,以便用户在访问特定网页的时候来判断用户是否已经登录,以及是否有权限访问改网页。这就需要有维护一个会话来保存用户的登录状态和用户信息。
  4. 从第三步我们也可以看出,如果我们的网页需要权限保护,那么当请求到来的时候,我们就首先要检查用户的信息,比如是否已经登录,是否有权限等,如果检查通过,那么在response的时候就会将相应网页回复给请求的用户,但是如果检查不通过,那么就需要返回错误信息。
  5. 在第二步,我们知道要将用户名和密码存储起来,但是如果只是简单的用明文存储用户名和密码,很容易被“有心人”盗取,从而造成用户信息泄露,那么我们实际上应当将用户信息尤其是密码做加密处理之后再存储比较安全。
  6. 用户登出

通过Flask以及相应的插件来实现登录过程

接下来讲述如何通过Flask框架以及相应的插件来实现整个登录过程,需要用到的插件如下:

  • flask-wtf
  • wtf
  • werkzeug
  • flask_login

使用flask-wtf和wtf来实现表单功能

flask-wtf对wtf做了一些封装,不过有些东西还是要直接用wtf,比如StringField等。flask-wtf和wtf主要是用于建立html中的元素和Python中的类的对应关系,通过在Python代码中操作对应的类,对象等从而控制html中的元素。我们需要在python代码中使用flask-wtf和wtf来定义前端页面的表单(实际是定义一个表单类),再将对应的表单对象作为render_template函数的参数,传递给相应的template,之后Jinja模板引擎会将相应的template渲染成html文本,再作为http response返回给用户。

定义表单类示例代码:

# forms.py
from flask_wtf import FlaskForm
from wtforms import StringField, BooleanField, PasswordField
from wtforms.validators import DataRequired# 定义的表单都需要继承自FlaskForm
class LoginForm(FlaskForm):# 域初始化时,第一个参数是设置label属性的username = StringField('User Name', validators=[DataRequired()])password = PasswordField('Password', validators=[DataRequired()])remember_me = BooleanField('remember me', default=False)

在wtf当中,每个域代表就是html中的元素,比如StringField代表的是<input type="text">元素,当然wtf的域还定义了一些特定功能,比如validators,可以通过validators来对这个域的数据做检查,详细请参考wtf教程。
对应的html模板可能如下login.html:

{% extends "layout.html" %}
<html><head><title>Login Page</title></head><body><form action="{{ url_for("login") }}" method="POST"><p>User Name:<br><input type="text" name="username" /><br></p><p>Password:</br><input type="password" name="password" /><br></p><p><input type="checkbox" name="remember_me"/>Remember Me</p>{{ form.csrf_token }} </form></body>
</html>

这里{{ form.csrf_token }}也可以使用{{ form.hidden_tag() }}来替换

同时我们也可以使用form去定义模板,跟直接用html标签去定义效果是相同的,Jinja模板引擎会将对象、属性转化为对应的html标签,
相对应的template,如下login.html:

<!-- 模板的语法应当符合Jinja语法 -->
<!-- extend from base layout -->
{% extends "base.html" %}{% block content %}<h1>Sign In</h1><form action="{{ url_for("login") }}" method="post" name="login">{{ form.csrf_token }}<p>{{ form.username.label }}<br>{{ form.username(size=80) }}<br></p><p>{{ form.password.label }}<br><!-- 我们可以传递input标签的属性,这里传递的是size属性 -->{{ form.password(size=80) }}<br></p><p>{{ form.remember_me }} Remember Me</p><p><input type="submit" value="Sign In"></p></form>
{% endblock %}

现在我们需要在view中定义相应的路由,并将相应的登录界面展示给用户。
简单起见,将view的相关路由定义放在主程序当中

# app.py
@app.route('/login')
def login():form = LoginForm()return render_template('login.html', title="Sign In", form=form)

这里简单起见,当用户请求'/login'路由时,直接返回login.html网页,注意这里的html网页是经过Jinja模板引擎将相应的模板转换后的html网页。
至此,如果我们把以上代码整合到flask当中,就应该能够看到相应的登录界面了,那么当用户提交之后,我们应当怎样存储呢?这里我们暂时先不用数据库这样复杂的工具存储,先简单地存为文件。接下来就看下如何去存储。

加密和存储

我们可以首先定义一个User类,用于处理与用户相关的操作,包括存储和验证等。

# models.pyfrom werkzeug.security import generate_password_hash
from werkzeug.security import check_password_hash
from flask_login import UserMixin
import json
import uuid# define profile.json constant, the file is used to
# save user name and password_hash
PROFILE_FILE = "profiles.json"class User(UserMixin):def __init__(self, username):self.username = usernameself.id = self.get_id()@propertydef password(self):raise AttributeError('password is not a readable attribute')@password.setterdef password(self, password):"""save user name, id and password hash to json file"""self.password_hash = generate_password_hash(password)with open(PROFILE_FILE, 'w+') as f:try:profiles = json.load(f)except ValueError:profiles = {}profiles[self.username] = [self.password_hash,self.id]f.write(json.dumps(profiles))def verify_password(self, password):password_hash = self.get_password_hash()if password_hash is None:return Falsereturn check_password_hash(self.password_hash, password)def get_password_hash(self):"""try to get password hash from file.:return password_hash: if the there is corresponding user inthe file, return password hash.None: if there is no corresponding user, return None."""try:with open(PROFILE_FILE) as f:user_profiles = json.load(f)user_info = user_profiles.get(self.username, None)if user_info is not None:return user_info[0]except IOError:return Noneexcept ValueError:return Nonereturn Nonedef get_id(self):"""get user id from profile file, if not exist, it willgenerate a uuid for the user."""if self.username is not None:try:with open(PROFILE_FILE) as f:user_profiles = json.load(f)if self.username in user_profiles:return user_profiles[self.username][1]except IOError:passexcept ValueError:passreturn unicode(uuid.uuid4())@staticmethoddef get(user_id):"""try to return user_id corresponding User object.This method is used by load_user callback function"""if not user_id:return Nonetry:with open(PROFILE_FILE) as f:user_profiles = json.load(f)for user_name, profile in user_profiles.iteritems():if profile[1] == user_id:return User(user_name)except:return Nonereturn None
  • User类需要继承flask-login中的UserMixin类,用于实现相应的用户会话管理。
  • 这里我们是直接存储用户信息到一个json文件"profiles.json"
  • 我们并不直接存储密码,而是存储加密后的hash值,在这里我们使用了werkzeug.security包中的generate_password_hash函数来进行加密,由于此函数默认使用了sha1算法,并添加了长度为8的盐值,所以还是相当安全的。一般用途的话也就够用了。
  • 验证password的时候,我们需要使用werkzeug.security包中的check_password_hash函数来验证密码
  • get_id是UserMixin类中就有的method,在这我们需要overwrite这个method。在json文件中没有对应的user id时,可以使用uuid.uuid4()生成一个用户唯一id

至此,我们就实现了第二步和第五步,接下来要看第三步,如何去维护一个session

维护用户session

先看下代码,这里把相应代码也放入到app.py当中

from forms import LoginForm
from flask_wtf.csrf import CsrfProtect
from model import User
from flask_login import login_user, login_required
from flask_login import LoginManager, current_user
from flask_login import logout_userapp = Flask(__name__)app.secret_key = os.urandom(24)# use login manager to manage session
login_manager = LoginManager()
login_manager.session_protection = 'strong'
login_manager.login_view = 'login'
login_manager.init_app(app=app)# 这个callback函数用于reload User object,根据session中存储的user id
@login_manager.user_loader
def load_user(user_id):return User.get(user_id)# csrf protection
csrf = CsrfProtect()
csrf.init_app(app)@app.route('/login')
def login():form = LoginForm()if form.validate_on_submit():user_name = request.form.get('username', None)password = request.form.get('password', None)remember_me = request.form.get('remember_me', False)user = User(user_name)if user.verify_password(password):login_user(user, remember=remember_me)return redirect(request.args.get('next') or url_for('main'))return render_template('login.html', title="Sign In", form=form)
  • 维护用户的会话,关键就在这个LoginManager对象。
  • 必须实现这个load_user callback函数,用以reload user object
  • 当密码验证通过后,使用login_user()函数来登录用户,这时用户在会话中的状态就是登录状态了

受保护网页

保护特定网页,只需要对特定路由加一个装饰器就可以,如下

# app.py# ...
@app.route('/')
@app.route('/main')
@login_required
def main():return render_template('main.html', username=current_user.username)
# ...
  • current_user保存的就是当前用户的信息,实质上是一个User对象,所以我们直接调用其属性, 例如这里我们要给模板传一个username的参数,就可以直接用current_user.username
  • 使用@login_required来标识改路由需要登录用户,非登录用户会被重定向到'/login'路由(这个就是由login_manager.login_view = 'login' 语句来指定的)

用户登出

# app.py# ...
@app.route('/logout')
@login_required
def logout():logout_user()return redirect(url_for('login'))
# ...

至此,我们就实现了一个完整的登陆和登出的过程。

另外我们可能还需要其它辅助的功能,诸如发送确认邮件,密码重置,权限分级管理等,这些功能都可以通过flask及其插件来完成,这个大家可以自己探索下啦!

作者:geekpy
链接:https://www.jianshu.com/p/06bd93e21945
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

使用Flask实现用户登陆认证的详细过程相关推荐

  1. 魔方APP项目-06-用户注册,完成短信验证码的校验、基于Celery实现短信异步发送、用户登录,jwt登陆认证、服务端提供用户登录的API接口

    一.用户注册- 1.完成短信验证码的校验 application.utils.language.message,代码: class ErrorMessage():ok = "ok" ...

  2. C#中模拟用户登陆SharePoint网站

    自动化测试一个SharePoint网站,首先要登陆,我们今天就模拟一下用户登陆SharePoint网站的过程,这一过程可以通过其他方式完成模拟,比如通过Coded UI Test录制脚本会更方便,但是 ...

  3. SSM整合Shiro进行登陆认证和授权详细配置

    本篇博客将进行详细介绍Shiro+Spring+SpringMVC+Mybatis+数据库整合并进行登陆认证和授权详细配置. SSM的整合可以参考:https://blog.csdn.net/a745 ...

  4. 用户的登陆认证、DjangoRestFramework JWT多条件登录,导航栏实现

    用户的登陆认证.DjangoRestFramework JWT&多条件登录 Django REST framework JWT JWT介绍 JWT的构成 生成规则: header payloa ...

  5. 构建具有用户身份认证的 React + Flux 应用程序

    序言:这是一篇内容详实的 React + Flux 教程,文章主要介绍了如何使用 API 获取远程数据以及如何使用 JSON Web Tokens 进行用户身份认证.在阅读本文之后,我一直使用文章介绍 ...

  6. linux用户双重认证登录,linux PAM 用户登录认证

    PAM(Pluggable Authentication Modules )是由Sun提出的一种认证机制.它通过提供一些动态链接库和一套统一的API,将系统提供的服务 和该服务的认证方式分开,使得系统 ...

  7. 微服务之间单点登录和用户权限认证的实现

    目录 单系统登录机制 HTTP无状态协议 Cookie会话机制 登录状态 多系统登录难点 单点登录系统 单点登录流程 单点注销流程 部署图 子系统与sso认证中心的功能 准备工作 项目结构 修改网关配 ...

  8. {Django基础九之中间件} 一 前戏 二 中间件介绍 三 自定义中间件 四 中间件的执行流程 五 中间件版登陆认证...

    Django基础九之中间件 本节目录 一 前戏 二 中间件介绍 三 自定义中间件 四 中间件的执行流程 五 中间件版登陆认证 六 xxx 七 xxx 八 xxx 一 前戏 我们在前面的课程中已经学会了 ...

  9. 魔方APP项目-07-客户端提交登录信息、在APICloud中集成防水墙验证码,前端获取显示并校验验证码、服务端校验验证码、保存用户登录状态,APICloud提供的数据存储、客户端保存用户登陆数据

    用户登录 一.客户端提交登录信息 html/login.html,代码: <!DOCTYPE html> <html> <head><title>登录& ...

最新文章

  1. RabbitMQ安装和运行
  2. 软件工程实践2017结对作业
  3. 物联网的下一步动作会是什么?
  4. matlab编程的步骤,如何画matlab程序的流程图?求解答
  5. 了解一下Golang的市场行情
  6. Abaqus取消汉化(汉译英,英译汉)
  7. bat计算机清理原理,如何一键清除系统垃圾bat,教您如何清理
  8. 微博话题墙 html,Js仿微博插入话题功能
  9. QQ音乐、网易云音乐、酷狗音乐歌单导入到Spotify
  10. 用VBA检查Word文档中是否存在位于行首的脚注引用,如存在则通过调整字符间距使其移动到非行首的位置
  11. 互联网金融革命已让银行家们彻夜难眠
  12. Google drive谷歌网盘下载副本文件无法调用IDM解决方法
  13. 设计分享|基于51单片机的万年历(汇编)
  14. 外卖小程序源码+后台_外卖cps外卖优惠券 赚钱小程序源码
  15. 如何使用git 生成密钥?
  16. 猿创征文 | 【STM32】ESP8266 wifi模块创建阿里云产品
  17. 硬件描述语言(HDL)
  18. MFC如何修改窗口背景色(转载)
  19. 使用DOM4J解析XML文档
  20. sip信令常用的响应码

热门文章

  1. Python基础入门之变量和数据类型二
  2. XuperChain基本功能使用
  3. 《Spring源码深度解析 郝佳 第2版》bean的加载、循环依赖的解决
  4. 202112C语言二级真题
  5. 你不知道的负压输出技巧
  6. python键盘监听模块大全_python监听、操作键盘鼠标库pynput详细教程
  7. Prescan许可证失效怎么办
  8. 「科聪分享」粗谈如何将林德L14改造成叉式搬运AGV
  9. UG中批量导出点坐标等信息
  10. 有趣的游戏编程学习网站