官方文档
- 基础的用法:如何使用token保护一个endpoint
- 可选的路由保护:对一个被保护的endpoint区分有token和没有token的用户
- 访问令牌中的存储数据:在令牌中存储额外的信息进行权限的管理
- 根据Python Object生成令牌:就是3中存储的信息在数据库存储着该怎么办
- 根据令牌获取Python Object:在endpoint函数中从current_user中获得用户信息
- 自定义装饰器:对用户的身份以及权限等进行更多的验证,处理不同级别用户访问不同的被保护的endpoint的权限
最基础的用法不需要很多的调用,只需要使用三个函数:
1. create_access_token()用来创建令牌
2. get_jwt_identity()用来根据令牌取得之前的identity信息
3. jwt_required()这是一个装饰器,用来保护flask节点
官方的代码如下:
import json
from flask import Flask, jsonify, request
from flask_jwt_extended import (
JWTManager, jwt_required, create_access_token,
get_jwt_identity
)
app = Flask(__name__)
app.config['JWT_SECRET_KEY'] = 'super-secret'
jwt = JWTManager(app)
@app.route('/login', methods=['POST'])
def login():
if not request.is_json:
return jsonify({
"msg": "Missing JSON in request"}), 400
# 这部分需要看下POST请求的格式
data = request.get_data()
data = json.loads(data)
username = data.get('username', None)
password = data.get('password', None)
if not username:
return jsonify({
"msg": "Missing username parameter"}), 400
if not password:
return jsonify({
"msg": "Missing password parameter"}), 400
if username != 'test' or password != 'test':
return jsonify({
"msg": "Bad username or password"}), 401
access_token = create_access_token(identity=username)
return jsonify(access_token=access_token), 200
@app.route('/protected', methods=['GET'])
@jwt_required
def protected():
current_user = get_jwt_identity()
return jsonify(logged_in_as=current_user), 200
if __name__ == '__main__':
app.run()
这里尝试了使用同一个账户信息再次请求login,发现新获取的令牌和旧的令牌均可以访问protected节点,感觉这个还是挺好用的,就是可能会导致多个令牌都有效还有就是用户退出登录会稍微麻烦些。
# 对于一个路由节点,授权和未授权的均可以访问,但会使用不同的功能,
# 这个时候就要使用jwt_optional()装饰器,
# 至于判断是否是有token的用户,可以根据get_jwt_identity()函数的返回值判断
@app.route('/partially-protected', methods=['GET'])
@jwt_optional
def partially_protected():
# If no JWT is sent in with the request, get_jwt_identity()
# will return None
current_user = get_jwt_identity()
if current_user:
return jsonify(logged_in_as=current_user), 200
else:
return jsonify(logged_in_as='anonymous user'), 200
除去存放基本的用户的标识identity外,在access_token中还可能存放其他的信息,
1. user_claims_loader()用于将信息存储到access_token中,例子中的注释提到
该函数在create_access_token()函数被调用后使用,参数是创建令牌的参数identity
2. get_jwt_claims()用于在被包含的节点内获取access_token的信息
# 该函数在creat_access_token()被调用后使用
@jwt.user_claims_loader
def add_claims_to_access_token(identity):
return {
'hello': identity,
'foo': ['bar', 'baz']
}
# In a protected view, get the claims you added to the jwt with the
# get_jwt_claims() method
@app.route('/protected', methods=['GET'])
@jwt_required
def protected():
claims = get_jwt_claims()
return jsonify({
'hello_is': claims['hello'],
'foo_is': claims['foo']
}), 200
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)
# Python对象,可以是一个SQLAlchemy的用来ORM的对象
class UserObject:
def __init__(self, username, roles):
self.username = username
self.roles = roles
# Create a function that will be called whenever create_access_token
# 在create_access_token()之后调用, 并获取前者的参数,
# 决定get_jwt_claims()函数的返回值
@jwt.user_claims_loader
def add_claims_to_access_token(user):
return {
'roles': user.roles}
# 在create_access_token调用之后使用,获取相关的所有参数,返回的是每个token的标识符
@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
# 可以是相关的Sqlalchemy的查询操作
user = UserObject(username='test', roles=['foo', 'bar'])
# 传递object之后,可以使用user_identity_loader获取token需要的identity
# 在user_claims_loader存储其他信息
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()
# 访问受保护的节点的时候会被调用,这个函数在token被验证后调用,可以使用get_jwt_claims()获取相关的信息,如果加载没有成功,规定需要返回None
@jwt.user_loader_callback_loader
def user_loader_callback(identity):
if identity not in users_to_roles:
return None
return UserObject(
username=identity,
roles=users_to_roles[identity]
)
# 上面函数返回None的错误处理,展示给客户端看的信息
@jwt.user_loader_error_loader
def custom_user_loader_error(identity):
ret = {
"msg": "User {} not found".format(identity)
}
return jsonify(ret), 404
# 如果user_loader_callback返回的是None,这个节点就不会执行函数,
# 此外就是可以通过current_user函数以及get_current_user()方法访问对象
@app.route('/admin-only', methods=['GET'])
@jwt_required
def protected():
if 'admin' not in current_user.roles:
return jsonify({
"msg": "Forbidden"}), 403
else:
return jsonify({
"msg": "don't forget to drink your ovaltine"})
# 这里注意下wraps的使用,涉及到闭包函数的使用,不用的话会导致fn这个函数的
# 属性都消失,具体的可以看下functools的wraps的用法
def admin_required(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
verify_jwt_in_request()
claims = get_jwt_claims()
if claims['roles'] != 'admin':
return jsonify(msg='Admins only!'), 403
else:
return fn(*args, **kwargs)
return wrapper
@jwt.user_claims_loader
def add_claims_to_access_token(identity):
if identity == 'admin':
return {
'roles': 'admin'}
else:
return {
'roles': 'peasant'}
@app.route('/login', methods=['POST'])
def login():
username = request.json.get('username', None)
access_token = create_access_token(username)
return jsonify(access_token=access_token)
@app.route('/protected', methods=['GET'])
@admin_required
def protected():
return jsonify(secret_message="go banana!")
if __name__ == '__main__':
app.run()