官方文档
基础的用法:如何使用token保护一个endpoint可选的路由保护:对一个被保护的endpoint区分有token和没有token的用户访问令牌中的存储数据:在令牌中存储额外的信息进行权限的管理根据Python Object生成令牌:就是3中存储的信息在数据库存储着该怎么办根据令牌获取Python Object:在endpoint函数中从current_user中获得用户信息自定义装饰器:对用户的身份以及权限等进行更多的验证,处理不同级别用户访问不同的被保护的endpoint的权限
1. 基础的用法
最基础的用法不需要很多的调用,只需要使用三个函数:
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
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节点,感觉这个还是挺好用的,就是可能会导致多个令牌都有效还有就是用户退出登录会稍微麻烦些。
2. 可选的路由保护
@app
.route
('/partially-protected', methods
=['GET'])
@jwt_optional
def partially_protected():
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
3. 访问令牌中存储数据
除去存放基本的用户的标识identity外,在access_token中还可能存放其他的信息,
1. user_claims_loader
()用于将信息存储到access_token中,例子中的注释提到
该函数在create_access_token
()函数被调用后使用,参数是创建令牌的参数identity
2. get_jwt_claims
()用于在被包含的节点内获取access_token的信息
@jwt
.user_claims_loader
def add_claims_to_access_token(identity
):
return {
'hello': identity
,
'foo': ['bar', 'baz']
}
@app
.route
('/protected', methods
=['GET'])
@jwt_required
def protected():
claims
= get_jwt_claims
()
return jsonify
({
'hello_is': claims
['hello'],
'foo_is': claims
['foo']
}), 200
4. 根据Python对象生成令牌
(感觉就是说参数可以传任何对象,不限于str类型)一般来说,用户信息会存储在数据库中,如果现在想用username作为token的identity,并且添加额外的权限信息。如果用3中的user_claims_loader,直接传递username必定会导致在数据库再一次的查询操作。一次在login的路由节点,一次在user_claims_loader装饰的函数内部。扩展提供了向create_access_token()函数传递object对象的能力,然后根据3中的规则,之后会传递给user_claims_loader()装饰器。这样的话就只需要在login的路由节点查询一次参数就可以,但是需要让 扩展知道 identity的信息(不能是一个对象),需要使用user_identity_loader()函数,它可以接受create_access_token()函数获取的所有参数,并返回一个可以json序列化的identity(get_jwt_identity()会返回这个值)。
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'
jwt
= JWTManager
(app
)
class UserObject:
def __init__(self
, username
, roles
):
self
.username
= username
self
.roles
= roles
@jwt
.user_claims_loader
def add_claims_to_access_token(user
):
return {'roles': user
.roles
}
@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
user
= UserObject
(username
='test', roles
=['foo', 'bar'])
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
(),
'current_roles': get_jwt_claims
()['roles']
}
return jsonify
(ret
), 200
if __name__
== '__main__':
app
.run
()
5. 根据令牌获取python对象
这里是根据Object获取token的一个反向的操作,主要是持有token访问被保护的节点的时候,自动通过token加载Python对象,可能是Sqlalchemy中的对象(通过user_loader_callback_loader()装饰器)。该Object可以通过get_current_user()或者是current_user这个局部代理直接在节点函数中获取。需要注意的是user_loader_callback_loader()如果访问数据库,可能会增加查找的开销,不管是否需要数据库中的信息。
@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
]
)
@jwt
.user_loader_error_loader
def custom_user_loader_error(identity
):
ret
= {
"msg": "User {} not found".format(identity
)
}
return jsonify
(ret
), 404
@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"})
6. 自定义装饰器
这里主要是用自定义的装饰器来扩展jwt_extend的功能,官网的例子就是对用户身份以及权限都进行验证,此外就是提到的官方的一系列的Verify Tokens in Request的装饰器函数,用来进行默认的验证。
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
()