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

优化基于哈希API密钥的verifyKey中间件性能方案咨询

解决API密钥认证性能问题的方案

你的核心问题在于全量拉取所有哈希密钥并循环比对——bcrypt是为慢哈希设计的(防暴力破解),循环N次就等于把单次验证的耗时放大了N倍,这直接导致性能雪崩。另外你之前尝试直接哈希请求密钥再查库失败,是因为bcrypt每次哈希都会生成随机盐,相同明文的哈希结果并不固定,没法直接用哈希值做等值查询。

正确的解决方案思路

给每个API密钥绑定一个可查询的唯一标识符,先通过标识符定位到对应的哈希密钥,再做一次bcrypt比对。这样每次认证只需要1次SQL查询+1次bcrypt验证,性能会和未启用中间件时接近。

步骤1:调整数据库表结构

给ApiKeys表新增一个唯一字段,比如key_prefix(存储API密钥的前8位,或者生成密钥时单独生成的唯一ID),确保这个字段有索引:

ALTER TABLE ApiKeys ADD COLUMN key_prefix VARCHAR(16) NOT NULL UNIQUE;
CREATE INDEX idx_key_prefix ON ApiKeys(key_prefix);

步骤2:修改API密钥生成逻辑

生成API密钥时,拆分出前缀和完整密钥,哈希完整密钥后和前缀一起存入数据库:

const bcrypt = require("bcrypt");
const crypto = require("crypto");

// 生成API密钥
async function generateApiKey() {
  const rawKey = crypto.randomBytes(32).toString("hex");
  // 取前8位作为前缀
  const keyPrefix = rawKey.slice(0, 8);
  const hashedKey = await bcrypt.hash(rawKey, 10); // 10是bcrypt的成本因子,按需调整

  // 存入数据库:key_prefix, hashed_api_key
  // cxn.query("INSERT INTO ApiKeys (key_prefix, hashed_api_key) VALUES (?, ?)", [keyPrefix, hashedKey])

  // 返回给用户完整密钥(用户需要用这个密钥请求)
  return rawKey;
}

步骤3:优化verifyKey中间件

从请求Header的API密钥中拆分出前缀,用前缀查询对应的哈希密钥,再做一次bcrypt比对:

const bcrypt = require("bcrypt");
const cxn = require("../models/db.js");

const verifyKey = async (req, res, next) => {
  const apiKey = req.headers.apikey;
  if (!apiKey) {
    return res.status(401).json({ message: "API密钥缺失" });
  }

  // 拆分前缀(和生成逻辑对应,取前8位)
  const keyPrefix = apiKey.slice(0, 8);
  
  try {
    // 只查询对应前缀的哈希密钥
    const [result] = await cxn.promise().query(
      `SELECT hashed_api_key FROM ApiKeys WHERE key_prefix = ?`,
      [keyPrefix]
    );

    if (result.length === 0) {
      return res.status(401).json({ message: "无效的API密钥" });
    }

    // 只做一次bcrypt比对
    const isMatch = await bcrypt.compare(apiKey, result[0].hashed_api_key);
    if (isMatch) {
      return next();
    } else {
      return res.status(401).json({ message: "无效的API密钥" });
    }
  } catch (err) {
    console.error(err);
    return res.status(500).json({ message: "服务器内部错误" });
  }
};

module.exports = { verifyKey };

额外优化建议

  • 缓存验证结果:用Redis缓存已验证通过的API密钥(比如缓存key为api_key:${keyPrefix},值为true,有效期设为5分钟),避免重复查询数据库和bcrypt比对。
  • 调整bcrypt成本因子:如果性能还是有压力,可以适当降低bcrypt的成本因子(比如从10降到8),但要权衡安全性。
  • 统一错误处理:原代码未处理SQL查询错误、未在比对失败时返回401,优化后的代码补上了这些逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 20:30:31