【Python】Flask API 登录
Flask API 登录
零、起因
最近要写uniapp客户端,服务器使用的是Python的Flask框架,为了实现用户登录,在网上查到了一些Flask的扩展,其中比较简单的就是flask_httpauth(此时版本__version__ = '4.2.1dev'),其官网给出的基本示例:
from flask import Flask
from flask_httpauth import HTTPBasicAuth
from werkzeug.security import generate_password_hash, check_password_hash
app = Flask(__name__)
auth = HTTPBasicAuth()
users = {
"john": generate_password_hash("hello"),
"susan": generate_password_hash("bye")
}
@auth.verify_password
def verify_password(username, password):
if username in users and \
check_password_hash(users.get(username), password):
return username
@app.route('/')
@auth.login_required
def index():
return "Hello, {}!".format(auth.current_user())
if __name__ == '__main__':
app.run()
浏览器访问127.0.0.1:5000会提示输入账号和密码才能访问页面,否则是错误提示。实现原理是基于http auth协议完成的,登录成功后不需要在浏览器里设置session,而是设置了一个请求头,拿上例第一个账号来说,在请求时添加请求头Authorization:Basic am9objpoZWxsbw==就可以完成对用户的认证。因为机制简单,这非常适合uniapp这种客户端的程序编写,于是决定采用flask_httpauth完成对用户的认证。但是其中遇到了问题,例如其设置的请求头,用Base64算法解am9objpoZWxsbw==,其解出来的值中有包含账号和密码,这样很容易造成密码泄露,因此我开始想办法把这里解出的密码换成用加盐散列算法generate_password_hash计算出的hash值。
壹、解决
通过对源码的阅读,我发现貌似没有相关函数可以支持把密码换成hash值,有另外一个类HTTPDigestAuth,但是需要存session,失去了简单的初衷。
首先API想访问受保护的函数就必须提供Authorization参数。分析发现默认的Authorization参数格式是Basic(空格)((用户名(冒号)密码)的base64编码)于是写一个参数生成函数,API登录时首先访问这个函数验证账号密码后获取Authorization参数值。
@app.route('/api/login', methods=['POST'])
def get_auth():
username = request.args.get('username')
print(username)
password = request.args.get('password')
print(password)
if username and password:
if username in users and check_password_hash(users.get(username), password):
print('登录成功')
token = username + ':' + users.get(username)
b64_token = base64.urlsafe_b64encode(token.encode("utf-8"))
au = b64_token.decode("utf-8")
return 'Basic {}'.format(au)
else:
return '账号或密码错误'
else:
return '参数不完整'
需要使用POST方法,在Query参数列表里传入username=hello&password=hello,使用ApiPost接口测试软件发起请求后获得响应:
Basic am9objpwYmtkZjI6c2hhMjU2OjE1MDAwMCRNc2NEdDNJTSQyMmZlZGNmZTQwNDc3YzAyMzhjNGVkMmIxOTZiZjg5ODIyN2IyOGNlOTcxY2IzOTU2NjE3MWI1NTJhYTgzMzM3
使用Base64解出来是
john:pbkdf2:sha256:150000$MscDt3IM$22fedcfe40477c0238c4ed2b196bf898227b28ce971cb39566171b552aa83337
用户名和密码hash值。
接下来是密码验证部分,因为存储的用户数据里的密码就是hash值,因此直接判断相等否就行:
@auth.verify_password
def verify_password(username, password):
if username in users and users.get(username)==password:
return username
再次使用ApiPost加上Header参数Authorization:Basic am9objpwYmtkZjI6c2hhMjU2OjE1MDAwMCRNc2NEdDNJTSQyMmZlZGNmZTQwNDc3YzAyMzhjNGVkMmIxOTZiZjg5ODIyN2IyOGNlOTcxY2IzOTU2NjE3MWI1NTJhYTgzMzM3访问127.0.0.1:5000
成功返回Hello, john!
至此,uniapp登录的基本机制解决了,并且密码安全性也得到了提高。
但是发现浏览器不能正常登录了,在flask_httpauth源码部分貌似没找到生成Authorizationd的函数。项目是用的flask_login用在网页登录部分的,因此暂时不受影响,不过在后来的flask_httpauth源码阅读中貌似发现了它可以实现把明文密码替换成密码hash下发到浏览器。稍后再做分析。
贰、重写
就一直觉得秘钥生成和认证分在不同的地方总不对,而且没有对flask_httpauth有很深的了解,生成的秘钥格式有时候不一定对得上。因此决定仿造flask_httpauth自己写一个,就暂时叫flask_apiauth吧,因为主要是为API服务的。
源文件只有一个,一个类,仿造flask_httpauth的结构:
flask_httpauth.py
# coding:utf-8
# @Time : 2021/4/24 17:08
# @Author : minuy
# @File : flask_apiauth.py
from functools import wraps
from flask import request, g
import base64
class ApiAuth(object):
def __init__(self, split_character=' '):
# 分割词,最好唯一且不出现在账号里
self.split_character = split_character
self.verify_password_callback = None
self.error_content_callback = None
def verify_password(self, f):
""" 验证密码回调,此回调返回的非空数据将放在current_user中 """ print('设置密码验证函数')
self.verify_password_callback = f
return f
def error_content(self, f):
""" 错误数据回调,此回调应返回登录、验证失败回复给客户端的内容 """ print('设置错误内容函数')
self.error_content_callback = f
return f
def get_token(self, username=None, password=None):
""" 根据账号和密码(hash)生成token,用于登录函数 """ print('生成token')
token = username + self.split_character + password
return base64.urlsafe_b64encode(token.encode("utf-8"))
def authentication_failed(self):
""" 认证失败调用 """ print('验证密码失败')
# 如果有错误内容处理,返回错误内容
if self.error_content_callback:
print('返回自定义错误数据')
return self.error_content_callback()
else:
# 否则返回文字,登录失败
return 'Login failed'
@property
def current_user(self):
""" 登录后通过这个属性获取在verify_password函数里返回的内容(用户信息) """ if hasattr(g, 'flask_api_auth_user'):
return g.flask_api_auth_user
def login_required(self, f=None):
""" 登录拦截,没有相应的请求头或者验证密码返回空值会返回错误信息 """ def login_required_internal(f):
@wraps(f)
def decorated(*args, **kwargs):
auth_user = None
if 'token' in request.headers:
print('token存在')
try:
# 把账号和密码hash都一起打包到base64里
token = base64.urlsafe_b64decode(request.headers['token']).decode('utf-8')
print('token:', token)
# 账号和密码hash之间使用空格分割
user, hash_password = token.split(self.split_character, 1)
print('user:', user, 'hash_password', hash_password)
# 如果账号和密码都存在
if user and hash_password:
auth_user = {'user': user, 'hash_password': hash_password}
except (ValueError, KeyError):
# 如果解析失败或者没有token
print('token解析失败')
pass
# 没提交参数
else:
# 在这里可以特别设置未登录的提醒
return self.authentication_failed()
print('auth', auth_user)
# 如果存在用户信息,开始验证密码
if auth_user:
user = None
# 如果有密码验证函数
if self.verify_password_callback:
print('开始验证密码')
user = self.verify_password_callback(auth_user.get('user'), auth_user.get('hash_password'))
if user:
print('密码验证成功')
# 如果user不为空,加载
g.flask_api_auth_user = user if user is not True \
else auth_user.get('user') if auth_user else None
# 如果user为空
if user in (False, None):
return self.authentication_failed()
else:
# 用户信息不存在
return self.authentication_failed()
return f(*args, **kwargs)
return decorated
if f:
return login_required_internal(f)
return login_required_internal
(注释打印可以去掉一下)
完成用户token生成(登录)和用户登录拦截(认证),众所周知,退出登录即把uniapp存储的token删除。
然后是示例代码:
test.py
from flask import Flask, request
from werkzeug.security import generate_password_hash, check_password_hash
from flask_apiauth import ApiAuth
app = Flask(__name__)
auth = ApiAuth()
users = {
"john": generate_password_hash("hello"),
"susan": generate_password_hash("bye")
}
@app.route('/api/login', methods=['POST'])
def get_auth():
username = request.args.get('username')
print(username)
password = request.args.get('password')
print(password)
if username and password:
if username in users and check_password_hash(users.get(username), password):
return auth.get_token(username, users.get(username))
else:
return {
'code': '403',
'data': {},
'message': '账号或密码错误'
}
else:
return {
'code': '403',
'data': {},
'message': '参数不完整'
}
@auth.error_content
def error_content():
return {
'code': '403',
'data': {},
'message': '请先登录'
}
@auth.verify_password
def verify_password(username, password):
if username in users and users.get(username) == password:
print('密码正确')
# 返回的数据是下面auth.current_user拿到的
return {'username': username, 'sex': '男'}
@app.route('/')
@auth.login_required
def index():
return {
'code': '200',
'data': {
'name': auth.current_user.get('username'),
'sex': auth.current_user.get('sex')
},
'message': '成功'
}
if __name__ == '__main__':
app.run()
使用ApiPost测试,登录、拦截功能正常,目前就这么用着先吧。
开源地址:Flask-APIAuth
叁、效果


【Python】Flask API 登录的更多相关文章
- Python Flask API实现方法-测试开发【提测平台】阶段小结(一)
微信搜索[大奇测试开],关注这个坚持分享测试开发干货的家伙. 本篇主要是对之前几次分享的阶阶段的总结,温故而知新,况且虽然看起来是一个小模块简单的增删改查操作,但其实涉及的内容点是非常的密集的,是非常 ...
- python flask API 返回状态码
@app.route('/dailyupdate', methods = ['POST','GET'])def dailyUpdate(): try: db=MySQLdb.connect(" ...
- [Python][flask][flask-login]关于flask-login中各种API使用实例
本篇博文跟上一篇[Python][flask][flask-wtf]关于flask-wtf中API使用实例教程有莫大的关系. 简介:Flask-Login 为 Flask 提供了用户会话管理.它处理了 ...
- 使用python+flask让你自己api(教程源代码)
1.背景 ok,这可能是很多朋友和我一样经常使用的各种api,例facebook的.github的.甚至微信api.因此,很多人都想使自己的api.在线教程在这方面它是非常小的,今天,我做了一个平稳, ...
- python之Flask实现登录功能
网站少不了要和数据库打交道,归根到底都是一些增删改查操作,这里做一个简单的用户登录功能来学习一下Flask如何操作MySQL. 用到的一些知识点:Flask-SQLAlchemy.Flask-Logi ...
- Python+Flask搭建mock api server
Python+Flask搭建mock api server 前言: 近期由于工作需要,需要一个Mock Server调用接口直接返回API结果: 假如可以先通过接口文档的定义,自己模拟出服务器返回结果 ...
- Python Flask 实现移动端应用接口(API)
引言 目前,Web 应用已形成一种趋势:业务逻辑被越来越多地移到客户端,逐渐完善为一种称为富互联网应用(RIA,rich Internet application)的架构.在 RIA 中,服务器的主要 ...
- Python Flask高级编程之RESTFul API前后端分离精讲 (网盘免费分享)
Python Flask高级编程之RESTFul API前后端分离精讲 (免费分享) 点击链接或搜索QQ号直接加群获取其它资料: 链接:https://pan.baidu.com/s/12eKrJK ...
- flask - fastapi (python 异步API 框架 可以自动生成swagger 文档) 常用示例 以及整合euraka nacos
flask - fastapi (python 异步API 框架 可以自动生成swagger 文档) 常用示例: 之前使用 flask 需要手动写文档, 这个可以自动生成, fastapi ...
- python flask实现小项目方法
本文目的是为了完成一个项目用到的flask基本知识,例子会逐渐加深.最好对着源码,一步一步走. 下载源码,运行 pip install -r requirements.txt 建立环境 python ...
随机推荐
- CDS标准视图:维护通知数据 I_PMNotifMaintenanceData
视图名称:维护通知数据 I_PMNotifMaintenanceData 视图类型:基础视图 视图代码: 点击查看代码 @EndUserText.label: 'Notification Mainte ...
- python基础学习6和7
模块类与对象 模块 内置模块 time, random, os, json 第三方模块 requests, pandas, numpy,.... 自定义模块 xxx.py 常见的内置模块 hashli ...
- manim边做边学--动画联动
今天介绍Manim中的动画联动的技巧,在数学动画中,动画联动是常用的功能, 比如讲解平面几何中三角形与圆的位置关系变化,通过动画联动可以让圆沿着三角形的边滚动,或者让三角形的顶点在圆上移动,从而直观地 ...
- 深入解析 Spring AI 系列:解析OpenAI接口对接
今天我们将主要探讨OpenAI是如何进行接口对接的,虽然我们不打算深入细节,但会对整体流程进行一个大概的了解.后续会逐步分析其中的具体细节,大家可以耐心等待,逐步展开.好的,现在让我们开始,下面是我简 ...
- biancheng-NumPy教程
目录http://c.biancheng.net/numpy/ 1NumPy是什么2NumPy下载与安装3NumPy ndarray对象4NumPy数据类型5NumPy数组属性6Numpy创建数组7N ...
- 分布式事务---2PC和3PC原理TCC事务
分布式事务(1)---2PC和3PC原理 分布式事物基本理论:基本遵循CPA理论,采用柔性事物特征,软状态或者最终一致性特点保证分布式事物一致性问题. 分布式事物常见解决方案: 2PC两段提交协议 3 ...
- 金山毒霸提示这是高危入侵行为taskeng.exe
如果安装了金山毒霸之后经常会弹窗提示:这是高位入侵行为.行为发起taskeng.exe.可疑进程regsvr32.EXE,可疑路径antivirus.php,如下入所示: 可以直接点击"阻止 ...
- 多方安全计算(3):MPC万能钥匙-混淆电路
学习&转载文章:多方安全计算(3):MPC万能钥匙-混淆电路 前言 我们在讲解不经意传输(Oblivious Transfer,OT)的文章(安全多方计算(1):不经意传输协议)中提到,利用n ...
- 个人数据保全计划:部署joplin server笔记同步服务
前言 在这个数据爆炸的时代,个人数据的价值愈发凸显,成为我们生活与工作中无可替代的重要资产.上一篇文章里,我介绍了从印象笔记迁移至 Joplin 的过程,这是我寻求数据自主掌控的关键一步.在探索同步方 ...
- SQL注入之布尔盲注
SQL注入之布尔盲注 一.布尔盲注原理 布尔盲注是一种基于布尔逻辑的SQL注入攻击技术,其核心原理是通过构造特定的SQL查询语句,利用应用程序对查询结果的不同响应(通常是真或假)来逐步推断数据库中的信 ...