实施 HMAC 认证
HMAC 认证使用共享密钥对选定的请求组件签名。APISIX 会重新计算签名,以验证调用方持有该密钥,并确认已签名的组件未被修改。请求日期限 制了可接受签名的最大时效,但无法阻止在允许的时钟偏差窗口内重放请求。由于客户端和网关共享签名密钥,HMAC 认证不提供不可否认性。
在本指南中,你将实现一个场景,其中有两个消费者使用 HMAC 认证 向 APISIX 进行身份认证,每个消费者具有不同的限流限速配额。一旦实施,消费者应该能够访问上游服务并将消费者 ID 转发到上游服务,从而为额外的业务逻辑提供选项。
前置条件
- 安装 Python 3 以生成当前请求的签名。
- 如果使用 Admin API 示例且启用了身份验证,请将管理员角色的密钥导出为
ADMIN_API_KEY。请在deployment.admin.admin_key下配置该密钥。Docker 快速入门为了便于本地评估而禁用了身份验证;生产环境中请保持启用。
创建消费者
消费者是指使用 API 的应用程序或开发者。在使用 APISIX 内置身份认证方法时,你应该始终创建消费者。
创建一个具有可选自定义 ID 和 30 秒窗口内一个请求的限流限速配额的消费者 johndoe:
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"username": "johndoe",
"labels": {
"custom_id": "john-doe-junior"
},
"plugins": {
"limit-count": {
"count": 1,
"time_window": 30,
"rejected_code": 429
}
}
}'
如果你希望实施额外的业务逻辑,自定义 ID 将转发到上游服务。
创建另一个具有可选自定义 ID 和 30 秒窗口内两个请求的限流限速配额的消费者 janedoe:
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"username": "janedoe",
"labels": {
"custom_id": "jane-doe-senior"
},
"plugins": {
"limit-count": {
"count": 2,
"time_window": 30,
"rejected_code": 429
}
}
}'
创建消费者凭证
凭证用于配置与消费者关联的身份认证凭证。
为 johndoe 创建 hmac-auth 凭证:
curl "http://127.0.0.1:9180/apisix/admin/consumers/johndoe/credentials" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "cred-john-hmac-auth",
"plugins": {
"hmac-auth": {
"key_id": "john-key",
"secret_key": "john-secret-key"
}
}
}'
为 janedoe 创建 hmac-auth 凭证:
curl "http://127.0.0.1:9180/apisix/admin/consumers/janedoe/credentials" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "cred-jane-hmac-auth",
"plugins": {
"hmac-auth": {
"key_id": "jane-key",
"secret_key": "jane-secret-key"
}
}
}'
创建路由
创建一个路由并启用 hmac-auth:
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "hmac-auth-route",
"uri": "/anything",
"methods": ["GET"],
"plugins": {
"hmac-auth": {}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
创建签名
为每个消费者生成当前的 Date 和对应的 HMAC-SHA256 签名。签名路径必须与请求路径完全一致,包括任何查询字符串。将以下脚本保存为 hmac-sig-header-gen.py:
import base64
import hashlib
import hmac
import shlex
from email.utils import formatdate
for prefix, key_id, secret in [
("JOHN", "john-key", "john-secret-key"),
("JANE", "jane-key", "jane-secret-key"),
]:
date = formatdate(usegmt=True)
signing_string = f"{key_id}\nGET /anything\ndate: {date}\n"
digest = hmac.new(secret.encode(), signing_string.encode(), hashlib.sha256).digest()
signature = base64.b64encode(digest).decode()
authorization = (
f'Signature keyId="{key_id}",algorithm="hmac-sha256",'
f'headers="@request-target date",signature="{signature}"'
)
print(f"export {prefix}_HMAC_DATE={shlex.quote(date)}")
print(f"export {prefix}_HMAC_AUTH={shlex.quote(authorization)}")
将生成的请求头加载到当前 Shell:
eval "$(python3 hmac-sig-header-gen.py)"
默认的时钟偏差窗口为 300 秒。如果请求头过旧,请重新运行脚本,并确保每个 Date 与同时生成的签名配对使用。时间戳限制请求时效,但不能阻止在该接受窗口内重放请求。
验证
使用生成的标头,使用 johndoe 的凭证向路由发送请求:
curl -X GET "http://127.0.0.1:9080/anything" \
-H "Date: ${JOHN_HMAC_DATE}" \
-H "Authorization: ${JOHN_HMAC_AUTH}"
你应该看到类似以下的 HTTP/1.1 200 OK 响应:
{
"args": {},
"data": "",
"files": {},
"form": {},
"headers": {
...
"X-Consumer-Username": "johndoe",
"X-Credential-Identifier": "cred-john-hmac-auth",
"X-Consumer-Custom-Id": "john-doe-junior",
"X-Forwarded-Host": "127.0.0.1"
},
...
}
等待当前 30 秒配额窗口结束,然后使用 John 的密钥发送三个请求:
sleep 31
resp=$(seq 3 | xargs -I{} curl -X GET "http://127.0.0.1:9080/anything" \
-H "Date: ${JOHN_HMAC_DATE}" \
-H "Authorization: ${JOHN_HMAC_AUTH}" \
-o /dev/null -s -w "%{http_code}\n") && \
count_200=$(echo "$resp" | grep "200" | wc -l) && \
count_429=$(echo "$resp" | grep "429" | wc -l) && \
echo "200": $count_200, "429": $count_429
你应该看到以下响应,显示在 3 个请求中,1 个请求成功,而其他请求被拒绝:
200: 1, 429: 2
使用 jane 的凭证向路由生成三个请求:
resp=$(seq 3 | xargs -I{} curl -X GET "http://127.0.0.1:9080/anything" \
-H "Date: ${JANE_HMAC_DATE}" \
-H "Authorization: ${JANE_HMAC_AUTH}" \
-o /dev/null -s -w "%{http_code}\n") && \
count_200=$(echo "$resp" | grep "200" | wc -l) && \
count_429=$(echo "$resp" | grep "429" | wc -l) && \
echo "200": $count_200, "429": $count_429
你应该看到以下响应,显示在 3 个请求中,2 个请求成功,而另一个被拒绝:
200: 2, 429: 1
最后,如有需要请重新生成请求头,然后发送一个格式正确但故意使用错误签名的 Authorization 请求头:
curl -X GET "http://127.0.0.1:9080/anything" \
-H "Date: ${JOHN_HMAC_DATE}" \
-H 'Authorization: Signature keyId="john-key",algorithm="hmac-sha256",headers="@request-target date",signature="AAAA"'
你应该看到 HTTP/1.1 401 Unauthorized 响应,并带有类似于以下消息:
{"message":"client request can't be validated"}
下一步
你现在已经学会了如何实施 HMAC 认证。APISIX 还支持其他内置身份认证方法,例如密钥认证、基本认证和 JWT 认证。