一种非常常见的设置是将用户信息(用户名、密码、角色等)存储在数据库中。现在,假设我们要创建一个访问令牌,其中令牌标识是用户名,我们还希望将用户角色作为附加声明存储在令牌中。我们可以使用上一节讨论的user_claims_loader()
装饰器来实现这一点。
但是,如果我们将用户名传递给`user_claims_loader()`,我们最终需要从数据库查询该用户两次。
* 1 第一次是当登录端点被点击时,我们需要验证用户名和密码。
* 2 第二次是在`user_claims_loader()`函数中,因为我们需要查询这个用户的角色。这不是什么大事,但显然它可以更有效率。
在本章的方法中,提供了将任何对象传递给create_access_token()函数的能力,然后将该函数作为user_claims_loader()
传递。这允许我们只访问数据库一次,但是引入了一个需要解决的新问题。我们仍然需要从对象中提取用户名,这样我们就可以让用户名作为新令牌的标识。我们可以为此使用第二个装饰器user_identity_loader()
,它允许您接收传入create_access_token()
的任何对象,并从该对象返回一个json序列化的标识。
请看示例代码:
from flask import Flask, jsonify, request
from flask_jwt_extended import (
JWTManager, jwt_required, create_access_token,
get_jwt_identity, get_jwt_claims
)
app = Flask(__name__)
app.config['JWT_SECRET_KEY'] = 'super-secret' # Change this!
jwt = JWTManager(app)
#创建一个用来构建JWT数据的复杂对象
#看起来有点像SQLAlchemy 的实例
class UserObject:
def __init__(self, username, roles):
self.username = username
self.roles = roles
#定义jwt.user_claims_loader装饰器,该装饰器会在调用create_access_token函数时自动被调用,
#user_claims_loader的参数就是传递给 create_access_token的参数
#user_claims_loader返回的数据会被保存到jwt 中,作为claims存在
@jwt.user_claims_loader
def add_claims_to_access_token(user):
return {'roles': user.roles}
#user_identity_loader装饰器,该装饰器会在调用create_access_token函数时自动被调用,
#user_identity_loader的参数就是传递给 create_access_token的参数
#user_claims_loader返回的数据会被保存到jwt 中,作为identity存在
@jwt.user_identity_loader
def user_identity_lookup(user):
return user.username
@app.route('/login', methods=['POST'])
def login():
username = request.json.get('username', None)
password = request.json.get('password', None)
if username != 'test' or password != 'test':
return jsonify({"msg": "Bad username or password"}), 401
# Create an example UserObject
user = UserObject(username='test', roles=['foo', 'bar'])
# We can now pass this complex object directly to the
# create_access_token method. This will allow us to access
# the properties of this object in the user_claims_loader
# function, and get the identity of this object from the
# user_identity_loader function.
access_token = create_access_token(identity=user)
ret = {'access_token': access_token}
return jsonify(ret), 200
@app.route('/protected', methods=['GET'])
@jwt_required
def protected():
ret = {
'current_identity': get_jwt_identity(), # test
'current_roles': get_jwt_claims()['roles'] # ['foo', 'bar']
}
return jsonify(ret), 200
if __name__ == '__main__':
app.run()