使用AK(Access Key ID)、SK(Secret Access Key)对请求进行签名。
生成 AK、SK
如果已生成过AK/SK,则可跳过此步骤,找到原来已下载的AK/SK文件,文件名一般 为:credentials.csv。
如下图所示,文件包含了租户名(User Name),AK(Access Key Id),SK(Secret Access Key)。
图4-3 credential.csv 文件内容
AK/SK生成步骤:
1. 注册并登录管理控制台。
2. 将鼠标移至用户名,在下拉列表中单击“我的凭证”。
3. 单击“访问密钥”。
4. 单击“新增访问密钥”,进入“新增访问密钥”页面。
5. 按照界面提示输入验证码或登录密码,单击“确定”,下载密钥,请妥善保管。
生成签名
生成签名的方式和APP认证相同,用AK代替APP认证中的AppKey,SK替换APP认证中 的AppSecret,即可完成签名和请求。您可使用Java、Go、Python、C#、
JavaScript、PHP、C++、C、Android进行签名和访问。
须知
客户端须注意本地时间与时钟服务器的同步,避免请求消息头X-Sdk-Date的值出现较 大误差。
API网关(即API管理)除了校验时间格式外,还会校验该时间值与网关收到请求的时 间差,如果时间差超过15分钟,API网关将拒绝请求。
5 创建用于前端自定义认证的函数
操作场景
自定义认证包括前端自定义认证与后端自定义认证,前端自定义认证指APIG利用校验 函数对收到的API请求进行安全认证,后端自定义认证指API后端服务利用校验函数,
对来自APIG转发的API请求进行安全认证。
如果您想要使用自己的认证系统对API的访问进行认证鉴权,您可以在API管理中创建 一个前端自定义认证来实现此功能。在使用前端自定义认证对前端请求进行认证鉴权 前,您需要先在FunctionGraph创建一个函数,通过函数定义您所需的认证信息。函数 创建完后,作为自定义认证的后端函数,对API网关中的API进行认证鉴权。
本章节介绍如何将校验函数封装成一个“自定义认证”,以及封装成自定义认证过程 中的操作注意事项。
图5-1 前端自定义认证示意图
使用自定义认证调用API的流程如下图所示:
图5-2 自定义认证调用 API
说明
自定义认证依赖函数服务。如果当前Region没有上线函数服务,则不支持使用自定义认证。
操作步骤
步骤1 在FunctionGraph中开发函数。
下面以python2.7语言为例,函数代码需要满足如下条件:
● 函数代码支持三种请求参数定义,格式为:
– Header中的请求参数:event["headers"]["参数名"]
– Query中的请求参数:event["queryStringParameters"]["参数名"]
– 您自定义的用户数据:event["user_data"]
● 函数代码获取的三种请求参数与API网关自定义认证中的参数关系如下所示:
– Header中的请求参数:对应自定义认证中参数位置为Header的身份来源,其 参数值在您调用使用该前端自定义认证的API时传入
– Query中的请求参数:对应自定义认证中参数位置为Query的身份来源,其参 数值在您调用使用该前端自定义认证的API时传入
– 您自定义的用户数据:对应自定义认证中的用户数据,其参数值在您创建自 定义认证时输入
● 函数的返回值不能大于1M,必须满足如下格式:
{ "statusCode":200,
"body": "{\"status\": \"allow\", \"context\": {\"user\": \"abc\"}}"
}
其中,body字段的内容为字符串格式,json解码之后为:
{ "status": "allow/deny",
def handler(event, context):
if event["headers"].get("test")=='abc':
resp = {
def handler(event, context):
if event["queryStringParameters"].get("test")=='abc':
resp = {
return json.dumps(resp)
用户数据定义代码示例:
# coding:utf-8 -*-import json
def handler(event, context):
if event.get("user_data")=='abc':
resp = {
return json.dumps(resp)
步骤2 测试函数。在测试事件的“事件模板”中选择“apig-event-template”,根据实际情 况修改后保存测试模板,单击“测试”。
执行结果为“成功”时,表示测试成功。
接下来您需要进入API网关界面创建前端自定义认证。
图5-4 测试函数
----结束
后续操作
在自定义认证中已经创建完成用于前端自定义认证的Function API,下一步您需要进入 API网关中创建前端自定义认证。
6 创建用于后端自定义认证的函数
操作场景
如果您需要使用一种认证机制对接多个不同的外部认证系统,实现对于后端服务的保 护,您可以通过API网关中的后端自定义认证实现此功能。在使用后端自定义认证对后 端请求进行认证授权前,您需要先在FunctionGraph创建一个函数,通过函数定义您所 需的认证信息。函数作为自定义认证的后端函数,对API网关中的API进行认证授权。
图6-1 后端自定义认证示意图
使用自定义认证调用API的流程如下图所示:
图6-2 使用自定义认证调用 API
说明
自定义认证依赖函数服务。如果当前Region没有上线函数服务,则不支持使用自定义认证。
操作步骤
步骤1 在FunctionGraph中开发函数。
下面以python2.7为例,函数代码需要满足如下条件:
● 函数代码只支持您自定义的用户数据,且它的格式为:event["user_data"]。
● 函数代码获取的请求参数与API网关自定义认证中的参数关系为:函数请求参数中 的自定义用户数据对应API网关自定义认证中的用户数据,参数值在您创建API网 关自定义认证时输入,用户数据格式不限制,您可以自行指定。
● 函数的返回值不能大于1M,必须满足如下格式:
{ "statusCode":200,
"body": "{\"status\": \"allow\", \"context\": {\"user\": \"abc\"}}"
}
其中,body字段的内容为字符串格式,json解码之后为:
“context”字段为可选,只支持字符串类型键值对,键值不支持JSON对象或数 组。
context中的数据为您自定义的字段,认证通过后作为认证参数映射到API网关后 端参数中,其中context中的参数名称与系统参数名称必须完全一致,且区分大小 写。context中的参数名称必须以英文字母开头,支持英文大小写字母、数字、下 划线和中划线,且长度为1 ~ 32个字符。
如图6-3所示,后端认证通过后,context中的user的值abc映射到后端服务Header 位置的test参数中,并将其传递给API的后端服务。
图6-3 认证参数映射到后端参数
用户数据定义代码示例:
# coding:utf-8 -*-import json
import base64
def handler(event, context):
token=base64.b64encode(event["user_data"]) resp = {
'statusCode': 200, 'body': json.dumps({
"status":"allow", "context":{
"token":token }
}) }
return json.dumps(resp)
步骤2 测试函数。在测试事件的“事件模板”中选择“空白模板”,内容为:
{"user_data": "123"}
根据实际情况修改后保存测试模板,单击“测试”。
执行结果为“成功”时,表示测试成功。
接下来您需要进入API网关界面创建后端自定义认证。
图6-4 测试函数
----结束
后续操作
在自定义认证中已经创建完成用于后端自定义认证的Function API,下一步您需要进入 API网关中创建后端自定义认证。
7 对后端服务进行签名
7.1 Java
操作场景
使用Java语言进行后端服务签名时,您需要先获取SDK,然后导入工程,最后参考校 验后端签名示例校验签名是否一致。
本章节以IntelliJ IDEA 2018.3.5版本为例介绍。
前提条件
● 准备待用的签名密钥的Key和Secret。
● 已在控制台创建签名密钥,并绑定API,具体请参见创建并使用签名密钥。
● 已下载后端签名示例代码。
● 获取并安装IntelliJ IDEA,如果未安装,请至IntelliJ IDEA官方网站下载。
● 已安装Java Development Kit 1.8.111或以上版本,如果未安装,请至Oracle官方 下载页面下载。
导入工程
步骤1 打开IntelliJ IDEA,在菜单栏选择“File > New > Project from Existing Sources”,选 择解压后的“apigateway-backend-signature-demo\pom.xml”文件,单击“OK”。
图7-1 Select File or Directory to Import
步骤2 保持默认设置,单击“Next > Next > Next > Next > Finish”,完成工程导入。
步骤3 在右侧Maven页签,双击“compile”进行编译。
图7-2 编译工程
步骤4 右键单击BackendSignatureApplication,选择“Run”运行服务。
图7-3 运行服务
“ApigatewaySignatureFilter.java”为示例代码,请根据实际情况修改参数后使用。
具体代码说明请参考校验后端签名示例。
----结束
校验后端签名示例
示例演示如何编写一个基于Spring boot的服务器,作为API的后端,并且实现一个 Filter,对API网关(即API管理)的请求做签名校验。
说明
API绑定签名密钥后,发给后端的请求中才会添加签名信息。
步骤1 编写一个Controller,匹配所有路径和方法,返回体为“Hello World!”。
// HelloController.java
@RestController
@EnableAutoConfiguration public class HelloController { @RequestMapping("/*") private String index() {
@WebFilter(filterName = "ApigatewaySignatureFilter", urlPatterns = "/*") public class ApigatewaySignatureFilter implements Filter {
private static Map<String, String> secrets = new HashMap<>();
static {
secrets.put("signature_key1", "signature_secret1");
secrets.put("signature_key2", "signature_secret2");
}
@Override
public void init(FilterConfig filterConfig) throws ServletException { }
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) { //签名校验代码
RequestWrapper request = new RequestWrapper((HttpServletRequest) servletRequest);
步骤4 使用正则表达式解析Authorization头,得到signingKey和signedHeaders。
private static final Pattern authorizationPattern = Pattern.compile("SDK-HMAC-SHA256\\s+Access=([^,]+),\
\s?SignedHeaders=([^,]+),\\s?Signature=(\\w+)");
...
}String signingKey = m.group(1);
String signingSecret = secrets.get(signingKey);
if (signingSecret == null) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Signing key not found.");
return;
}String[] signedHeaders = m.group(2).split(";");
例如,Authorization头为:
SDK-HMAC-SHA256 Access=signature_key1, SignedHeaders=host;x-sdk-date, Signature=e11adf65a20d1b82c25419b5********8d0ba12fed1ceb13ed00
则解析的结果为:
signingKey=signature_key1 signedHeaders=host;x-sdk-date
步骤5 通过signingKey找到signingSecret,如果不存在signingKey,则返回认证失败。
String signingSecret = secrets.get(signingKey);
if (signingSecret == null) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Signing key not found.");
return;
}
步骤6 新建一个Request对象,将请求method、url、query、signedHeaders对应的请求头放 入其中。判断是否需要设置body并设置。
需要读取body的条件为:不存在值为UNSIGNED-PAYLOAD的x-sdk-content-sha256 头。Request apiRequest = new DefaultRequest();
apiRequest.setHttpMethod(HttpMethodName.valueOf(request.getMethod()));
String url = request.getRequestURL().toString();
String queryString = request.getQueryString();
try {
apiRequest.setEndpoint((new URL(url)).toURI());
Map<String, String> parametersmap = new HashMap<>();
if (null != queryString && !"".equals(queryString)) { String[] parameterarray = queryString.split("&");
for (String p : parameterarray) {
parametersmap.put(URLDecoder.decode(key, "UTF-8"), URLDecoder.decode(value, "UTF-8"));
}
apiRequest.setParameters(parametersmap); //set query }
} catch (URISyntaxException e) { e.printStackTrace();
}
boolean needbody = true;
String dateHeader = null;
for (int i = 0; i < signedHeaders.length; i++) {
String headerValue = request.getHeader(signedHeaders[i]);
if (headerValue == null || headerValue.length() == 0) {
((HttpServletResponse) response).sendError(HttpServletResponse.SC_UNAUTHORIZED, "signed header" + signedHeaders[i] + " not found.");
} else {
apiRequest.addHeader(signedHeaders[i], headerValue);//set header if (signedHeaders[i].toLowerCase().equals("x-sdk-content-sha256") &&
headerValue.equals("UNSIGNED-PAYLOAD")) { needbody = false;
}
if (signedHeaders[i].toLowerCase().equals("x-sdk-date")) {
dateHeader = headerValue;
} } }
if (needbody) {
apiRequest.setContent(new ByteArrayInputStream(request.getBody())); //set body }
步骤7 校验签名是否过期。从X-Sdk-Date头中取出时间,判断与服务器时间是否相差在15分 钟以内。如果signedHeaders中不包含X-Sdk-Date,也返回认证失败。
private static final DateTimeFormatter timeFormatter =
DateTimeFormat.forPattern("yyyyMMdd'T'HHmmss'Z'").withZoneUTC();
...
if (dateHeader == null) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Header x-sdk-date not found.");
return;
}long date = timeFormatter.parseMillis(dateHeader);
long duration = Math.abs(DateTime.now().getMillis() - date);
if (duration > 15 * 60 * 1000) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Signature expired.");
return;
}
步骤8 将Authorization头也放入Request对象中,调用verify方法校验请求签名。如果校验通 过,则执行下一个filter,否则返回认证失败。
DefaultSigner signer = (DefaultSigner) SignerFactory.getSigner();
boolean verify = signer.verify(apiRequest, new BasicCredentials(signingKey, signingSecret));
if (verify) {
chain.doFilter(request, response);
} else {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "verify authroization failed.");
}
步骤9 运行服务器,验证代码正确性。下面示例使用JavaScript SDK中的html签名工具生成 签名。
填入如图所示字段后,单击“Send request”,复制生成的curl命令,并在命令行中执 行,服务器返回“Hello World!”。
如果使用错误的Key和Secret访问,服务器返回401认证不通过。
----结束
7.2 Python
操作场景
使用Python语言进行后端服务签名时,您需要先获取SDK,然后导入工程,最后参考 校验后端签名示例校验签名是否一致。
本章节以IntelliJ IDEA 2018.3.5版本为例介绍。
准备环境
● 准备待用的签名密钥的Key和Secret。
● 已在控制台创建签名密钥,并绑定API,具体请参见创建并使用签名密钥。
● 请登录API网关控制台,参考《用户指南》的“SDK”章节,进入SDK页面并下载 SDK。或直接下载SDK的最新版本。
● 获取并安装Python安装包(可使用2.7或3.X),如果未安装,请至Python官方下 载页面下载。
● 获取并安装IntelliJ IDEA,如果未安装,请至IntelliJ IDEA官方网站下载。
● 已在IntelliJ IDEA中安装Python插件,如果未安装,请按照图7-4所示安装。
图7-4 安装 Python 插件
导入工程
步骤1 打开IntelliJ IDEA,在菜单栏选择“File > New > Project”。
步骤1 打开IntelliJ IDEA,在菜单栏选择“File > New > Project”。