操作场景
本文介绍如何在 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 base64import datetimeimport hashlibimport hmacimport jsonimport requestsfrom urllib.parse import urlparse, urlencode#usernameUsername = 'xxx'#secretSecret = 'xxxx'# 访问地址Url = 'http://test.com/'HTTPMethod = 'POST' # methodAccept = 'application/json'ContentType = 'application/json'urlInfo = urlparse(Url)Host = urlInfo.hostnamePath = urlInfo.pathDigest = ''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'] = Digestif 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));}}