# 签名算法

接入方访问文档中台在线预览编辑服务的接口时,都需要在请求头添加 Authorizationnoncetimestamp 三个字段。其中,Authorization 字段由 ${appId}:${signature} 组成,中间用半角冒号(英文输入模式)隔开;nonce 是长度为 6 的一串随机数,例如 413410 ;timestamp 是当前时间戳(秒),例如 1619078626。appId 由文档中台提供,每个接入方有唯一的 appId 和相应的AppSecretsignature 通过下述签名算法生成。

签名参数

key value示例 说明
appId dd379d6c appId
method GET 请求方式,大写
nonce 123adf456aof2131ew 10-32 位随机字符串
uri /api/edit&fid=JHhjABmSbKiy2Oujkq2 请求路径。此路径包含query参数,参数顺序同实际调用顺序。
body dd4034cf589875fee94d34410d98e67a 对请求体转字符串取md5值。GET请求body不参与签名计算。form-data中的file使用md5值作为value,对参数key进行字典排序,使用& =拼接参数转为queryString格式字符串,最后对拼接字符串取md5值。
timestamp 1619078626 时间戳(秒)

将上述参数按照 key 的字典顺序(升序)来排序,keyvalue 之间用 = 连接,每个参数之间用 & 连接,得到如下字符串:

appId=dd379d6c&method=GET&nonce=123adf456aof2131ew&timestamp=1619078626&uri=%2Fapi%2Fedit%26fid%3DJHhjABmSbKiy2Oujkq2

接着对上一步得到的字符串使用 HmacSHA1 算法加密,秘钥为 AppSecret,对结果使用 base64 编码。

例如,对于上述的例子,如果对应的 AppSecret = bb84cd4a6a123632ce2be787c955ac0e,则通过签名算法得到的签名为 vxX3aZ2Y4rFMjkNrSrY/AVIOLeA=

示例代码

public static String signature(String accessKeySecret, SignModel signModel) throws Exception {
    Map<String,String> signMap =  JSON.parseObject(JSON.toJSONString(signModel), new TypeReference<Map<String,String>>(){});
    String baseString = getQueryString(signMap);
    log.info("baseString:" + baseString);
    Mac mac = Mac.getInstance("HmacSHA1");
    SecretKeySpec keySpec = new SecretKeySpec(accessKeySecret.getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8.name());
    mac.init(keySpec);
    byte[] signBytes = mac.doFinal(baseString.getBytes(StandardCharsets.UTF_8));
    log.info(new String(signBytes,"utf-8"));
    log.info(bytesToHex(signBytes));
    return Base64.getEncoder().encodeToString(signBytes);
}

public static String bytesToHex(byte[] bytes) {
    StringBuffer sb = new StringBuffer();
    for(int i = 0; i < bytes.length; i++) {
        String hex = Integer.toHexString(bytes[i] & 0xFF);
        if(hex.length() < 2){
            sb.append(0);
        }
        sb.append(hex);
    }
    return sb.toString();
}

public static String getQueryString(Map<String, String> queryParam) throws UnsupportedEncodingException {
    TreeMap<String, String> treeMap = new TreeMap<>(queryParam);
    StringBuilder builder = new StringBuilder();
    for (Map.Entry<String, String> entry : treeMap.entrySet()) {
        String value = entry.getValue();
        if (value != null && !value.isEmpty()) {
            String encode = URLEncoder.encode(value, StandardCharsets.UTF_8.name());
            builder.append(entry.getKey()).append("=").append(encode).append("&");
        }
    }
    if (builder.length() > 0) {
        builder.deleteCharAt(builder.length() - 1);
    }
    return builder.toString();
}

中间用到的实体类SignModel.class

public class SignModel {
    /**
     * 应用唯一标识
     */
    private String appId;

    /**
     * 调用方法
     */
    private String method;

    /**
     * 随机字符,混淆签名
     */
    private String nonce;

    /**
     * 请求地址(包含query参数)
     */
    private String uri;

    /**
     * 请求体(json请求体取md5值。form-data请求参数转为queryString后取md5值, file参数转为md5值参与计算)
     */
    private String body;

    /**
     * 秒级时间戳
     */
    private String timestamp;
}