flask之token认证
使用token的理由
为符合restful api风格,我们的服务器应该是无状态的,即不应该在服务端维持每个用户状态,不需要为每个登录用户维护一个会话,从而改进服务器的性能
这种无状态的前后端通信可以通过token实现,具体讲就是
- 后端验证密码成功,用户成功登录之后,将用户的身份信息加密成有一定时效性的token返回前端
- 前端后续每次请求都将token插入请求header当中,供后端认证请求者的身份
- 后端得到任意请求后检查请求header中的token,解密token得到用户身份信息,然后进行后续操作
从头到尾,服务器都没有维持过用户的登录信息,服务器都不知道具体的某个用户是否是在登录状态,只有等到请求到达,检查出token合法,服务器才知道,这个用户是处于登录之中的
flask中封装token的package使用
flask在itsdangerous这个package中封装了TimedJSONWebSignatureSerializer类,即有一定时效性的json web token(简称jwt)
使用方法
-
构造实例时传入秘钥参数、有效时长参数’expires_in’(单位是s),这个秘钥应该由你自己设定,不建议使用明文写在代码、或者明文存在磁盘数据库中,为了安全起见,建议在程序运行时手动输入一个秘钥,由程序读取后存入内存,下次服务器程序再次启动时重新手动输入上次输入的秘钥,一次保证安全性
- 使用上一步构造的实例,调用TimedJSONWebSignatureSerializer类的dumps方法,传入一个对象以此创建一个带有加密用户信息、具有时效性的token,对象中的键值对记录了该用户的信息,比如 { id:’1’, username:’user’ } 就是一个合格的例子。创建好之后,将这个token放到response的body中,返回给前端
- 当有请求从前端过来,解析request中的header,获取其中的token字段,进行解密,判断是否有SignatureRxpired过期、BadSignature无法解密,如果都没有,就将解密的用户信息放入服务器内存,进行使用
具体代码如下:
# 步骤1代码:
s = Serializer(current_app.config['SECRET_KEY'], expires_in=3600)
# 步骤2代码:
return jsonify(data={
'message':'Login successfully',
'token':s.dumps({'idUser': user['idUser'], 'email': user['email']}).decode('utf-8')
})
#前端收到token后将token放入后续请求的头部:
get({headers={'Authorization':'加密后的token'},body={}})
# 步骤3代码
# 请求代码:在请求函数前加上login_required
@bp.route('/user/', methods=['GET'])
@auth.login_required
def get_info():
pass
上面的代码中,我们看到,通过在请求处理函数之前加上修饰@auth.login_required就能够完成token验证(如果验证失败则通过另外的处理机制向前端返回报错),其实只有上面的代码还不够,我们还需要定义login_required函数来处理、解析token:
想要定义自己的login_required函数,可以这样做:
from flask_httpauth import HTTPTokenAuth
auth = HTTPTokenAuth()
@auth.verify_token
def verify_token(token):
g.user = None
s = Serializer(current_app.config['SECRET_KEY'])
try:
data = s.loads(token)
except SignatureExpired:
# token正确但是过期了
return False
except BadSignature:
# token错误
return False
if 'idUser' in data:
db = get_db()
g.user = db.execute(
'SELECT * FROM User WHERE idUser = ?', (data['idUser'],)
).fetchone()
return True
return False
当然,上面提到的处理验证失败的机制也是可以自定义的,可以这样:
@auth.error_handler
def error_handler():
return jsonify({ code:401, message:'401 Unauthorized Access' })
实现原理探究
如果你只想知道怎么做能够完成flask的token认证,那么这篇教学到此为止,可以不用看下去了
但是作为一名合格的程序员,我们不应该满足于怎么做,还应该去探求为什么,所以我们是不是应该进一步知道为什么这样写可以呢:
想要知道为什么,最简单的方法就是翻源码,直接到flask_httpauth的github上看具体实现。
flask_httpauth.HTTPTokenAuth的实现,代码&解读如下:
# HTTPTokenAuth类继承HTTPAuth
class HTTPTokenAuth(HTTPAuth):
def __init__(self, scheme='Bearer', realm=None):
super(HTTPTokenAuth, self).__init__(scheme, realm)
self.verify_token_callback = None
def verify_token(self, f):
self.verify_token_callback = f
return f
def authenticate(self, auth, stored_password):
if auth:
token = auth['token']
else:
token = ""
if self.verify_token_callback:
return self.verify_token_callback(token)
return False
# 父类HTTPAuth
class HTTPAuth(object):
def __init__(self, scheme=None, realm=None):
self.scheme = scheme
self.realm = realm or "Authentication Required"
self.get_password_callback = None
self.auth_error_callback = None
def default_get_password(username):
return None
def default_auth_error():
return "Unauthorized Access"
self.get_password(default_get_password)
self.error_handler(default_auth_error)
# 认证错误处理机制
def error_handler(self, f):
@wraps(f)
def decorated(*args, **kwargs):
res = f(*args, **kwargs)
res = make_response(res)
if res.status_code == 200:
res.status_code = 401
if 'WWW-Authenticate' not in res.headers.keys():
res.headers['WWW-Authenticate'] = self.authenticate_header()
return res
# 把auth_error_callback设置为自己,login_required认证失败之后调用自己,处理异常
self.auth_error_callback = decorated
return decorated
# 登录需要
def login_required(self, f):
@wraps(f)
def decorated(*args, **kwargs):
auth = self.get_auth()
if request.method != 'OPTIONS': # pragma: no cover
password = self.get_auth_password(auth)
if not self.authenticate(auth, password):
request.data
return self.auth_error_callback()
return f(*args, **kwargs)
return decorated
为了抓住重点,我上面只是贴了重要部分的代码,完整代码参见flaskauth,
其实你可能会疑惑,为什么完成token验证是在请求处理函数之前加上修饰@auth.login_required但是实际上定义自己的login_required函数是通过@auth.verify_token修改来完成的,下面就为你解答:
我们可以试着溯源,从请求到达后端开始分析,前端发来/user/,method=get的api过来,被flask通过路由@bp.route(‘/user/’, methods=[‘GET’]),引导到get_info函数,然而在执行get_info函数之前,需要先执行login_required
login_required是哪来的呢,我们翻看HTTPTokenAuth,没有这个成员函数,再向前看,看到HTTPTokenAuth的父类HTTPAuth,终于找到HTTPTokenAuth.login_required函数
这个函数接受一个函数f作为参数,并对f进行修饰,其实在这里,get_info函数就是这个f,login_required中,先执行用户认证,认证通过再调用f,即get_info,否则调用auth_error_callback处理错误
-
正常处理:
-
login_required中通过get_auth获得request.header.Authorization,这里面存的就是token
-
然后调用authenticate函数进行认证,authenticate不是父类HTTPAuth的成员,所以找回HTTPTokenAuth,果然找到了
-
在HTTPTokenAuth的authenticate中,调用了verify_token_callback
-
verify_token_callback又是哪来的呢:
def verify_token(self, f): self.verify_token_callback = f return f
-
即verify_token将输入的f,即我们自定义的那个verify_token赋值给self.verify_token_callback
@auth.verify_token def verify_token(token):
-
即在HTTPTokenAuth的authenticate中调用了我们自定义的verify_token
-
-
上面authenticate调用了自定义的verify_token进行认证,如果不出错就将解密的用户信息从数据库载入服务器内存,否则返回false给login_required
-
login_required接到false的话就调用后面的‘认证不通过处理’函数
-
-
认证不通过处理:
- 如果正常处理中authenticate返回False,认证失败,那么将调用self.auth_error_callback进行认证失败处理
- self.auth_error_callback来自于HTTPAuth类的error_handler,error_handler接受一个被修饰的函数f(我们可以通过@auth.error_handler来自定义这个f),在上面的代码中,被修饰的f就是我们自定义的error_handler,在这个error_handler中,我们直接返回身份认证失败的信息,错误码401
-
到此为止,案子破了—-接到请求后,login_required
- 先通过get_auth获得request.header.Authorization中的token
- 然后调用authenticate,进而调用我们自己定义的verify_token来解析token
- 如果解析不出错,就将解密的用户信息从数据库载入服务器内存
- 否则返回false
- login_required接到false后调用error_handler进而调用自定义的error_handler,返回身份认证失败的信息,错误码401
总结
如果您只想知道用法,那么仅仅需要了解flask中封装token的package使用
如果您想要知道为什么能够做到认证,那么您还需要看实现原理探究
以上,足以讲清楚flask如何使用token认证用户身份这一技术,以及为什么能够这样做,让您能够知其然并且知其所以然。
最后感谢阅读