1. 接口鉴权

1.1. 鉴权方式

OpenAPI请求采用接口签名方式,需要在HTTP HEADER中加入以下请求头:

序号 header名 描述
1 ctimestamp 客户端本地unix毫秒时间戳
2 cnonce 随机字符串,可含字母,客户端自行生成
3 cappkey 标识第三方系统身份,由鸿图分配
4 csign 按签名规则计算的签名
5 zoneUuid 区域uuid(选填)

1.2. 签名计算规则

签名算法

  • 签名算法:MD5,32位小写
  • sign计算规则:csign=MD5(uri-method-queryParamStr-requestBodyMD5-secretKey-ctimestamp-cnonce-cappkey)
    • 数据解释:
      • uri:必须,请求URI,以"/"开头,如:"/v1/api/person/list"
      • method:必须,请求方式,全大写,如: "POST"
      • queryParamStr:非必须,query域传值,有值传形如"name=张三&age=28&..."字符串;无值传""
      • requestBodyMD5:非必须,仅文件上传接口可为空;否则为JSON数据或"{}"的MD5加密串(为空会报JSON解析异常)
      • secretKey:必须,鸿图平台提供的签名私钥
      • ctimestamp:必须,当前时间戳
      • cnonce:必须,随机码
      • cappkey:必须,鸿图平台提供的签名公钥
      • csign:生成的最终MD5签名串
    • 示例:
      • uri="/v1/api/person/list"
      • method="POST"
      • queryParamStr=""
      • requestBodyMD5="dcb8b5bfe22ef26821b9b34e650c534b"
      • secretKey="sdfajk3242324fa!djq7"
      • ctimestamp="1627300328673"
      • cnonce="1234344"
      • cappkey="appkey1"

1.3. 签名示例

以人员列表查询为例,通过在线Demo计算签名,Demo地址

sign

请求参数如下:

  • uri="/v1/api/person/list"
  • method="POST"
  • requestBody(请求体):
{"pageNum":1,"pageSize":10}

则requestBodyMD5为:675c299bcd88edf31c09c48f9c7fba4b

  • secretKey="sdfajk3242324fa!djq7"
  • ctimestamp="1627300328673"
  • cnonce="1234344"
  • cappkey="appkey1"
  • 则csign=MD5("/v1/api/person/list-POST--675c299bcd88edf31c09c48f9c7fba4b-sdfajk3242324fa!djq7-1627300328673-1234344-appkey1"),结果为:52141def7a1e346ceee2f22c43d5fb97

那么,我们可以如下请求: postmain.png

1.4. 可用签名及秘钥

目前,鸿图里面有两组cappkey和秘钥,如下:

cappkey secret
appkey1 sdfajk3242324fa!djq7
appkey2 zxcv&jlkfd234adf#xi9

1.5. 签名SDK

1.5.1. Java版本

package com.megvii.sign.sdk;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;
/**
描述:
Author:liuxing
Date:2020-09-11
*/
public class SignUtlis {
    private static final String SIGN_FORMAT = "%s-%s-%s-%s-%s-%s-%s-%s";
    /**
签名算法生成卡密昂
@param ctimestamp
@param cnonce
@param requestBody  请求体对象
@param requestParam
@param url
@param method
@param cappKey
@param secret
@return
*/
    public static String sign(String ctimestamp, String cnonce, Object requestBody, String requestParam, String url, String method, String cappKey, String secret) {
        String requestBodyMd5 = "";
        if (requestBody != null) {
            String requestBodyJson = JSONObject.toJSONString(requestBody);
            if (StringUtils.isNotEmpty(requestBodyJson)) {
                requestBodyMd5 = DigestUtil.encryptMd5(requestBodyJson);
            }
        }
        String signStr = String.format(SIGN_FORMAT, url, method, requestParam, requestBodyMd5, secret, ctimestamp, cnonce, cappKey);
        String sign = DigestUtil.encryptMd5(signStr);
        return sign;
    }
}
package com.megvii.sign.sdk;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESedeKeySpec;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class DigestUtil {
    /** 算法名称 */
    private static final String ALGORITHM = "DESede";
    /** 16进制字母 */
    private static final char[] HEX_DIGITS={'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
    /**
     * @param decript 要加密的字符串
     * @return 加密的字符串
     * SHA1加密
     */
    public final static String SHA1(String decript) {
        try {
            MessageDigest digest = java.security.MessageDigest
                                .getInstance("SHA-1");
            digest.update(decript.getBytes());
            byte messageDigest[] = digest.digest();
            // Create Hex String
            StringBuffer hexString = new StringBuffer();
            // 字节数组转换为 十六进制 数
            for (int i = 0; i < messageDigest.length; i++) {
                String shaHex = Integer.toHexString(messageDigest[i] & 0xFF);
                if (shaHex.length() < 2) {
                    hexString.append(0);
                }
                hexString.append(shaHex);
            }
            return hexString.toString();
        }
        catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return "";
    }
    /**
     * 3DES加密,key必须是长度大于等于 3*8 = 24 位
     * @param src
     * @param key
     * @return
     * @throws Exception
     */
    public static String encrypt3DES(String src, String key) {
        byte[] keyBytes = key.getBytes();
        try {
            DESedeKeySpec dks = new DESedeKeySpec(keyBytes);
            SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(ALGORITHM);
            SecretKey securekey = keyFactory.generateSecret(dks);
            Cipher cipher = Cipher.getInstance("DESede/ECB/PKCS5Padding");
            cipher.init(Cipher.ENCRYPT_MODE, securekey);
            byte[] b=cipher.doFinal(src.getBytes());
            return StringUtil.byte2hex(b);
        }
        catch (Exception e) {
            return null;
        }
    }
    /**
     * 3DESECB解密,key必须是长度大于等于 3*8 = 24 位
     * @param src
     * @param key
     * @return
     * @throws Exception
     */
    public static String decrypt3DES(String src, String key) {
        byte[] keyBytes = key.getBytes();
        try {
            //--通过base64,将字符串转成byte数组
            byte[] bytesrc = StringUtil.hex2byte(src);
            //--解密的key
            DESedeKeySpec dks = new DESedeKeySpec(keyBytes);
            SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(ALGORITHM);
            SecretKey securekey = keyFactory.generateSecret(dks);
            //--Chipher对象解密
            Cipher cipher = Cipher.getInstance("DESede/ECB/PKCS5Padding");
            cipher.init(Cipher.DECRYPT_MODE, securekey);
            byte[] retbyte = cipher.doFinal(bytesrc);
            return new String(retbyte);
        }
        catch (Exception e) {
            return null;
        }
    }
    /**
     *
     * Description:md5加密
     * @Create_by:JH
     * @Create_date:2014-9-9
     * @Last_Edit_By:
     * @Edit_Description
     * @Create_Version:exinhua 1.0
     */
    public static String encryptMd5(String src) {
        if(StringUtil.isEmpty(src)) {
            return src;
        }
        try {
            byte[] btInput = src.getBytes();
            // 获得MD5摘要算法的 MessageDigest 对象
            MessageDigest mdInst = MessageDigest.getInstance("MD5");
            // 使用指定的字节更新摘要
            mdInst.update(btInput);
            // 获得密文
            byte[] md = mdInst.digest();
            // 把密文转换成十六进制的字符串形式
            return byteToHexString(md);
        }
        catch (Exception e) {
            throw new RuntimeException("error occurated when encrypt", e);
        }
    }
    /**
     * 对输入进行sha1加密后,在进行16进制转换
     * @param value
     * @return
     */
    public static String hexSHA1(String value) {
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-1");
            md.update(value.getBytes("utf-8"));
            byte[] digest = md.digest();
            return byteToHexString(digest);
        }
        catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }
    /**
     * 字节数组转换成16进制表示
     * @param bytes
     * @return
     */
    public static String byteToHexString(byte[] bytes) {
        int j = bytes.length;
        char[] str = new char[j * 2];
        int k = 0;
        for (int i = 0; i < j; i++) {
            byte byte0 = bytes[i];
            str[k++] = HEX_DIGITS[byte0 >>> 4 & 0xf];
            str[k++] = HEX_DIGITS[byte0 & 0xf];
        }
        return new String(str);
    }
}
package com.megvii.sign.sdk;
/**
 * 字符串工具类
 *
 */
public class StringUtil {
    public static final String[] HEX_ARRAYS = { "0", "1", "2", "3", "4", "5",
                "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };
    /**
     *  判断value是否是null或者其length为0
     *
     * @param value
     * @return
     */
    public static Boolean isEmpty(String value) {
        return value == null || value.length() == 0;
    }
    /**
     * 判断value是否不是null且其length>0
     *
     * @param value
     * @return
     */
    public static Boolean isNotEmpty(String value) {
        return !isEmpty(value);
    }
    /**
     * trim操作
     *
     * @param value
     * @return
     */
    public static String trim(String value) {
        if (value == null) {
            return null;
        }
        return value.trim();
    }
    /**
     * byte string to hex string
     *
     * @param b
     * @return
     */
    public static String byte2hex(byte[] b) {
        if (b == null) {
            return null;
        }
        if (b.length == 0) {
            return "";
        }
        StringBuilder retBuilder = new StringBuilder();
        for (int n = 0; n < b.length; ++n) {
            retBuilder.append(HEX_ARRAYS[(b[n] & 0xF0) >> 4]);
            retBuilder.append(HEX_ARRAYS[b[n] & 0x0F]);
        }
        return retBuilder.toString();
    }
    /**
     * hex string to byte string
     *
     * @param str
     * @return
     */
    public static byte[] hex2byte(String str) {
        if (str == null)
                    return null;
        str = str.trim();
        int len = str.length();
        if ((len == 0) || (len % 2 == 1))
                    return null;
        byte[] b = new byte[len / 2];
        byte tmp = 0;
        char[] strs = str.toCharArray();
        for (int i = 0; i < len; i++) {
            byte t = 0;
            if (strs[i] >= '0' && strs[i] <= '9') {
                t = (byte) (strs[i] - '0');
            } else if (strs[i] >= 'A' && strs[i] <= 'F') {
                t = (byte) (strs[i] - 'A' + 10);
            } else if (strs[i] >= 'a' && strs[i] <= 'f') {
                t = (byte) (strs[i] - 'a' + 10);
            }
            if ((i & 0x1) == 1) {
                tmp <<= 4;
                tmp += t;
                b[i / 2] = tmp;
                tmp = 0;
            } else {
                tmp = t;
            }
        }
        return b;
    }
    /**
     * 判断一个字符串是否只包含数字(10进制)字符
     *
     * @param str
     * @return
     */
    public static Boolean isNumeric(String str) {
        if (isEmpty(str)) {
            return false;
        }
        char[] arr = str.toCharArray();
        for (char c : arr) {
            if (!Character.isDigit(c)) {
                return false;
            }
        }
        return true;
    }
    /**
     * 混淆字串串(显示前pn个字符和后tn个字符,其余全部用*填充)
     *
     * @param source
     * @param pn
     * @param tn
     * @return
     */
    public static String mix(String source, int pn, int tn) {
        return mix(source, pn, tn, '*');
    }
    /**
     * 混淆字串串(显示前pn个字符和后tn个字符,其余前部用mixChar填充)
     *
     * @param source
     * @param pn
     * @param tn
     * @param mixChar
     * @return
     */
    public static String mix(String source, int pn, int tn, char mixchar) {
        if (source == null || source.length() <= pn + tn) {
            return source;
        }
        int len = source.length();
        StringBuilder tmp = new StringBuilder(source.length());
        char[] mobileAs = source.toCharArray();
        for (int i = 0; i < pn; i++) {
            tmp.append(mobileAs[i]);
        }
        for (int i = 0; i < len - (pn + tn); i++) {
            tmp.append(mixchar);
        }
        for (int i = len - tn; i < len; i++) {
            tmp.append(mobileAs[i]);
        }
        return tmp.toString();
    }
    /**
     * 获取字符串长度(全角2,半角1)
     * @param value
     * @return
     */
    public static final int getLength(String value) {
        if(StringUtil.isEmpty(value)) {
            return 0;
        }
        int len = 0;
        for (char c : value.toCharArray()) {
            len++;
            if(isSbcCase(c)) {
                len++;
            }
        }
        return len;
    }
    /**
     * 判断字符是否是半角
     * @param c
     * @return
     */
    public static final Boolean isDbcCase(char c) {
        int k = 0x80;
        return c / k == 0 ? true : false;
    }
    /**
     * 判断是否时全角
     * @param c
     * @return
     */
    public static final Boolean isSbcCase(char c) {
        return isDbcCase(c);
    }
    /**
     * 判断是否都是数字
     * @param value
     * @return
     */
    public static final Boolean isAllDigits(String value) {
        if(StringUtil.isEmpty(value)) {
            return true;
        }
        for (char c : value.toCharArray()) {
            if(c < '0' ||  c > '9') {
                return false;
            }
        }
        return true;
    }
    /**
     * 在指定的字符串前面填充0,直到字段串的长度达到指定的长度
     * 示例:输入6, 2 ---> 返回06
     * @param data 需要被填充的字符串
     * @param fillStr 需要填充得字符串
     * @param length 目标字符串的长度
     * @return 返回填充后的字符串
     */
    public static String frontFillStr(String data, String fillStr, int length) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < length-data.length(); i++) {
            sb.append(fillStr);
        }
        return sb.append(data).toString();
    }
}

请求示例

package test;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
 * 描述:
 * Author:liuxing
 * Date:2020-09-14
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class DeviceReq {
    private String name;
    private Integer pageNum;
    private Integer deviceType;
    private Integer pageSize;
}
package test;
import com.alibaba.fastjson.JSONObject;
import com.megvii.sign.sdk.SignUtlis;
import okhttp3.*;
import java.util.concurrent.TimeUnit;
/**
 * 描述: 模拟一次请求的过程
 * Author:liuxing
 * Date:2020-09-14
 */
public class SignUtilTest {
    public static void main(String[] args) {
        String ctimestamp = "1599648837833";
        // 时间戳,用户自己生成
        String cnonce = "123456";
        // 6位随机码,用户自己生成
        String requestParam = "";
        //请求参数,form表单方式,鸿图大部分接口都是body传参,故这里大部分都是"",如果有参数,查看文档说明
        String url = "/v1/api/device/list";
        // 要请求的url,看要请求的接口说明
        String method = "POST";
        // 请求方式,看要请求的接口说明
        String cappKey = "appkey1";
        // appkey,固定,
        String secret = "sdfajk3242324fa!djq7";
        // 秘钥,固定
        DeviceReq deviceReq = DeviceReq.builder().name("core").build();
        // 构建请求体,看要请求的接口说明
        // 计算签名
        String csign = SignUtlis.sign(ctimestamp, cnonce, deviceReq, requestParam, url, method, cappKey, secret);
        // 发起请求
        String realUrl = "http://10.122.101.181:18082" + url;
        postJson(ctimestamp, cnonce, cappKey, csign, deviceReq, realUrl);
    }
    private static void postJson(String ctimestamp, String cnonce, String cappKey, String csign, Object reqeustData, String url) {
        String bodyJson = JSONObject.toJSONString(reqeustData);
        OkHttpClient httpClient = new OkHttpClient.Builder()
                        .connectTimeout(3000, TimeUnit.SECONDS)
                        .readTimeout(30000, TimeUnit.SECONDS)
                        .writeTimeout(30000, TimeUnit.SECONDS)
                        .build();
        MediaType mediaType = MediaType.parse("application/json;charset=UTF-8");
        RequestBody requestBody = RequestBody.create(mediaType, bodyJson);
        Request request = new Request.Builder()
                        .url(url)
                        .addHeader("cnonce", cnonce)
                        .addHeader("ctimestamp", ctimestamp)
                        .addHeader("cappKey", cappKey)
                        .addHeader("csign", csign)
                        .post(requestBody)
                        .build();
        Call call = httpClient.newCall(request);
        try {
            Response response = call.execute();
            System.out.println("请求结果:" + response.body().string());
            System.out.println(response);
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
}

results matching ""

    No results matching ""