flask-jwt-extended document learning

Official document

  1. Basic usage: how to use token to protect an endpoint
  2. Optional route protection: distinguish between users with and without tokens for a protected endpoint
  3. Stored data in the access token: store additional information in the token for authority management
  4. Generate tokens based on Python Object: What should I do if the information stored in 3 is stored in the database?
  5. Obtain Python Object according to the token: Obtain user information from current_user in the endpoint function
  6. Custom decorator: Perform more verification on the user's identity and permissions, and handle the permissions of users of different levels to access different protected endpoints

1. Basic usage

最基础的用法不需要很多的调用,只需要使用三个函数:
1. create_access_token()用来创建令牌
2. get_jwt_identity()用来根据令牌取得之前的identity信息
3. jwt_required()这是一个装饰器,用来保护flask节点

The official code is as follows:

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()

Insert picture description here

Results of the visit
Here I tried to request login again using the same account information, and found that both the newly obtained token and the old token can access the protected node. I feel that this is still very useful, but it may cause multiple tokens to be valid. It will be a little more troublesome for users to log out.

2. Optional route protection

# 对于一个路由节点,授权和未授权的均可以访问,但会使用不同的功能,
# 这个时候就要使用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

3. Store data in the access token

除去存放基本的用户的标识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

4. Generate tokens based on Python objects

  • (It feels that the parameter can pass any object, not limited to the str type) Generally speaking, user information will be stored in the database. If you want to use the username as the identity of the token, and add additional permission information. If the user_claims_loader in 3 is used, passing the username directly will definitely cause another query operation in the database. Once in the routing node of login, once in the function decorated by user_claims_loader.
  • The extension provides the ability to pass object objects to the create_access_token() function, and then according to the rules in 3, it will be passed to the user_claims_loader() decorator. In this case, you only need to query the parameters once in the login routing node, but you need to let the extension know the identity information (not an object), you need to use the user_identity_loader() function, which can accept all the parameters obtained by the create_access_token() function. And return a json serializable identity (get_jwt_identity() will return this value).
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()

5. Get python object based on token

  • Here is a reverse operation of obtaining tokens based on Object. When holding tokens to access protected nodes, Python objects are automatically loaded through tokens, which may be objects in Sqlalchemy (via user_loader_callback_loader() decorator). The Object can be obtained directly in the node function through get_current_user() or the current_user local agent.
  • It should be noted that if user_loader_callback_loader() accesses the database, it may increase the search overhead, regardless of whether the information in the database is needed.
# 访问受保护的节点的时候会被调用,这个函数在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"})

6. Custom decorator

  • Here is mainly to use a custom decorator to extend the function of jwt_extend. The example of the official website is to verify the user identity and permissions. In addition, it is the official series of Verify Tokens in Request decorator functions mentioned. The default authentication.
# 这里注意下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()

Guess you like

Origin blog.csdn.net/qq_42573343/article/details/107126082