API认证之AK-SK机制详解
API认证的AK-SK机制详解:安全可靠的接口鉴权方案 摘要: AK-SK是一种广泛应用于云服务和开放平台的API认证机制,由Access Key(公开标识)和Secret Key(机密密钥)组成。其核心工作原理是客户端使用SK对请求内容进行HMAC签名,服务器端通过AK查找对应SK并验证签名真实性。本文详细解析了AK-SK的安全特性、签名算法实现流程,包括规范请求构造、签名计算方法,并提供了Ja
API认证之AK-SK机制详解
1. AK-SK简介
AK/SK(Access Key / Secret Key)是一对用于API认证的密钥,类似用户名和密码的组合。
1.1 AK(Access Key)
访问密钥,用于标识用户身份。
| 属性 | 说明 |
|---|---|
| 作用 | 告诉服务器"我是谁" |
| 格式 | 通常为字母数字组合,如 AKIAIOSFODNN7EXAMPLE |
| 保密性 | 可以公开(会随请求发送) |
| 泄露后果 | 别人可以冒充你发起请求,但无法伪造签名 |
类比: 银行卡号 - 告诉别人用来给你打钱
1.2 SK(Secret Key)
秘密密钥,用于验证请求真实性。
| 属性 | 说明 |
|---|---|
| 作用 | 对请求内容进行签名,证明"请求确实是我发出的" |
| 格式 | 通常为更长的随机字符串 |
| 保密性 | 绝对不能泄露,不传输、不存储在客户端 |
| 泄露后果 | 别人可以伪造任意请求,后果严重 |
类比: 银行卡密码 - 丢了钱就没了
1.3 安全对比
AK泄露:风险可控
├── 别人只能知道你是谁
├── 无法伪造合法请求
└── 可以轮换SK修复
SK泄露:灾难性后果
├── 攻击者可以伪造任意请求
├── 可以执行任何操作
└── 必须立即禁用并重新生成
1.4 应用场景
AK/SK认证广泛应用于以下场景:
| 场景 | 说明 | 典型厂商 |
|---|---|---|
| 云服务API | 调用云厂商的API管理资源 | AWS、阿里云、腾讯云、华为云 |
| 开放平台 | 第三方应用调用平台能力 | 微信开放平台、支付宝开放平台 |
| 微服务间认证 | 内部服务间的API调用鉴权 | 服务网格、API网关 |
| IoT设备认证 | 设备与云端的安全通信 | 智能家居、工业物联网 |
| 数据开放平台 | 对外提供数据接口 | 政务数据、企业数据共享 |
| CI/CD集成 | 自动化工具调用API | GitHub Actions、Jenkins |
为什么选择AK/SK而不是账号密码?
- 无状态:服务器不需要存储会话状态
- 可审计:每个请求都有独立签名,便于追踪
- 可控制:可以限制AK的权限范围、IP、有效期
- 可轮换:定期更换SK,降低泄露风险
2. 工作原理
签名计算详解(客户端)
输入:
├── HTTP方法:GET
├── URI:/api/users
├── 查询参数:limit=10&page=1(已排序)
├── 请求头:host:api.example.com, date:2026-06-04T10:00:00Z
└── 请求体哈希:空(GET请求)
处理:
├── Step 1: 构造规范请求(Canonical Request)
├── Step 2: 对规范请求做SHA256哈希
├── Step 3: 构造签名字符串(Algorithm + 时间戳 + 哈希值)
└── Step 4: 用SK对签名字符串做HMAC-SHA256
输出:
└── Base64编码后的签名字符串
签名验证详解(服务器)
输入:
├── 客户端传来的AK
├── 客户端传来的签名
├── 完整的HTTP请求(用于重新计算签名)
处理:
├── Step 1: 根据AK查找对应的SK
├── Step 2: 检查时间戳是否在±5分钟内
├── Step 3: 检查Nonce是否已使用过
├── Step 4: 用相同算法重新计算签名
└── Step 5: 对比两个签名是否一致
输出:
├── 一致 → 处理请求
└── 不一致 → 返回401/403
3. 签名算法
重要说明:
签名只用SK,不用AK。AK只是告诉服务器"我是谁",服务器根据AK找到对应的SK来验证签名。
3.1 规范请求构造
规范请求(Canonical Request)是将HTTP请求的关键信息按固定格式拼接:
CanonicalRequest = HTTPMethod + "\n" +
URI + "\n" +
QueryString + "\n" +
CanonicalHeaders + "\n" +
SignedHeaders + "\n" +
HexEncode(Hash(RequestPayload))
示例:
GET ← HTTP方法
/api/users ← URI
limit=10&page=1 ← 查询参数(已排序)
host:api.example.com ← 请求头1的值
date:2026-06-04T10:00:00Z ← 请求头2的值
← 空行分隔
host:date ← SignedHeaders:声明签了哪些请求头(分号分隔)
e3b0c44298fc1c149afbf4c8... ← 请求体的SHA256哈希(空body)
3.2 完整请求示例
GET /api/users?page=1&limit=10 HTTP/1.1
Host: api.example.com
Date: 2026-06-04T10:00:00Z
X-AK: AKIAIOSFODNN7EXAMPLE
X-Signature: 02987e4b68a7e3d4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8
3.3 阿里云签名
阿里云使用HMAC-SHA1算法,签名通过URL参数传递(不是请求头)。
签名计算公式:
Signature = Base64( HMAC-SHA1( AccessSecret, UTF-8-Encoding-Of(StringToSign) ) )
完整请求示例:
https://ecs.aliyuncs.com/
?Action=DescribeInstances
&Format=JSON
&Version=2014-05-26
&AccessKeyId=LTAI****************
&SignatureMethod=HMAC-SHA1
&Timestamp=2026-06-04T10:00:00Z
&SignatureVersion=1.0
&SignatureNonce=ed8fb51f-0c38-4da4-a21a-f189b3a7aecb
&Signature=PPwf***********************
参数说明:
| 参数 | 说明 |
|---|---|
| Action | API操作名称 |
| Format | 返回格式(JSON/XML) |
| Version | API版本 |
| AccessKeyId | AK |
| SignatureMethod | 签名算法(HMAC-SHA1) |
| Timestamp | 时间戳 |
| SignatureVersion | 签名算法版本(1.0) |
| SignatureNonce | 随机数,防重放 |
| Signature | 签名值 |
注意:
阿里云签名通过URL参数传递,不是请求头。这与AWS不同(AWS用请求头)。
官方文档: 阿里云官方文档
4. Java代码示例
4.1 签名方法
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
/**
* 使用HMAC-SHA256算法对请求进行签名
*
* @param secret 密钥(SK)
* @param stringToSign 待签名的字符串(规范请求)
* @return Base64编码的签名结果
*/
public static String sign(String secret, String stringToSign) throws Exception {
// 获取HMAC-SHA256算法实例
Mac mac = Mac.getInstance("HmacSHA256");
// 用SK初始化Mac对象
mac.init(new SecretKeySpec(secret.getBytes("UTF-8"), "HmacSHA256"));
// 计算HMAC签名
byte[] signBytes = mac.doFinal(stringToSign.getBytes("UTF-8"));
// 将字节数组转换为Base64字符串
return Base64.getEncoder().encodeToString(signBytes);
}
4.2 签名校验方法
import java.security.MessageDigest;
/**
* 校验签名是否有效
*
* @param secret 密钥(SK)
* @param stringToSign 待签名的字符串(与签名时相同)
* @param expectedSignature 客户端传来的签名
* @return true表示签名有效,false表示签名无效
*/
public static boolean verify(String secret, String stringToSign, String expectedSignature) throws Exception {
// 用相同的SK重新计算签名
String calculatedSignature = sign(secret, stringToSign);
// 使用时间安全的比较方法,防止时序攻击
// 普通的equals()会在发现第一个不同字符时立即返回,攻击者可以通过计时推断出签名
return MessageDigest.isEqual(
calculatedSignature.getBytes("UTF-8"),
expectedSignature.getBytes("UTF-8")
);
}
4.3 使用示例
// ===== 客户端签名 =====
// 1. 定义请求参数
String url = "/api/users?limit=10&page=1"; // 完整URL路径+参数
String host = "api.example.com";
String date = "2026-06-04T10:00:00Z";
String bodyHash = "abc123"; // 空body时为固定值
// 2. 构造需要签名的请求头
String signedHeaders = "host:date";
String header1 = "host:" + host;
String header2 = "date:" + date;
// 3. 构造规范请求字符串(StringToSign)
String stringToSign = String.join("\n",
"GET", // HTTP方法
url, // 完整URL(路径+参数)
header1, // Host请求头
header2, // Date请求头
"", // 空行分隔
signedHeaders, // 签名的请求头列表
bodyHash // 请求体哈希
);
// 4. 使用SK进行签名
String signature = sign(sk, stringToSign);
// 5. 将签名添加到请求头
// 请求头:X-AK: <access_key>, X-Signature: <signature>
// ===== 服务器校验 =====
// 1. 从请求头获取AK和签名
// 2. 根据AK查找对应的SK
// 3. 用相同的SK和请求内容重新计算签名
boolean isValid = verify(sk, stringToSign, clientSignature);
// 4. 根据校验结果处理请求
if (!isValid) {
// 签名无效,拒绝请求
throw new SecurityException("签名验证失败");
}
// 签名有效,继续处理业务逻辑
说明:
- 使用JDK标准库(
javax.crypto.Mac、java.util.Base64)- 使用
MessageDigest.isEqual()进行时间安全比较,防止时序攻击- 生产环境建议使用HMAC-SHA256(如阿里云用SHA1则改为
HmacSHA1)
5. 安全机制
5.1 时间戳防重放
请求携带时间戳
├── 服务器检查时间戳是否在允许范围内(如±5分钟)
├── 超出范围的请求直接拒绝
└── 防止请求被录制后重复发送
形象类比:
时间戳 = 验证码有效期
验证码5分钟内有效,过期就不能用了
5.2 Nonce防重放
每次请求携带唯一随机数(Nonce)
├── 服务器记录已使用的Nonce
├── 相同Nonce的第二次请求直接拒绝
└── 彻底防止重放攻击
形象类比:
Nonce = 一次性优惠券
用过就作废,别人捡到也没用
Nonce存储方案:
| 方案 | 特点 | 适用场景 |
|---|---|---|
| Redis | SETEX key ttl value,自动过期 |
分布式系统(推荐) |
| 数据库 | 带expire_time字段,定时清理 |
数据量小、需要审计 |
| 内存缓存 | Caffeine/Guava Cache,设maxSize和过期时间 | 单机部署 |
// Redis存储示例
// 存储Nonce(5分钟过期)
redis.setex("nonce:" + nonce, 300, "1");
// 检查Nonce是否已存在
boolean exists = redis.exists("nonce:" + nonce);
关键点:
- 必须设过期时间,否则内存会爆
- 过期时间 > 请求最大有效期(如5分钟)
- 分布式环境用Redis,单机用内存缓存即可
5.3 范围限制
限制AK的使用范围:
├── IP白名单:只允许特定IP调用
├── 服务范围:只能访问特定服务
├── 时间范围:AK有有效期
└── 操作范围:只能执行特定操作
6. 安全最佳实践
6.1 密钥管理
| 实践 | 说明 |
|---|---|
| 不要硬编码 | 密钥存在环境变量或密钥管理服务 |
| 定期轮换 | 每3-6个月更换一次SK |
| 最小权限 | AK只授予必要的权限 |
| 分离环境 | 开发、测试、生产用不同的AK |
6.2 传输安全
| 实践 | 说明 |
|---|---|
| 必须用TLS | AK/SK + 明文HTTP = 灾难 |
| 不要在URL中传递 | 参数会被记录在日志中 |
| 使用HTTPS | 确保传输过程加密 |
6.3 代码实现
| 实践 | 说明 |
|---|---|
| 使用官方SDK | 不要自己实现签名逻辑 |
| 时间同步 | 确保服务器时间准确 |
| 错误处理 | 不要泄露签名细节 |
7. 常见问题
7.1 Q: AK泄露了怎么办?
1. 立即禁用该AK
2. 生成新的AK/SK对
3. 更新所有使用该AK的客户端
4. 检查是否有异常调用
7.2 Q: SK泄露了怎么办?
1. 立即禁用该AK(SK无法单独禁用)
2. 生成新的AK/SK对
3. 检查是否有数据泄露
4. 考虑是否需要通知用户
7.3 Q: 如何安全存储SK?
├── 环境变量(不写在代码里)
├── 密钥管理服务(如AWS KMS、阿里云KMS)
├── 配置中心(加密存储)
└── 绝对不要提交到Git
8. 总结
| 概念 | 一句话解释 |
|---|---|
| AK | 标识身份,可以公开 |
| SK | 签名验证,必须保密 |
| 签名 | 用SK对请求内容计算摘要 |
| 验证 | 服务器用同样的SK重新计算并对比 |
| 时间戳 | 防止请求被重放 |
| Nonce | 一次性随机数,彻底防重放 |
| TLS | 必须配合使用,保护传输安全 |
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐
所有评论(0)