Flask_实现token鉴权

1、安装依赖

pip install flask
pip install pycryptodome

2、实现代码

import random
import string
import time
import base64

from functools import wraps

from flask import Flask, jsonify, session, request, make_response, g
from cryptography.hazmat.primitives.ciphers.aead import AESGCM


SECRET_KEY = "DnKRYZbvVzdhPlF01rtcxmi5Cj36AbCd"


app = Flask(__name__)
app.config["SECRET_KEY"] = SECRET_KEY


# ========================= 数据加密解密方法 ==============================================
def encrypt_aes_gcm(key, data, nonce_len=32):
    """
    AES-GCM加密
    :param key: 密钥。16, 24 or 32字符长度的字符串
    :param data: 待加密字符串
    :param nonce_len: 随机字符串长度
    """
    key = key.encode('utf-8')
    if not isinstance(data, str):
        data = str(data)
    data = data.encode('utf-8')
    # 生成32位长度的随机值,保证相同数据加密后得到不同的加密数据
    nonce = ''.join(random.sample(string.ascii_letters + string.digits, nonce_len))
    nonce = nonce.encode("utf-8")

    # 生成加密器
    cipher = AESGCM(key)
    # 加密数据
    crypt_bytes = cipher.encrypt(nonce, data, associated_data=None)
    return base64.b64encode(nonce + crypt_bytes).decode()


def decrypt_aes_gcm(key, cipher_data, nonce_len=32):
    """
    AES-GCM解密
    :param key: 密钥
    :param cipher_data: encrypt_aes_gcm 方法返回的数据
    :param nonce_len: 随机字符串长度
    :return:
    """
    key = key.encode('utf-8')

    # 进行base64解码
    debase64_cipher_data = base64.b64decode(cipher_data)

    # 提取密文数据
    nonce = debase64_cipher_data[:nonce_len]
    cipher_data = debase64_cipher_data[nonce_len:]

    # 解密数据
    cipher = AESGCM(key)
    plaintext = cipher.decrypt(nonce, cipher_data, associated_data=None)
    return plaintext.decode()


# ========================= 鉴权部分 ==============================================
def generate_token(username, expiration=3600):
    """ 生成token生成token的密钥
    :param username: 生成token的信息
    :param expiration: token有效时间,单位秒
    """
    expiration = int(time.time() + expiration)
    data = {'username': username, "expiration": expiration}
    return encrypt_aes_gcm(SECRET_KEY, data)


def decrypt_token(token):
    """ 解析token """
    data = decrypt_aes_gcm(SECRET_KEY, token)
    return eval(data)


def login_required(func):
    """ 鉴权装饰器 """

    @wraps(func)
    def wrapper(*args, **kwargs):
        # 获取存储的token(如果登录视图使用redis存储的token,这里需要改为从redis获取)
        s_token = session.get("token")

        # 获取请求中带的token
        r_token = request.headers.get("token")

        # 验证请求中是否带有token
        if r_token is None:
            return jsonify(code="4000", msg="鉴权失败")

        # 验证服务器存储的session是否存在
        if s_token is None:
            return jsonify(code="4010", msg="授权失效")

        # 验证token是否匹配
        if s_token != r_token:
            return jsonify(code="4000", msg="鉴权失败")

        # 验证token是否失效
        data = decrypt_token(r_token)
        g.username = data.get("username")     # g变量存储username。(g变量每次请求会重置,可以理解为同一个视图的全局变量)
        expiration = data.get("expiration")
        if expiration < int(time.time()):
            return jsonify(code="4010", msg="授权失效")

        # 还可以继续验证接口签名
        return func(*args, **kwargs)
    return wrapper


# ========================= api接口 ==============================================
@app.post('/login')
def login():
    req = eval(request.data)
    username = req.get('username')
    password = req.get('password')

    # 验证账密
    if username != "admin" and password != "admin":
        return jsonify(code="1000", msg="用户名或密码错误")

    # 账密验证通过,生成token
    token = generate_token(username)

    # 存储token(建议改用redis存储)
    session["token"] = token

    # 定义响应信息
    resp = make_response(jsonify(code="0", data={'token': token}, msg="登录成功"))
    resp.headers["token"] = token
    return resp


@app.post('/index')
@login_required
def index():
    # 视图中使用加密token用到的用户数据
    username = g.username
    return jsonify(code="0", data=f"{username}发起请求", msg="请求成功")


if __name__ == '__main__':
    app.run()

流程图

3、测试

请求接口不传token

请求接口传有效token

请求接口传失效token

 

posted @ 2023-03-26 13:00  码上测  阅读(541)  评论(0编辑  收藏  举报