慕课网Flask构建可扩展的RESTful API-5. Tok
2018-06-16 本文已影响45人
9c0ddf06559c
专题章节目录
慕课网Flask高级编程实战-知识点思维导图
慕课网Flask高级编程实战-1.项目准备 和 Flask入门
慕课网Flask高级编程实战-2.搜索书籍路由编写
慕课网Flask高级编程实战-3.蓝图、模型与CodeFirst
慕课网Flask高级编程实战-4.flask核心机制
慕课网Flask高级编程实战-5.书籍详情页面的构建
慕课网Flask高级编程实战-6.书籍详情页面的构建
慕课网Flask高级编程实战-7.静态文件、模板、消息闪现与Jinja2
慕课网Flask高级编程实战-8.用户登录与注册
慕课网Flask高级编程实战-9.书籍交易模型(数据库事务、重写Flask中的对象)
慕课网Flask高级编程实战-10.鱼书业务处理
慕课网Flask高级编程实战-11.Python与Flask的结合应用
慕课网Flask构建可扩展的RESTful API-1. 起步与红图
慕课网Flask构建可扩展的RESTful API-2. REST基本特征
慕课网Flask构建可扩展的RESTful API-3. 自定义异常对象
慕课网Flask构建可扩展的RESTful API-5. Token与HTTPBasic验证 —— 用令牌来管理用户
慕课网Flask构建可扩展的RESTful API-6. 模型对象的序列化
慕课网Flask构建可扩展的RESTful API-7. 权限控制
5.1 Token
1.Token概述
以下是网站登录和使用API登录的区别

与网站登录不同的是,网站登录将登录信息写入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传递空值


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)