API Key 认证原理与实现技术方案

5 阅读5分钟

一、概述

API Key(应用程序接口密钥)是一种轻量级的身份认证机制,用于识别和验证调用方身份。它适用于机器对机器(M2M)通信场景,具有实现简单、性能高、易于管理等优点。

⚠️ 注意:API Key 不是用于用户登录(应使用 OAuth 2.0 / JWT),而是用于标识客户端应用或服务

🔍 API Key 本质上只是一个字符串凭证,其验证逻辑完全由业务系统自定义,因此:

  • 没有统一的颁发流程
  • 没有标准的令牌结构
  • 没有跨系统的互操作需求(通常只用于自家 API)

二、核心设计原则

原则说明
无状态每次请求独立验证,不依赖会话
最小权限每个 Key 应限制可访问的资源和操作
安全存储服务端绝不存储原始 Key,仅存哈希值
可撤销 & 可轮换支持立即禁用或生成新 Key
默认过期强烈建议设置有效期(如 90 天)

三、认证流程

sequenceDiagram
    participant Client as 客户端
    participant Server as 服务端
    participant DB as 数据库

    Client->>Server: 请求头携带 X-API-Key: sk_xxx
    Server->>Server: 对 sk_xxx + 固定盐 做 SHA-256 哈希
    Server->>DB: 查询 key_hash = 'a1b2c3...'
    DB-->>Server: 返回记录(含 expires_at, user_id, 权限等)
    alt 找不到 / 已禁用 / 已过期
        Server-->>Client: 403 Forbidden
    else 验证通过
        Server->>Server: 检查限流、权限、IP 白名单等
        Server->>Client: 返回业务数据
    end

关键说明:

  • API Key 字符串本身不包含任何元数据(如过期时间、权限)
  • 所有属性均存储在服务端数据库中,通过 key_hash 关联
  • 客户端只需原样传递原始 Key,无需任何加密或哈希处理

四、数据模型设计

表名:api_keys

字段名类型必填说明
idBIGINT主键
key_hashVARCHAR(255)哈希后的 Key(唯一)
user_idBIGINT所属用户/租户 ID
app_nameVARCHAR(100)应用名称(便于管理)
permissionsTEXT / JSON权限列表,如 ["read:data", "write:logs"]
rate_limitINT每分钟最大请求数(默认 100)
expires_atDATETIME过期时间(NULL 表示永不过期)
is_activeBOOLEAN是否启用(默认 true)
created_atDATETIME创建时间
last_used_atDATETIME最后使用时间(用于审计)

🌍 时区建议:所有时间字段统一使用 UTC 时间


五、API Key 生命周期管理

1. 生成规则

  • 格式:sk_<prefix>_<48位随机字符>
  • 示例:sk_live_aB3xK9mQpL2vR8nT7yU4wZ1cE6fG0hJ5
  • 随机性:使用密码学安全随机数(Java SecureRandom
public String generateRawApiKey() {
    String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
    return "sk_" + new SecureRandom().ints(48, 0, chars.length())
        .mapToObj(chars::charAt)
        .collect(StringBuilder::new, StringBuilder::append, StringBuilder::append)
        .toString();
}

2. 存储安全

  • 绝不存储原始 Key
  • 使用 固定盐 + SHA-256 哈希(因 API Key 为高熵随机串,无需 bcrypt)
  • 盐值从环境变量读取,禁止硬编码
// application.yml
app:
  security:
    api-key-salt: ${API_KEY_SALT}
public String hashApiKey(String rawKey) {
    return DigestUtils.sha256Hex(rawKey + fixedSalt);
}

3. 过期机制

  • 创建时可指定有效期(如 30/90/365 天)或“永不过期”
  • 验证时检查:if (expiresAt != null && now > expiresAt) → 403
  • 过期 Key 无法恢复,需创建新 Key(轮换)

4. 轮换(Rotate)

  • 用户点击“重新生成” → 创建新 Key + 禁用旧 Key
  • 原始 Key 仅在创建时返回一次,之后不可见

六、Java 实现(Spring Boot)

1. 实体类

@Entity
@Table(name = "api_keys")
public class ApiKey {
    private Long id;
    private String keyHash;      // 哈希值
    private Long userId;
    private String appName;
    private LocalDateTime expiresAt;
    private Boolean active = true;
    // ... 其他字段
}

2. 认证过滤器

@Component
public class ApiKeyAuthFilter implements Filter {

    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {
        String rawKey = ((HttpServletRequest) req).getHeader("X-API-Key");
        if (rawKey == null) {
            // 返回 401
        }

        String keyHash = apiKeyHasher.hash(rawKey);
        ApiKey record = apiKeyRepo.findByKeyHash(keyHash);

        if (record == null || !record.getActive()) {
            // 返回 403
        }

        if (record.getExpiresAt() != null &&
            LocalDateTime.now(ZoneOffset.UTC).isAfter(record.getExpiresAt())) {
            // 返回 403: "API key has expired"
        }

        // 继续请求
        chain.doFilter(req, res);
    }
}

3. 创建接口

@PostMapping("/api-keys")
public ResponseEntity<?> createApiKey(@RequestBody CreateKeyRequest req) {
    String rawKey = apiKeyService.generateRawApiKey();
    String keyHash = apiKeyHasher.hash(rawKey);
    
    LocalDateTime expiresAt = null;
    if (req.getExpireDays() != null) {
        expiresAt = LocalDateTime.now(ZoneOffset.UTC).plusDays(req.getExpireDays());
    }

    apiKeyRepo.save(new ApiKey(keyHash, ..., expiresAt));
    
    // 仅在此处返回原始 Key!
    return ok(Map.of("api_key", rawKey, "expires_at", expiresAt));
}

七、安全最佳实践

措施说明
强制 HTTPS防止 Key 在传输中被窃听
Header 传递禁止通过 URL 参数(避免日志泄露)
限流保护基于 Key 的请求频率控制(Redis + 滑动窗口)
IP 白名单(可选)高安全场景可绑定调用 IP
审计日志记录每次 Key 使用(时间、IP、Endpoint)
定期轮换默认 90 天过期,支持自动提醒
泄露监控使用工具扫描代码仓库是否泄露 Key

八、与其他认证方式对比

方式适用场景安全性复杂度
API Key服务间调用、内部系统、简单集成
OAuth 2.0第三方授权、用户委托
JWT无状态用户会话、微服务中高
Basic Auth内部测试、临时调试低(需 HTTPS)极低

选择建议

  • 如果是你的服务调用你的 API → 用 API Key
  • 如果是第三方代表用户访问数据 → 用 OAuth 2.0

九、常见问题 FAQ

Q1:API Key 能防重放攻击吗?

❌ 不能。如需防重放,需额外引入 nonce + timestamp 机制(通常用于支付等高安全场景)。

Q2:为什么不用 JWT 存储过期信息?

JWT 适合自包含令牌,但 API Key 更强调集中管理(如随时禁用、修改权限)。JWT 一旦签发就无法撤销(除非引入黑名单)。

Q3:盐值泄露了怎么办?

因 API Key 是高熵随机值,即使知道盐和哈希,也几乎无法暴力破解。但应立即轮换所有 Key 并更换盐值。

Q4:为什么 JWT一旦签发就难以撤销,而 API Key 可以随时禁用

✅一句话总结:

JWT 是“一次性通行证”,发出去就管不了; API Key 是“门禁卡”,后台随时可以拉黑。

能力JWTAPI Key
是否需要查库验证否(无状态)是(有状态)
能否立即禁用❌ 不能(除非加黑名单)✅ 能(改 is_active
适合场景用户会话、需要自包含信息机器身份、服务间调用
性能极高(无 IO)中(需查库/缓存)
安全性控制粒度粗(靠过期时间)细(可随时开关、限流、改权限)