2.4.1 Java SDK 使用说明
操作场景
使用Java语言进行后端服务签名时,您需要先获取SDK,然后导入工程,最后参考校 验后端签名示例校验签名是否一致。
本章节以IntelliJ IDEA 2018.3.5版本为例介绍。
说明
Java SDK仅支持hmac和basic类型的后端服务签名。
前提条件
● 准备待用的签名密钥的Key和Secret。
● 已在控制台创建签名密钥,并绑定API,具体请参见配置后端服务的签名校验。
● 已获取后端签名示例代码,您可以在ROMA Connect实例控制台的“服务集成 APIC > API管理”页面中,选择“签名密钥”页签,单击“下载SDK”下载签名示 例代码。
● 获取并安装2018.3.5或以上版本的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”。
图2-43 Select File or Directory to Import
步骤2 保持默认设置,单击“Next > Next > Next > Next > Finish”,完成工程导入。
步骤3 在右侧Maven页签,双击“compile”进行编译。
图2-44 编译工程
返回“BUILD SUCCESS”,表示编译成功。
步骤4 右键单击BackendSignatureApplication,选择“Run”运行服务。
图2-45 运行服务
“ApigatewaySignatureFilter.java”为示例代码,请根据实际情况修改参数后使用。
具体代码说明请参考校验hmac类型后端签名示例。
----结束
校验 hmac 类型后端签名示例
说明
● 示例演示如何编写一个基于Spring boot的服务器,作为API的后端,并且实现一个Filter,对 APIC的请求做签名校验。
● API绑定hmac类型签名密钥后,发给后端的请求中会添加签名信息。
步骤1 编写一个Controller,路径为/hmac。
// HelloController.java
@RestController
@EnableAutoConfiguration public class HelloController { @RequestMapping("/hmac") private String hmac() {
return "Hmac authorization success";
} }
步骤2 编写一个Filter,匹配所有路径和方法。将允许的签名key和secret对放入一个Map中。
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) { //签名校验代码
...
} }
步骤3 doFilter函数为签名校验代码。校验流程如下:由于filter中需要读取body,为了使得 body可以在后续的filter和controller中再次读取,把request包装起来传给后续的filter 和controller。包装类的具体实现可见RequestWrapper.java。
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 authorization = request.getHeader("Authorization");
if (authorization == null || authorization.length() == 0) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Authorization not found.");
return;
}
Matcher m = authorizationPattern.matcher(authorization);
if (!m.find()) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Authorization format incorrect.");
return;
}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 注册filter和路径的映射关系。
@Configuration
public class FilterConfig { @Bean
public FilterRegistrationBean registApigatewaySignatureFilter() { FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new ApigatewaySignatureFilter());
registration.addUrlPatterns("/hmac");
registration.setName("ApigatewaySignatureFilter");
return registration;
} }
步骤10 运行服务器,验证代码正确性。下面示例使用JavaScript SDK中的html签名工具生成 签名。
填入如图所示字段后,单击“Send request”,复制生成的curl命令,并在命令行中执 行,服务器返回“Hello World!”。
如果使用错误的Key和Secret访问,服务器返回401认证不通过。
----结束
校验 basic 类型后端签名示例
说明
● 示例演示如何编写一个基于Spring boot的服务器,作为API的后端,并且实现一个Filter,对 APIC的请求做签名校验。
● API绑定basic类型签名密钥后,发给后端的请求中会添加basic认证信息,其中basic认证的用 户名为签名秘钥的key,密码为签名秘钥的secret。
步骤1 编写一个Controller,路径为/basic。
// HelloController.java
@RestController
@EnableAutoConfiguration public class HelloController { @RequestMapping("/basic") private String basic() {
return "Basic authorization success";
} }
步骤2 编写一个Filter,按照basic认证的规则,Authorization头格式为"Basic
"+base64encode(username+":"+password)。以下为根据规则编写的校验代码。
// BasicAuthFilter.java
public class BasicAuthFilter implements Filter {
private static final String CREDENTIALS_PREFIX = "Basic ";
private static Map<String, String> secrets = new HashMap<>();
static {
secrets.put("signature_key1", "signature_secret1");
secrets.put("signature_key2", "signature_secret2");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) { HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
try {
String credentials = request.getHeader("Authorization");
if (credentials == null || credentials.length() == 0) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Authorization not found.");
return;
}
if (!credentials.startsWith(CREDENTIALS_PREFIX)) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Authorization format incorrect.");
return;
}
String authInfo = credentials.substring(CREDENTIALS_PREFIX.length());
String decoded;
try {
decoded = new String(Base64.getDecoder().decode(authInfo));
} catch (IllegalArgumentException e) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Authorization format incorrect.");
return;
}
String[] spl = decoded.split(":", 2);
if (spl.length < 2) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Authorization format incorrect.");
return;
}
String signingSecret = secrets.get(spl[0]);
if (signingSecret == null) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Username not found.");
return;
}
if (signingSecret.equals(spl[1])) { chain.doFilter(request, response);
} else {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Incorrect username or password");
}
} catch (Exception e) { e.printStackTrace();
try {
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
} catch (IOException e1) { }
} } }
步骤3 注册filter和路径的映射关系。
@Configuration
public class FilterConfig { @Bean
public FilterRegistrationBean registBasicAuthFilter() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new BasicAuthFilter());
registration.addUrlPatterns("/basic");
registration.setName("BasicAuthFilter");
return registration;
} }
步骤4 运行服务器,验证代码正确性。将用户名和密码生成basic认证的Authorization头域传 给请求接口。如果使用错误的用户名和密码访问,服务器返回401认证不通过。
----结束
2.4.2 Python SDK 使用说明
操作场景
使用Python语言进行后端服务签名时,您需要先获取SDK,然后导入工程,最后参考 校验后端签名示例校验签名是否一致。
本章节以IntelliJ IDEA 2018.3.5版本为例介绍。
说明
Python SDK仅支持hmac类型的后端服务签名。
准备环境
● 准备待用的签名密钥的Key和Secret。
● 已在控制台创建签名密钥,并绑定API,具体请参见配置后端服务的签名校验。
● 已获取后端签名SDK,您可以在ROMA Connect实例控制台的“服务集成 APIC >
API调用”页面中下载Python SDK。
● 获取并安装2.7或3.X版本的Python安装包,如果未安装,请至Python官方下载页 面下载。
● 获取并安装2018.3.5或以上版本的IntelliJ IDEA,如果未安装,请至IntelliJ IDEA 官方网站下载。
● 已在IntelliJ IDEA中安装Python插件,如果未安装,请按照图2-46所示安装。
图2-46 安装 Python 插件
导入工程
步骤1 打开IntelliJ IDEA,在菜单栏选择“File > New > Project”。
弹出“New Project”对话框,选择“Python”,单击“Next”。
图2-47 New Project
步骤2 再次单击“Next”,弹出以下对话框。单击“...”,在弹出的对话框中选择解压后的 SDK路径,单击“Finish”。
图2-48 选择解压后的 SDK 路径
步骤3 完成工程创建后,目录结构如下。
图2-49 目录结构
步骤4 单击“Edit Configurations”,弹出“Run/Debug Configurations”对话框。
图2-50 Edit Configurations
步骤5 单击“+”,选择“Flask server”。
图2-51 选择 Flask server
步骤6 “Taget type”选择“Script path”,“Target”选择工程下的
“backend_signature.py”文件,单击“OK”,完成工程配置。
----结束
校验后端签名示例
说明
● 示例演示如何编写一个基于Flask的服务器,作为API的后端,并且实现一个wrapper,对 APIC的请求做签名校验。
● API绑定签名密钥后,发给后端的请求中才会添加签名信息。
步骤1 编写一个返回“Hello World!”的接口,方法为GET、POST、PUT和DELETE,且使用 requires_apigateway_signature的wrapper。
app = Flask(__name__)
@app.route("/<id>", methods=['GET', 'POST', 'PUT', 'DELETE'])
@requires_apigateway_signature() def hello(id):
return "Hello World!"
步骤2 实现requires_apigateway_signature。将允许的签名key和secret对放入一个dict中。
def requires_apigateway_signature():
def wrapper(f):
r'SDK-HMAC-SHA256\s+Access=([^,]+),\s?SignedHeaders=([^,]+),\s?Signature=(\w+)') BasicDateFormat = "%Y%m%dT%H%M%SZ"
步骤3 wrapped函数为签名校验代码。校验流程如下:使用正则表达式解析Authorization 头。得到key和signedHeaders。
if "authorization" not in request.headers:
return 'Authorization not found.', 401 authorization = request.headers['authorization']
m = authorizationPattern.match(authorization) if m is None:
return 'Authorization format incorrect.', 401 signingKey = m.group(1)
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
步骤4 通过key找到secret,如果不存在key,则返回认证失败。
if signingKey not in secrets:
return 'Signing key not found.', 401 signingSecret = secrets[signingKey]
步骤5 新建一个HttpRequest对象,将请求method、url、query、signedHeaders对应的请求 头放入其中。判断是否需要设置body并设置。
需要读取body的条件为:不存在值为UNSIGNED-PAYLOAD的x-sdk-content-sha256 头。
r = signer.HttpRequest() r.method = request.method r.uri = request.path r.query = {}
for k in request.query_string.decode('utf-8').split('&'):
spl = k.split("=", 1)
needbody = True dateHeader = None for k in signedHeaders:
if k not in request.headers:
return 'Signed header ' + k + ' not found', 401 v = request.headers[k]
if k.lower() == 'x-sdk-content-sha256' and v == 'UNSIGNED-PAYLOAD':
needbody = False if k.lower() == 'x-sdk-date':
dateHeader = v r.headers[k] = v if needbody:
r.body = request.get_data()
步骤6 校验签名是否过期。从X-Sdk-Date头中取出时间,判断与服务器时间是否相差在15分 钟以内。如果signedHeaders中不包含X-Sdk-Date,也返回认证失败。
if dateHeader is None:
return 'Header x-sdk-date not found.', 401 t = datetime.strptime(dateHeader, BasicDateFormat) if abs(t - datetime.utcnow()) > timedelta(minutes=15):
return 'Signature expired.', 401
步骤7 调用verify方法校验请求签名。判断校验是否通过。
sig = signer.Signer() sig.Key = signingKey sig.Secret = signingSecret if not sig.Verify(r, m.group(3)):
return 'Verify authroization failed.', 401
步骤8 运行服务器,验证代码正确性。下面示例使用JavaScript SDK中的html签名工具生成 签名。
填入如图所示字段后,单击“Send request”,复制生成的curl命令,并在命令行中执 行,服务器返回200。
如果使用错误的Key和Secret访问,服务器返回401认证不通过。
----结束
2.4.3 C# SDK 使用说明
操作场景
使用C#语言进行后端服务签名时,您需要先获取SDK,然后打开工程,最后参考校验 后端签名示例校验签名是否一致。
说明
C# SDK仅支持hmac类型的后端服务签名。
准备环境
● 准备待用的签名密钥的Key和Secret。
● 已在控制台创建签名密钥,并绑定API,具体请参见配置后端服务的签名校验。
● 已获取后端签名SDK,您可以在ROMA Connect实例控制台的“服务集成 APIC >
API调用”页面中下载C# SDK。
● 获取并安装2019 version 16.8.4及以上版本的Visual Studio,如果未安装,请至
Visual Studio官方网站下载。
打开工程
双击SDK包中的“csharp.sln”文件,打开工程。工程中包含如下3个项目:
● apigateway-signature:实现签名算法的共享库,可用于.Net Framework与.Net Core项目。
● backend-signature:后端服务签名示例,请根据实际情况修改参数后使用。具体 代码说明请参考校验后端签名示例。
● sdk-request:签名算法的调用示例。
校验后端签名示例
说明
● 示例演示如何编写一个基于ASP.Net Core的服务器,作为API的后端,并且实现一个 IAuthorizationFilter,对APIC的请求做签名校验。
● API绑定签名密钥后,发给后端的请求中才会添加签名信息。
步骤1 编写一个Controller,提供GET、POST、PUT和DELETE四个接口,且加入 ApigatewaySignatureFilter的Attribute。
// ValuesController.cs
namespace backend_signature.Controllers { [Route("api/[controller]")]
[ApiController]
[ApigatewaySignatureFilter]
public class ValuesController : ControllerBase {
// GET api/values [HttpGet]
public ActionResult<IEnumerable<string>> Get() {
步骤2 实现一个ApigatewaySignatureFilter。将允许的签名key和secret对放入一个 Dictionary中。
// ApigatewaySignatureFilter.cs namespace backend_signature.Filters
{ public class ApigatewaySignatureFilter : Attribute, IAuthorizationFilter
{
private Dictionary<string, string> secrets = new Dictionary<string, string>
{
{"signature_key1", "signature_secret1" }, {"signature_key2", "signature_secret2" }, };
public void OnAuthorization(AuthorizationFilterContext context) { //签名校验代码
...
} } }
步骤3 OnAuthorization函数为签名校验代码。校验流程如下:使用正则表达式解析 Authorization头。得到key和signedHeaders。
private Regex authorizationPattern = new Regex("SDK-HMAC-SHA256\\s+Access=([^,]+),\\s?
SignedHeaders=([^,]+),\\s?Signature=(\\w+)");
...
string authorization = request.Headers["Authorization"];
if (authorization == null)
{ context.Result = new UnauthorizedResult();
return;
}var matches = authorizationPattern.Matches(authorization);
if (matches.Count == 0)
{ context.Result = new UnauthorizedResult();
return;
}var groups = matches[0].Groups;
string key = groups[1].Value;
string[] signedHeaders = groups[2].Value.Split(';');
例如,Authorization头为:
SDK-HMAC-SHA256 Access=signature_key1, SignedHeaders=host;x-sdk-date,
SDK-HMAC-SHA256 Access=signature_key1, SignedHeaders=host;x-sdk-date,