慕课网Flask构建可扩展的RESTful API-5. Token与HTTPBasic验证 —— 用令牌来管理用户

5.1 Token

1.Token概述

以下是网站登录和使用API登录的区别

image.png

与网站登录不同的是,网站登录将登录信息写入cookie存储在浏览器,而API只负责生成token发送给客户端,而客户端怎么存储有自己决定。

  • Token具有有效期
  • Token可以标示用户身份,如存储用户id

2.获取Token令牌

密码校验--models/user.py

@staticmethod
def verify(email, password):
user = User.query.filter_by(email=email).first()
if not user:
raise NotFound('user not found')
if not user.check_password(password):
raise AuthFailed()
return {'uid': user.id}

def check_password(self, raw):
if not self._password:
return False
return check_password_hash(self._password, raw)

返回token的试图函数,这里稍微破坏一下REST的规则,由于登录操作密码安全性较高,使用GET的话会泄漏

@api.route('', methods=['POST'])
def get_token():
form = ClientForm(request).validate_for_api()
promise = {
ClientTypeEnum.USER_EMAIL: User.verify,
}
identity = promise[form.type.data](
form.account.data,
form.secret.data
)
expiration = current_app.config['TOKEN_EXPIRATION']
token = generator_auth_token(identity['uid'],
form.type.data,
None,
expiration=expiration)
t = {
'token': token.decode('utf-8')
}
return jsonify(t), 201


def generator_auth_token(uid, ac_type, scope=None,
expiration=7200):
"""生成令牌"""
s = Serializer(current_app.config['SECRET_KEY'],
expires_in=expiration)
return s.dumps({
'uid': uid,
'type': ac_type.value
})

3.Token的用处

我们不可能让任何一个用户都来访问我们获取用户资料的接口,必须对这个加以控制,也就是说只有确定了身份的用户可以访问我们的接口。

如何对这个接口做保护呢?

当用户访问问的接口的时候,我们需要获取他传来的token并进行解析验证,只有token是合法的且没有过期,我们才允许访问。

由于每个需要验证token的试图函数都需要上面的业务逻辑,所以我们可以编写一个装饰器,以面向切面的方式统一处理,编写一个函数验证token,如果验证通过,我们就继续执行试图函数的方法,如果不通过,我们就返回一个自定义异常。

libs/token_auth.py

from flask_httpauth import HTTPBasicAuth

__author__ = "gaowenfeng"

auth = HTTPBasicAuth()


@auth.verify_password
def verify_password(account, password):
return False
@api.route('/get')
@auth.login_required
def get_user():
return 'i am gwf'

5.2 HTTPBasicAuth

1.HTTPBasicAuth基本原理

除了自定义发送账号和密码之外,HTTP这种协议本身就有多种规范,来允许我们来传递账号和密码。其中一种就是HTTPBasic

HTTPBasic:需要在HTTP请求的头部设置一个固定的键值对key=Authorization,value=basic base64(account:psd)

2.以BasicAuth方式来发送token

我们可以将token作为上面所说的账号account,而密码psd传递空值

image.png

image.png


5.3 Token的发送与验证

1.验证token

auth = HTTPBasicAuth()
User = namedtuple('User', ['uid', 'ac_type', 'scope'])

@auth.verify_password
def verify_password(token, password):
user_info = verify_auth_token(token)
if not user_info:
return False
else:
g.user = user_info

return True


def verify_auth_token(token):
s = Serializer(current_app.config['SECRET_KEY'])
try:
data = s.loads(token)
# token不合法抛出的异常
except BadSignature:
raise AuthFailed(msg='token is valid', error_code=1002)
# token过期抛出的异常
except SignatureExpired:
raise AuthFailed(msg='token is expired', error_code=1003)
uid = data['uid']
ac_type = data['type']
return User(uid, ac_type, '')

2.视图函数的编写

@api.route('/<int:uid>', methods=['GET'])
@auth.login_required
def get_user(uid):
user = User.query.get_or_404(uid)
r = {
'nickname': user.nickname,
'email': user.email
}
return jsonify(r), 200

3.重写后的get_or_404,抛出自定义异常

def get_or_404(self, ident):
rv = self.get(ident)
if not rv:
raise NotFound()
return rv

def first_or_404(self):
rv = self.first()
if not rv:
raise NotFound()
return rv

4.获取令牌信息

@api.route('/secret', methods=['POST'])
def get_token_info():
"""获取令牌信息"""
form = TokenForm().validate_for_api()
s = Serializer(current_app.config['SECRET_KEY'])
try:
data = s.loads(form.token.data, return_header=True)
except SignatureExpired:
raise AuthFailed(msg='token is expired', error_code=1003)
except BadSignature:
raise AuthFailed(msg='token is invalid', error_code=1002)

r = {
'scope': data[0]['scope'],
'create_at': data[1]['iat'],
'expire_in': data[1]['exp'],
'uid': data[0]['uid']
}
return jsonify(r)

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏阮一峰的网络日志

Koa 框架教程

Node 主要用在开发 Web 应用。这决定了使用 Node,往往离不开 Web 应用框架。 ? Koa 就是一种简单好用的 Web 框架。它的特点是优雅、简洁...

70050
来自专栏张善友的专栏

Mono 3.0.2 基于双工通信的WCF应用 Demo

双工(Duplex)模式的消息交换方式体现在消息交换过程中,参与的双方均可以向对方发送消息。基于双工MEP消息交换可以看成是多个基本模式下(比如请求-回复模式和...

22360
来自专栏应兆康的专栏

Python Web - Flask笔记8

CSRF(Cross Site Request Forgery)跨站域请求伪造是一种网络攻击方式。

14510
来自专栏大内老A

WCF技术剖析之十一:异步操作在WCF中的应用(上篇)

按照操作执行所需的资源类型,我们可以将操作分为CPU绑定型(CPU Bound)操作和I/O绑定型(I/O Bound)操作。对于前者,操作的执行主要利用CPU...

21470
来自专栏xingoo, 一个梦想做发明家的程序员

Redis Lua脚本原理

2.6版本之后支持嵌入Lua脚本,客户端使用Lua脚本,直接在服务器端原子的执行多条命令 Lua脚本执行过程 创建并修改Lua环境 1 创建基础Lua环境...

41760
来自专栏抠抠空间

RESTful规范

40400
来自专栏大内老A

《WCF技术剖析(卷1)》(修订版)目录

第1章 WCF简介 (WCF Overview) 1.1 SOA的基本概念和设计思想 1.2 WCF是对现有Windows平台下分布式通信技术的整合 1.3 构...

18680
来自专栏Python

数据库连接池,本地线程,上下文管理

一、数据库连接池 flask中是没有ORM的,如果在flask里要连接数据库有两种方式 一:pymysql 二:SQLAlchemy 是pyth...

49660
来自专栏代码世界

Python数据库连接池DBUtils

 如果没有连接池,使用pymysql来连接数据库时,单线程应用完全没有问题,但如果涉及到多线程应用那么就需要加锁,一旦加锁那么连接势必就会排队等待,当请求比较多...

40010
来自专栏Flutter&Dart

DartVM服务器开发(第十二天)--Jaguar获取请求内容

当路径上为参数时,我们可以在参数名前添加:符号,表面该路径上有一个是参数,下面我们请求一下这个地址吧!

16510

扫码关注云+社区

领取腾讯云代金券