网关认证
新华智云OpenAPI是新华智云统一对外提供SaaS服务的统一API出口,服务包括但不限于:MAGIC,智能媒资,融媒平台,视频中台,AI中台、元卯、数字人等等。提供基础鉴权、流控控制、负载均衡等能力。
1. 接入准备
标题部分 1. 接入准备2. 鉴权流程
标题部分 2. 鉴权流程
3. 接入域名
标题部分 3. 接入域名4. 通用参数
标题部分 4. 通用参数query参数:
参数名 | 类型 | 说明 |
---|---|---|
access_key | String | 接入标识,需先申请 |
timestamp | String | 请求的时间戳 |
sign_type | String | 签名方式。取值范围:MD5。 |
sign_version | String | 签名算法版本。取值范围:2.0。 |
sign_nonce | String | 签名唯一随机数。用于防止网络重放攻击,建议您每一次请求都使用不同的随机数(在一定时间范围内部允许重复) |
signature | String | 以上参数进行加密计算后的签名,该字段非加签字段 |
5. 签名参考
标题部分 5. 签名参考Java
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;
import java.security.MessageDigest;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
/**
* 签名工具类
*/
public class OpenApiV1Demo {
/**
* 过期时间:5分钟
*/
private static final Long AUTH_TIME_EXPIRED = 5 * 60 * 1000L;
/**
* 检验
*
* @param accessKey ak(用户传递)
* @param secretKey sk(自己保存)
* @param timestamp 时间戳(用户传递)
* @param signature 签名串(用户传递)
* @param param 字典序的参数字符串(例如:access_key=***&status=test&state=bobo188×tamp=1571744445945)
* @return Boolean
* @throws Exception 鉴权错误信息
*/
public static Boolean check(String accessKey, String secretKey, String timestamp, String signature,String param)
throws Exception {
if (StringUtils.isBlank(timestamp)) {
System.err.println("鉴权参数不可为空, timestamp=" + timestamp);
throw new IllegalArgumentException();
}
long time = 0;
try {
time = Long.parseLong(timestamp);
} catch (NumberFormatException e) {
throw new RuntimeException("鉴权参数异常!");
}
if (System.currentTimeMillis() - time > AUTH_TIME_EXPIRED) {
throw new RuntimeException("鉴权时间过期!");
}
String sign = getSignature(accessKey, secretKey, timestamp,param);
return StringUtils.equals(sign, signature);
}
/**
* @param params
* @return
*/
public static String sortParamToString(Map<String, String> params) {
StringBuilder sb = new StringBuilder();
if (Objects.nonNull(params) && params.size() > 0) {
params.entrySet()
.stream()
.sorted(Map.Entry.comparingByKey())
.forEach(paramEntry -> {
sb.append(paramEntry.getKey()).append("=").append(paramEntry.getValue()).append("#");
});
}
String res = sb.toString();
return res;
}
/**
* 获取签名串
*
* @param accessKey ak
* @param secretKey sk
* @param timestamp 时间戳
* @param param 字典序的参数(例如:access_key=***&status=test&state=bobo188×tamp=1571744445945)
* @return 签名串
*/
public static String getSignature(String accessKey, String secretKey, String timestamp, String param) {
if (StringUtils.isAnyBlank(accessKey, secretKey,timestamp)) {
System.err.println("鉴权参数不可为空, accessKey=" + accessKey + ", secretKey=" + secretKey + ",timestamp=" + timestamp);
throw new IllegalArgumentException();
}
String needSignatureStr = secretKey + "$" + timestamp + "$" + accessKey + "$" + param;
try {
MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.update(needSignatureStr.getBytes("UTF-8"));
byte[] summery = md5.digest();
StringBuilder md5StrBuilder = new StringBuilder();
for (byte aSummery : summery) {
if (Integer.toHexString(255 & aSummery).length() == 1) {
md5StrBuilder.append("0").append(Integer.toHexString(255 & aSummery));
} else {
md5StrBuilder.append(Integer.toHexString(255 & aSummery));
}
}
return md5StrBuilder.toString();
} catch (Exception e) {
System.err.println("获取签名串失败" + e);
return "";
}
}
/**
*
* @param accessKey
* @param secretKey
* @param bizParam map类型的业务业务参数名
* @return 返回构造好的带有签名和参数的url
*/
public static String getSignUrl(String accessKey, String secretKey, Map<String, String> bizParam){
if(Objects.isNull(bizParam)||bizParam.size()==0){
bizParam = new HashMap<>();
}
if(Objects.isNull(bizParam.get("sign_version"))){
bizParam.put("sign_version","2.0");
}
if(Objects.isNull(bizParam.get("sign_nonce"))){
bizParam.put("sign_nonce", UUID.randomUUID().toString().replace("-",""));
}
if(Objects.isNull(bizParam.get("sign_type"))){
bizParam.put("sign_type","MD5");
}
String currentTime = String.valueOf(System.currentTimeMillis());
if(Objects.isNull(bizParam.get("timestamp"))){
bizParam.put("timestamp",currentTime);
}
//添加access_key
bizParam.put("access_key",accessKey);
//排序后的参数字符串
String sortParam = sortParamToString(bizParam);
//MD5加密环节
String sign = getSignature(accessKey,secretKey,currentTime,sortParam);
//构造url后续参数
String url = getUrlParamsByMap(bizParam);
String signUrl = url.concat("&signature=").concat(sign);
return signUrl;
}
/**
* 将map转换成url
*
* @param map
* @return
*/
public static String getUrlParamsByMap(Map<String, String> map) {
if (map == null) {
return "";
}
StringBuffer sb = new StringBuffer();
for (Map.Entry<String, String> entry : map.entrySet()) {
sb.append(entry.getKey() + "=" + entry.getValue());
sb.append("&");
}
String s = sb.toString();
if (s.endsWith("&")) {
s = StringUtils.substringBeforeLast(s, "&");
}
return s;
}
/**
* 获取url后拼接的参数,使用时在需要请求的url后拼接"?"和返回的参数
* 本段代码返回样式如:
* sign_version=2.0&access_key=accessKey&sign_nonce=08b02b5b0e8243528369e1befddfbcef&sign_type=MD5×tamp=1627456021388&signature=727faa633c944b3f756bef95d80df954
* 拼接后的请求url为:
* http://api.shuwen.com/xxxxx/xxx?sign_version=2.0&access_key=accessKey&sign_nonce=08b02b5b0e8243528369e1befddfbcef&sign_type=MD5×tamp=1627456021388&signature=727faa633c944b3f756bef95d80df954
*/
public static void main(String args[]) {
// todo 填充对应 aksk
String ak = "";
String sk = "";
String auth = getSignUrl(ak, sk, null);
System.out.println(auth);
}
}
Python
# coding=utf-8
import time
import hashlib
import uuid
class AuthUtil:
def __init__(self):
pass
@staticmethod
def get_auth_url(ak_str, sk_str, params_dict):
if not params_dict:
params_dict = {}
if 'sign_version' not in params_dict.keys():
params_dict['sign_version'] = '2.0'
if 'sign_nonce' not in params_dict.keys():
params_dict['sign_nonce'] = str(uuid.uuid4()).replace('-', '')
if 'sign_type' not in params_dict.keys():
params_dict['sign_type'] = 'MD5'
timestamp = (str(round(time.time() * 1000)))
if 'timestamp' not in params_dict.keys():
params_dict['timestamp'] = timestamp
params_dict['access_key'] = ak_str
sorted_params = AuthUtil.sort_params_str(params_dict)
signature = AuthUtil.get_signature(ak_str, sk_str, sorted_params, str(round(time.time() * 1000)))
url = AuthUtil.get_url_params(params_dict)
return url + '&signature=' + signature
@staticmethod
def get_url_params(params_dict):
if not params_dict:
return ''
url_params = ''
for key, value in params_dict.items():
url_params += key + '=' + value + '&'
if url_params.endswith('&'):
url_params = url_params[0:-1]
return url_params
@staticmethod
def sort_params_str(params_dict):
keys = sorted(params_dict.keys())
sorted_params_str = ''
for key in keys:
sorted_params_str += key + '=' + params_dict[key] + '#'
return sorted_params_str
@staticmethod
def get_signature(ak_str, sk_str, param_str, timestamp):
if not ak_str or not sk_str or not param_str:
raise Exception('参数缺失')
need_sign_str = sk_str + '$' + timestamp + '$' + ak_str + '$' + param_str
m = hashlib.md5()
m.update(need_sign_str.encode("utf8"))
return m.hexdigest()
6. 统一异步回调aksk验证
标题部分 6. 统一异步回调aksk验证智云提供服务中如涉及对外回调部分,为保障请求来源安全,建议在请求参数中验证签名是否正确
- 请求方式:POST
- 请求体内容: 业务自定义
- 请求参数:如下
字段 | 类型 | 说明 |
---|---|---|
timestamp | String | 时间戳, 接口调用时的时间戳 |
signature | String | 加签信息, 加签信息计算规则md5(sk + $ + timestamp + $ + ak) timestamp 就是上面的入参aksk 与向客户提供的 aksk 相同为保证接口请求安全, 客户需验证接收到的加签信息是否一致 |
如回调样例
http://demo.shuwen.com/digital/notify?timestamp=1679646235565&signature=xxxxx
7. 常见错误码
标题部分 7. 常见错误码失败code码:
错误码 | 说明 |
---|---|
SW-GW-1002 | 鉴权时间过期 |
SW-GW-1003 | 鉴权不通过 |
SW-GW-1004 | 鉴权参数检验不通过 |
SW-GW-1005 | 当前ak无权访问此接口,请联系管理员 |