使用 HMAC Auth 认证访问

最近更新时间:2024-07-18 10:17:11

我的收藏

操作场景

本文介绍如何在 Kong 云原生 API 网关上通过 HMAC Authentication 认证插件实现认证访问。

前置条件

已购买 Kong 网关实例,操作文档
配置了后端(Service)以及路由(Route)。

操作步骤

本场景操作步骤以配置 Route 插件为例,指导如何实现 HMac Auth 认证访问。

步骤1:配置 HMAC Authentication 认证插件

1. 登录 TSE 控制台,进入需要配置限流插件的 Kong 网关实例详情页,在 Konga 控制台页面查看管理控制台登录方式。

2. 登录 Konga 管理控制台,进入需要限流的 Route 详情页,单击 Add Plugin 按钮创建插件,在 Authentication 分组下选择 Hmac Auth 插件。

3. 设置密钥身份认证插件参数,单击 enter 键,并保存。
consumer:填写需要应用访问控制的 Consumer ID,如空缺,表示该 IP 访问控制应用于所有 consumer。
hide credentials:是否隐藏来自上游服务的凭据。
anonymous:如果身份验证失败,则用作“匿名”使用者的可选字符串(使用者 uuid)值。如果为空(默认),则请求将失败,并且身份验证失败4xx。 请注意,此值必须引用 Kong 内部的 Consumer id 属性,而不是其 custom_id。
clock skew:时钟偏移在几秒钟内以防止重放攻击。
validate request body:是否启用 body 验证。
enforce headers:客户端至少应用于 HTTP 签名创建的 header 列表。
algorithms:用户想要支持的 HMAC 摘要算法列表。允许的值为 hmac-sha1,hmac-sha256,hmac-sha384 和 hmac-sha512,默认全部支持。


步骤2:创建 Consumer

1. 进入 CONSUMERS 页面创建 Consumer。

username:用户(应用)名,必须指定此字段或 custom_id。
custom_id:用于将使用者映射到另一个数据库的自定义标识符。必须指定此字段或 username。
Tags:标签。

2. 为 Consumer 创建 HMAC Auth 凭证。

username:在 HMAC 签名验证中使用的用户名。
secret:secret 值,默认为空会自动生成 secret。


步骤3:签名认证方案

期望客户端使用以下参数化发送 Authorization 或 Proxy-Authorization header:
例如: Authorization: hmac username="username", algorithm="hmac-sha1", headers="x-date digest", signature="Base64(HMAC-SHA1(signing_str, secret))"

签名参数

username:凭证的 username。
algorithm:用于创建签名的数字签名算法。
headers:用于对请求进行签名的 HTTP 标头名称列表,由单个空格字符分隔。
signature:客户端生成的 Base64 编码数字签名。

签名字符串构造

为了生成使用密钥签名的字符串,客户端必须按照它们出现的顺序获取 headers 指定的每个 HTTP 标头的值。
1. 如果标题名称不是 request-line,则附加小写标题名称,后跟 ASCII 冒号:和 ASCII 空格 。
2. 如果标题名称是 request-line,则附加 HTTP 请求行(ASCII 格式),否则追加标题值。
3. 如果 value 不是最后一个值,则附加 ASCII 换行符\\n。字符串绝不能包含尾随的 ASCII 换行符。
4. 服务器和请求客户端应与 NTP 同步,并且应使用 X-Date 或 Date header 发送有效日期(使用 GMT 格式, 例如 Mon, 19 Mar 2018 12:08:40 GMT)。

正文验证 Body Validation

用户可以将 config.validate_request_body 设置为 true 以验证请求正文。如果启用,插件将计算请求正文的 SHA-256 HMAC 摘要,并将其与 Digest header 的值进行匹配。 摘要 header 需要采用以下格式: Digest: SHA-256=base64(sha256(<body>))
如果没有请求主体,则应将 Digest 设置为0长度的主体的摘要。
注意
为了创建请求主体的摘要,插件需要将其保留在内存中,这可能会在处理大型主体(几个MB)或高请求并发期间对工作者的 Lua VM 造成压力。

步骤4:API 请求 Demo

说明:
以下Demo仅供参考,请依据实际使用场景进行修改。
Python版本
# -*- coding: utf-8 -*-
import base64
import datetime
import hashlib
import hmac
import json
import requests
from urllib.parse import urlparse, urlencode

#username
Username = 'xxx'
#secret
Secret = 'xxxx'

# 访问地址
Url = 'http://test.com/'
HTTPMethod = 'POST' # method
Accept = 'application/json'
ContentType = 'application/json'

urlInfo = urlparse(Url)
Host = urlInfo.hostname
Path = urlInfo.path

Digest = ''
GMT_FORMAT = '%a, %d %b %Y %H:%M:%S GMT'
xDate = datetime.datetime.utcnow().strftime(GMT_FORMAT)

# 修改 body 内容
if HTTPMethod == 'POST' :
body = { "arg1": "a", "arg2": "中文" }
body_json = json.dumps(urlencode(body))
body_digest = hashlib.sha256(body_json.encode()).digest()
Digest = "SHA-256=" + base64.b64encode(body_digest).decode()
print(Digest)

# 获取签名串
signing_str = 'x-date: %s\\ndigest: %s' % (
xDate, Digest)

# 计算签名
sign = hmac.new(Secret.encode(), msg=signing_str.encode(), digestmod=hashlib.sha1).digest()
sign = base64.b64encode(sign).decode()
auth = "hmac username=\\"" + Username + "\\", algorithm=\\"hmac-sha1\\", headers=\\"x-date digest\\", signature=\\""
sign = auth + sign + "\\""

# 发送请求
headers = {
'Host': Host,
'Accept': Accept,
'Content-Type': ContentType,
'x-date': xDate,
'Authorization': sign
}

if Digest :
headers['Digest'] = Digest

if HTTPMethod == 'GET' :
ret = requests.get(Url, headers=headers)
if HTTPMethod == 'POST' :
ret = requests.post(Url, headers=headers, data=(body_json))

print(ret.headers)
print(ret.text)
Java版本
package org.example;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.Base64;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;

public class Main {
public static void main(String[] args) throws Exception {
String username = "xxxx";
String secret = "xxxx";
String url = "http://www.test.com/";
String httpMethod = "POST";
String accept = "application/json";
String contentType = "application/json";

URI uri = new URI(url);
String host = uri.getHost();

String digest = "";
SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
format.setTimeZone(TimeZone.getTimeZone("GMT"));
String xDate = format.format(new Date());

if (httpMethod.equals("POST")) {
String body = "arg1=a&arg2=" + java.net.URLEncoder.encode("中文", "UTF-8");
byte[] bodyDigest = java.security.MessageDigest.getInstance("SHA-256").digest(body.getBytes(StandardCharsets.UTF_8));
digest = "SHA-256=" + Base64.getEncoder().encodeToString(bodyDigest);
System.out.println(digest);
}

String signingStr = "x-date: " + xDate + "\\ndigest: " + digest;
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA1"));
byte[] signBytes = mac.doFinal(signingStr.getBytes(StandardCharsets.UTF_8));
String sign = Base64.getEncoder().encodeToString(signBytes);
String auth = "hmac username=\\"" + username + "\\", algorithm=\\"hmac-sha1\\", headers=\\"x-date digest\\", signature=\\"";
sign = auth + sign + "\\"";

CloseableHttpClient httpClient = HttpClients.createDefault();
HttpPost httpPost = new HttpPost(url);
httpPost.setHeader("Host", host);
httpPost.setHeader("Accept", accept);
httpPost.setHeader("Content-Type", contentType);
httpPost.setHeader("x-date", xDate);
httpPost.setHeader("Authorization", sign);

if (!digest.isEmpty()) {
httpPost.setHeader("Digest", digest);
}

if (httpMethod.equals("POST")) {
StringEntity entity = new StringEntity("arg1=a&arg2=" + java.net.URLEncoder.encode("中文", "UTF-8"), "UTF-8");
httpPost.setEntity(entity);
}

HttpResponse response = httpClient.execute(httpPost);
HttpEntity responseEntity = response.getEntity();
System.out.println(EntityUtils.toString(responseEntity));
}
}

相关说明

更多相关说明请参见 Kong 插件