1. 概述
1.1 版本
FineDataLink 版本 | 功能说明 |
---|---|
4.0.29 | - |
1.2 应用场景
对数据安全性要求高的用户,需要对发布的 API 进行更高安全性的鉴权方式设置,提供更安全的认证方式,满足更严格的安全要求。
1.3 功能说明
数据服务发布的 API 支持基于 AK/SK 认证逻辑的摘要签名认证方式,避免认证信息和请求信息在传输过程中被截获和篡改,提升认证安全性。
FineDataLink 发布的 API 采用 HMAC-SHA256 摘要算法计算签名,验签逻辑为:
请求发起方(计算签名,并在请求中带上签名) ---> 请求接收方(按请求参数计算签名,并校验签名)
2. 前提条件
3. 签名计算方法
3.1 JAVA 签名算法代码
3.1.1 POST-application/json请求
根据下列示例代码,创建JAVA 文件:
package kk;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Base64;
import java.util.UUID;
public class k {
public static void main(String[] args) {
//修改为对应的AppSecret
String secretKey = "1bbe91b1-a39c-4742-9694-e126bcf9a3bd";
//请求方式,注意大写
String httpMethod = "POST";
//POST请求需要写contentType,GET请求此处写空字符串
String contentType = "application/json";
//API路径。如果是get请求,需要带上参数,例如xxx/xxx?a=1
String pathAndParameters = "a5ce6bb4-467b-46f2-8878-2132635973bb/87";
//body请求体的内容
String data = "{\"paging\":{\"pageSize\":10,\"pageNum\":1},\"params\":[]}";
//Nonce,自动生成
String nonce = String.valueOf(UUID.randomUUID());
//时间戳,自动生成
String timestamp = String.valueOf(System.currentTimeMillis());
//待签名字符串
String stringToSign = httpMethod +"\n"+
nonce +"\n"+
timestamp +"\n"+
pathAndParameters +"\n"+
contentType +"\n"+
md5(data);
//签名
String signature = hmacSHA256(secretKey,stringToSign);
//拼出完整的Authorization
System.out.println("Authorization:\n"+"HMAC-SHA256 Signature="+signature+",Nonce="+nonce+",Timestamp="+timestamp);
}
/**
* 对字符串data进行HmacSHA256签名,以Base64的结果返回
*
* @param secretKey 签名密钥
* @param data 待签名字符串
*/
public static String hmacSHA256(String secretKey, String data) {
try {
Mac hmacSha256 = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
hmacSha256.init(secretKeySpec);
byte[] hashBytes = hmacSha256.doFinal(data.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(hashBytes);
} catch (Exception e) {
throw new RuntimeException("HmacSha error", e);
}
}
/**
* 对字符串data计算md5摘要值,再进行Base64编码,返回编码后的结果
*
* @param data 待计算的字符串
*/
public static String md5(String data) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
md.reset();
md.update(data.getBytes(StandardCharsets.UTF_8));
String md5Result = String.format("%032x", new BigInteger(1, md.digest()));
return Base64.getEncoder().encodeToString(md5Result.getBytes(StandardCharsets.UTF_8));
} catch (Exception e) {
throw new RuntimeException("md5 error", e);
}
}
}
3.1.2 GET请求
package kk;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Base64;
import java.util.UUID;
public class k {
public static void main(String[] args) {
//修改为对应的AppSecret
String secretKey = "a07eefc1-4b29-469a-8cb1-f68e3532d3a2";
//请求方式,注意大写
String httpMethod = "GET";
//POST请求需要写contentType,GET请求此处写空字符串
String contentType = "";
//API路径。如果是get请求,需要带上参数,例如xxx/xxx?a=1
String pathAndParameters = "a5ce6bb4-467b-46f2-8878-2132635973bb/dd?pageSize=10&pageNum=1";
//body请求体的内容
String data = "";
//Nonce,自动生成
String nonce = String.valueOf(UUID.randomUUID());
//时间戳,自动生成
String timestamp = String.valueOf(System.currentTimeMillis());
//待签名字符串
String stringToSign = httpMethod +"\n"+
nonce +"\n"+
timestamp +"\n"+
pathAndParameters +"\n"+
contentType +"\n"+
("".equals(data)?"":md5(data));
//签名
String signature = hmacSHA256(secretKey,stringToSign);
//拼出完整的Authorization
System.out.println("Authorization:\n"+"HMAC-SHA256 Signature="+signature+",Nonce="+nonce+",Timestamp="+timestamp);
}
/**
* 对字符串data进行HmacSHA256签名,以Base64的结果返回
*
* @param secretKey 签名密钥
* @param data 待签名字符串
*/
public static String hmacSHA256(String secretKey, String data) {
try {
Mac hmacSha256 = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
hmacSha256.init(secretKeySpec);
byte[] hashBytes = hmacSha256.doFinal(data.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(hashBytes);
} catch (Exception e) {
throw new RuntimeException("HmacSha error", e);
}
}
/**
* 对字符串data计算md5摘要值,再进行Base64编码,返回编码后的结果
*
* @param data 待计算的字符串
*/
public static String md5(String data) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
md.reset();
md.update(data.getBytes(StandardCharsets.UTF_8));
String md5Result = String.format("%032x", new BigInteger(1, md.digest()));
return Base64.getEncoder().encodeToString(md5Result.getBytes(StandardCharsets.UTF_8));
} catch (Exception e) {
throw new RuntimeException("md5 error", e);
}
}
}
3.1.3 POST-application/x-www-form-urlencoded请求
package kk;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Base64;
import java.util.UUID;
public class k {
public static void main(String[] args) {
//修改为对应的AppSecret
String secretKey = "1bbe91b1-a39c-4742-9694-e126bcf9a3bd";
//请求方式,注意大写
String httpMethod = "POST";
//POST请求需要写contentType,GET请求此处写空字符串
String contentType = "application/x-www-form-urlencoded";
//API路径。如果是get请求,需要带上参数,例如xxx/xxx?a=1
String pathAndParameters = "a5ce6bb4-467b-46f2-8878-2132635973bb/87";
//body请求体的内容 body里的键值对也要编码
String data = "a=1&b=%E6%8C%AA%E5%A8%81";
//Nonce,自动生成
String nonce = String.valueOf(UUID.randomUUID());
//时间戳,自动生成
String timestamp = String.valueOf(System.currentTimeMillis());
//待签名字符串
String stringToSign = httpMethod +"\n"+
nonce +"\n"+
timestamp +"\n"+
pathAndParameters +"\n"+
contentType +"\n"+
md5(data);
//签名
String signature = hmacSHA256(secretKey,stringToSign);
//拼出完整的Authorization
System.out.println("Authorization:\n"+"HMAC-SHA256 Signature="+signature+",Nonce="+nonce+",Timestamp="+timestamp);
}
/**
* 对字符串data进行HmacSHA256签名,以Base64的结果返回
*
* @param secretKey 签名密钥
* @param data 待签名字符串
*/
public static String hmacSHA256(String secretKey, String data) {
try {
Mac hmacSha256 = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
hmacSha256.init(secretKeySpec);
byte[] hashBytes = hmacSha256.doFinal(data.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(hashBytes);
} catch (Exception e) {
throw new RuntimeException("HmacSha error", e);
}
}
/**
* 对字符串data计算md5摘要值,再进行Base64编码,返回编码后的结果
*
* @param data 待计算的字符串
*/
public static String md5(String data) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
md.reset();
md.update(data.getBytes(StandardCharsets.UTF_8));
String md5Result = String.format("%032x", new BigInteger(1, md.digest()));
return Base64.getEncoder().encodeToString(md5Result.getBytes(StandardCharsets.UTF_8));
} catch (Exception e) {
throw new RuntimeException("md5 error", e);
}
}
}
3.2 签名算法原理
3.2.1 客户端生成并传入参数
客户端需要传入 Authorization 请求头和 Content-Type 头,使用 GET/POST 向服务端发起请求:
请求头 | 内容 |
---|---|
Authorization | 算法名称:Signature=Signature值, Nonce=Nonce随机数, Timestamp=Timestamp时间戳 举例: HMAC-SHA256 Signature=FiXSVh+oBzitw+HDypwlmB0PE+Z67pdXbQM7QyHA7qk=, Nonce=c967a237-cd6c-470e-906f-a8655461897, Timestamp=1686542039670 |
Content-Type | 根据请求的内容类型,填入Content-Type请求头内容。 如果是GET请求头不需要传入 Content-Type。 |
其中生成 Signature 值作为「待签名字符串」需要的项目如下,其中标注字母的内容需要用户根据自己的实际值进行替换:
字母 | 项目 | 说明 |
---|---|---|
A | AppSecret | 应用密钥 在数据服务>应用列表,选择摘要签名,即复制 AppSecret,如下图所示: |
B | HTTPMethod | 请求方法,目前支持传入GET和POST。注:需要大写。 |
Nonce | UUID,在生成签名的 Java 代码中自动生成。 验证时,服务端将会通过此 UUID 检查近 5 分钟内是否有重复请求,如果有重复请求,将进行拦截 | |
Timestamp | 13 位时间戳(1970-01-01 00:00:00开始到现在的毫秒数),在生成签名的 Java 代码中自动生成 验证时,服务端将会通过此时间戳,检查此请求是否已过期,如果请求已过期,将进行拦截 | |
D | PathAndParameters | API 配置页面配置的 API 路径 如果是 get 请求,需要带上参数,例如xxx/xxx?a=1 示例:http://192.168.5.175:8089/webroot/service/publish/<appid>/api01?a=1&b=2中的加粗部分,不包含首尾的斜杠 在 API 列表下的授权应用中复制 API 路径,如下图所示: |
C | Content-Type | BODY 的 MIME 类型 POST 请求需要写 contentType,GET 请求此处写空字符串 |
E | String data | POST 请求时,在 BODY 中的参数字符串
例如 JSON 格式的 Body,示例为: {"paging":{"pageSize":10,"pageNum":1},"params":[]} 对于以上请求体内容,先做 MD5 处理(输出32位小写字符串),结果再进行 BASE64 编码,得到最终的 Content-MD5 结果。 |
「待签名字符串」构建方式:
待签名字符串 = 「HTTPMethod」+ "\n" +「Nonce」+ "\n" +「Timestamp」+ "\n" +「PathAndParameters」+ "\n" +「Content-Type」+ "\n" +「Content-MD5」 注1:加号代表拼接,实际是每个项目之间通过换行分隔,如果没有对应项目,则对应内容用空字符串代替,但是换行需要保存 注2:如果没有 BODY,那么 Content-MD5 项目直接为空字符串,不对空 BODY 做 MD5(也就是说,目前对于 GET 请求,Content-Type 和 Content-MD5 项目都会为空字符串) |
待签名字符串生成 Signature 签名,使用 HMAC-SHA256 加密算法。
Mac hmacSha256 = Mac.getInstance("HmacSHA256");
byte[] appSecretBytes = appSecret.getBytes(Charset.forName("UTF-8"));
hmacSha256.init(new SecretKeySpec(appSecretBytes, 0, appSecretBytes.length, "HmacSHA256"));
byte[] md5Result = hmacSha256.doFinal(stringToSign.getBytes(Charset.forName("UTF-8")));
String signature = Base64.encodeBase64String(md5Result);
3.2.2 服务端接受请求
计算签名方式同客户端
校验项目:
验证项目 | 通过条件 |
---|---|
Timestamp | 与服务端当前时间相比,时间差在5min以内 |
Nonce | 服务端在5min内,没有重复的Nonce |
Signature | 服务端计算的 Signature 与请求头中的 Signature 完全一致 |
4. 签名计算示例(以java为例)
4.1 客户端生成签名
参考本文 3.1 节修改代码中的指定内容:
例如用户已经在 FineDataLink 中发布请求为 POST application/json 的 API ,则需要准备以下数据,以备生成待签名字符串:
项目 | 说明 | JAVA文件中需要修改的内容 |
---|---|---|
AppSecret | 应用密钥 在数据服务>应用列表,选择摘要签名,即复制 AppSecret,如下图所示: | |
HTTPMethod | 请求方法,目前支持传入 GET 和 POST注:需要大写 | |
Nonce | UUID,在生成签名的 Java 代码中自动生成 验证时,服务端将会通过此 UUID 检查近 5 分钟内是否有重复请求,如果有重复请求,将进行拦截 | |
Timestamp | 13 位时间戳(1970-01-01 00:00:00开始到现在的毫秒数),在生成签名的 Java 代码中自动生成 验证时,服务端将会通过此时间戳,检查此请求是否已过期,如果请求已过期,将进行拦截 | |
PathAndParameters | API 配置页面配置的 API 路径 如果是 get 请求,需要带上参数,例如xxx/xxx?a=1 示例:http://192.168.5.175:8089/webroot/service/publish/<appid>/api01?a=1&b=2中的加粗部分,不包含首尾的斜杠 在 API 列表下的授权应用中复制 API 路径,如下图所示: | |
Content-Type | BODY 的 MIME 类型。 POST 请求需要写 contentType,GET 请求此处写空字符串 | |
Content-MD5 | POST请求时,在B ODY 中的参数字符串。
例如 JSON 格式的 Body,示例为: {"paging":{"pageSize":10,"pageNum":1},"params":[]} 对于 json 格式的 Body,可在 API 生成界面复制 Body 值 对于以上请求体内容,先做 MD5 处理(输出32位小写字符串),结果再进行 BASE64 编码,得到最终的 Content-MD5 结果 |
然后运行 JAVA 文件,即可获得 Authorization ,如下图所示:
4.2 服务端获取请求
此处以 POST JSON 请求为例,输入 Authorization 认证和 Body 请求值,即可取出发布的 API 数据,如下图所示: