<span class="nav-link-with-icon">HMAC <img src="https://doc.traefik.io/traefik-hub/img/ps-traefik-hub-logo-light.svg" class="menu-icon" alt="Traefik Hub API Gateway"></span>
Traefik Hub 功能
此中间件仅在 Traefik Hub 中提供。了解更多 Traefik Hub 高级功能。
此中间件验证使用 HTTP 请求内容和共享密钥计算出的数字签名,
该签名通过 Authorization 或 Proxy-Authorization 头发送给代理。
它确保:
- 发送方身份(认证):如果代理成功验证签名,就意味着发送方 确实持有共享密钥。因此,发送方的身份被视为已证实。
- 请求完整性:由于签名基于 HTTP 请求的一部分,如果代理 成功验证签名,则意味着生成签名所使用的请求在发送方与代理之间 没有被修改。此中间件还允许使用 Digest 头验证内容完整性。
此中间件基于 HTTP 签名草案。
配置示例¶
以下高级配置启用 HMAC 中间件,设置一个密钥,验证请求体的 摘要校验和,并确保计算请求签名时必须包含指定的 请求头。
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: hmac-auth
spec:
plugin:
hmac:
keys:
- id: secret-key
key: secret
validateDigest: true
enforcedHeaders:
- (request-target)
- (created)
- (expires)
- host
配置选项¶
| 字段 | 说明 | 默认值 | 必填 |
|---|---|---|---|
keys |
HMAC 中间件使用的一组静态密钥。 | 是 | |
validateDigest |
决定中间件是否验证请求体的摘要校验和。 | true | 否 |
enforcedHeaders |
计算请求签名时必须包含的一组请求头。 | 否 |
认证机制¶
发送方和代理共享一个由 keyId 标识的密钥。发送方如何获取密钥并安全保存,不在本文档讨论范围内。
构造 Authorization 头¶
要认证请求,发送方必须提供符合 HMAC 认证方案的 Authorization 或 ProxyAuthorization 头。
该头携带一组参数:
Authorization: Hmac keyId="secret-id-1",algorithm="hmac-sha256",headers="(request-target) (created) (expires) host x-example",signature="c29tZXNpZ25hdHVyZQ==",created="1584453022",expires="1584453032"
| 参数 | 说明 | 示例 |
|---|---|---|
keyId |
发送方构造签名时所用密钥的标识符。 | keyId="secret-key-1" |
algorithm |
用于生成签名的算法。 支持 hmac-sha1、hmac-sha256、hmac-sha384 和 hmac-sha512。 |
algorithm="hmac-sha512" |
headers |
用于构造签名字符串的请求头列表。 每一项都必须为小写。 |
headers="host content-type" |
signature |
请求的数字签名。参阅计算签名。 | signature="c29tZXNpZ25hdHVyZQ==" |
created |
签名创建时的 Unix 时间戳。 | created="1574453022" |
expires |
签名过期时的 Unix 时间戳。 | expires="1574453022" |
时间敏感性
如果 created 时间戳位于未来,或 expires 时间戳位于过去,中间件将拒绝请求。
这种行为使此中间件对客户端与服务器之间的时钟偏差敏感。
请确保客户端与服务器的时钟已同步。
计算签名¶
使用 signature string 和发送方的 secret key 计算 HMAC 签名算法,再对结果进行 base64 编码,得到签名。
例如:
signature=base64(HMAC(signatureString, secret))
构造签名字符串¶
经过网关、代理及其他实体传输时,已签名的 HTTP 请求需要能够容忍一些微小变化。 因此,不能对整个请求签名,因为单个请求头的修改就可能导致签名失效。
为避免此问题,此中间件根据发送方在认证头的 headers 参数中定义的部分请求头值构造 signature string。
构造签名字符串时,客户端必须按 headers 参数中出现的顺序提取各请求头的值,然后对每一项应用以下逻辑:
- 如果是特殊请求头,则按照特殊请求头值一节计算其值。
- 如果不是特殊请求头,则追加小写的请求头名称、ASCII 冒号
:、ASCII 空格 ` ` 以及请求头值。 如果请求头有多个值,则追加这些值,并用 ASCII 逗号,和 ASCII 空格 ` ` 分隔。 - 如果当前值不是最后一个值,则追加 ASCII 换行符
\n。签名字符串末尾不得包含 ASCII 换行符。
警告
所有请求头值都会去除两端的空格。"
特殊请求头值¶
按照设计,并非 HTTP 请求的全部信息都能通过请求头获得,但使用这些信息保护请求仍然有意义。
为此,headers 参数接受可供使用的特殊请求头名称。
| 值 | 说明 | 签名字符串示例 |
|---|---|---|
(request-target) |
将小写的 :method、ASCII 空格和 :path 伪头连接而成(遵循 HTTP/2 规范)。 |
(request-target): get /api/V1/resource?query=foo |
(created) |
认证头中 created 参数的值。 |
(created): 1584453022 |
(expires) |
认证头中 expires 参数的值。 |
(expires): 1584453082 |
计算值时,在特殊请求头名称之后依次追加 ASCII 冒号 :、ASCII 空格 ` `,以及相应的值。
(created): 1929494939
(request-target): get /foo/bar签名字符串示例¶
以下示例设置了这些认证头参数:
headers="(request-target) (created) (expires) host x-example x-emptyheader cache-control"created="1584466921"expires="1584466931"
GET /foo HTTP/1.1
Host: example.org
X-Example: Example header
with some whitespace.
X-EmptyHeader:
X-NotIncluded: always
Cache-Control: max-age=60
Cache-Control: must-revalidate(request-target): get /foo
(created): 1584466921
(expires): 1584466931
host: example.org
x-example: Example header with some whitespace.
x-emptyheader:
cache-control: max-age=60, must-revalidate强制包含的请求头¶
可以配置中间件,强制要求使用最小的一组请求头来创建签名字符串。 这意味着,任何未在签名中包含这些强制请求头的请求都会被拒绝。
此选项还会配置发起认证时返回的请求头列表。
默认值为 (request-target) (created) (expires)。
始终强制包含 (created) 和 (expires)
created 和 expires 头参数用于防范重放攻击。
为确保它们的值在传输中不被修改,强烈建议始终使用 (created) 和 (expired) 特殊请求头值,将这些参数值纳入签名。
因此,建议始终配置中间件,强制包含 (created) 和 (expires)。
发起认证¶
代理可以发起认证。它会返回 401 Unauthorized 响应,并携带 WWW-Authenticate 头,指示使用 Hmac 认证方案。
WWW-Authenticate: Hmac headers="(request-target) (created) (expires) host x-example"
此头表明发送方需要提供符合 Hmac 认证方案的 Authorization 头。
它还通过 headers 参数指明必须纳入签名的请求头列表。
强制包含的请求头
WWW-Authenticate 头携带的请求头列表,就是中间件配置中指定的强制请求头列表。
验证请求体完整性¶
将 digest 头纳入签名字符串,可以确保传入请求的请求体在传输中没有被修改。
默认情况下,只要存在摘要,此中间件就会验证请求体的摘要校验和。
计算校验和时,仅支持 SHA-256 和 SHA-512。
可能的 CPU 和内存使用量
验证摘要时,中间件会读取请求体并计算其校验和。 因此,这可能导致代理消耗大量内存和 CPU。
要禁用此功能而仅执行认证,请在中间件配置中将 validateDigest 设为 false。
在生产环境使用 Traefik OSS?
如果你在工作中使用 Traefik,可以考虑为 Traefik OSS 增加企业级 API 网关能力或商业支持。
为 Traefik OSS 增加 API 网关能力快捷且平滑,无需推倒重来,现有配置也会完整保留。请通过这个简短视频了解实际效果。