You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python脚本对接Flask API的访问密钥认证实现方案咨询

对你的方案2的评估

方案2逻辑可运行,但属于典型的过度设计,存在明显冗余:

  • JWT的核心价值是通过自带签名实现防篡改、自包含权限/过期信息,减少数据库查询频次。但你的设计里把accessKeyId和accessKey塞进JWT后,依然需要查数据库比对密钥哈希,完全没有用到JWT的核心特性,额外增加了JWT编解码的开销,没有带来任何安全增益。
  • 你提到的“密钥哈希存储”优势完全不需要依赖JWT实现,对方案1做小幅调整即可达成。

OpenWeatherMap这类公开API的密钥认证原理

这类服务面向脚本/第三方调用的API密钥机制本质是极简的对称凭证认证,没有复杂的令牌逻辑:

  • 服务端生成两类凭证:一类是可作为数据库索引的唯一标识(类似你提到的accessKeyId,无敏感性),一类是高熵的真实密钥(仅在生成时向用户展示一次,服务端不存储明文)
  • 服务端对真实密钥做慢哈希处理(常用算法为Argon2、bcrypt,不能用SHA-256这类快哈希,否则被拖库后容易被暴力破解),和唯一标识绑定存入数据库
  • 用户调用API时,按照服务端要求的格式把凭证放在请求头(常见格式为Authorization: Bearer <凭证>或自定义的X-API-Key头)
  • 服务端收到请求后,先从凭证里提取可索引的唯一标识,查到数据库里对应的密钥哈希,再和请求携带的明文密钥做哈希比对,匹配则放行请求。

OpenWeatherMap的实现更简单:直接给用户发放单个高熵随机字符串作为API Key,服务端存储Key的哈希值,请求时直接携带该Key即可完成校验。这种模式的缺点是当密钥量级超过十万级时,因为哈希值带盐无法直接做数据库索引,查询性能会明显下降,所以中大型服务普遍采用“ID+密钥”的拆分模式优化查询效率。

Flask/Python栈实现API密钥认证的实操指引

整个实现不需要引入JWT相关依赖,核心逻辑非常轻量:

前置依赖

安装需要的包:
pip install flask argon2-cffi requests
其中argon2-cffi用来做密钥的慢哈希处理,抗暴力破解能力优于bcrypt。

第一步:实现密钥生成逻辑

生成密钥时使用Python标准库的secrets模块(不要用random模块,熵不够不安全):

import secrets
from argon2 import PasswordHasher

ph = PasswordHasher()

def generate_api_key_pair():
    # 生成可索引的AK ID,长度足够保证唯一即可
    ak_id = secrets.token_urlsafe(16)
    # 生成高熵SK,作为真实认证凭证
    sk = secrets.token_urlsafe(32)
    # 对SK做哈希,存入数据库
    sk_hash = ph.hash(sk)
    # 此处执行数据库写入逻辑,把ak_id、sk_hash、创建时间、绑定的脚本备注等信息存库
    # 仅在这一步返回明文sk给调用方,后续服务端不再存储明文sk
    return {
        "ak_id": ak_id,
        "sk": sk,
        "auth_header_value": f"Bearer {ak_id}:{sk}"
    }

第二步:实现Flask认证装饰器

把认证逻辑封装成装饰器,方便给各个路由复用:

from functools import wraps
from flask import request, jsonify
from argon2.exceptions import VerifyMismatchError
# 导入你自己实现的数据库操作方法
from your_db_module import get_key_record_by_ak_id, update_sk_hash

def require_api_key(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        # 校验请求头格式
        auth_header = request.headers.get("Authorization")
        if not auth_header or not auth_header.startswith("Bearer "):
            return jsonify({"code": 401, "msg": "未提供有效的认证头"}), 401
        # 拆分AK ID和SK
        try:
            credential = auth_header.split(" ", 1)[1]
            ak_id, sk = credential.split(":", 1)
        except ValueError:
            return jsonify({"code": 401, "msg": "认证凭证格式错误"}), 401
        # 查询数据库记录
        key_record = get_key_record_by_ak_id(ak_id)
        if not key_record:
            return jsonify({"code": 401, "msg": "无效的API密钥"}), 401
        # 哈希比对
        try:
            ph.verify(key_record["sk_hash"], sk)
        except VerifyMismatchError:
            return jsonify({"code": 401, "msg": "无效的API密钥"}), 401
        # 自动重哈希:如果argon2的安全参数更新,自动替换库里的旧哈希
        if ph.check_needs_rehash(key_record["sk_hash"]):
            new_hash = ph.hash(sk)
            update_sk_hash(ak_id, new_hash)
        # 把AK ID挂载到request对象上,方便路由里做权限、限流等逻辑
        request.current_ak_id = ak_id
        return func(*args, **kwargs)
    return wrapper

第三步:路由中使用认证

from flask import Flask

app = Flask(__name__)

@app.route("/api/run_task", methods=["POST"])
@require_api_key
def run_task():
    # 这里可以通过request.current_ak_id拿到调用方的AK ID,做限流、操作日志记录等
    return jsonify({"code": 0, "msg": "任务执行成功", "caller": request.current_ak_id})

第四步:客户端调用实现

纯Python脚本端从环境变量读取凭证,不要把密钥硬编码在脚本里:

import os
import requests

# 提前把凭证写入系统环境变量,避免硬编码泄露
AK_ID = os.getenv("SERVICE_AK_ID")
SK = os.getenv("SERVICE_SK")

resp = requests.post(
    "http://your-service-domain/api/run_task",
    headers={"Authorization": f"Bearer {AK_ID}:{SK}"}
)
print(resp.json())

关键注意事项

  • 所有API接口必须强制使用HTTPS,否则密钥会在传输链路中被窃听,任何端侧的加密逻辑都无法弥补这个风险
  • 不要用MD5、SHA-1、SHA-256这类快速哈希算法存储密钥,否则数据库被拖库后攻击者可以极快速度暴力破解出明文密钥
  • 如果需要实现密钥过期、接口权限粒度控制、调用限流等能力,直接在数据库的密钥记录中增加对应字段,在认证装饰器中补充判断逻辑即可,不需要引入JWT增加系统复杂度
  • 密钥仅在生成时向调用方展示一次,丢失后直接生成新的密钥对即可,不要提供密钥查询功能。

内容的提问来源于stack exchange,提问作者MartinAg

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.01 21:45:47