用户中心
所有文章
组织操作手册
人事操作手册
入职操作手册
假勤操作手册
薪酬操作手册
绩效操作手册
团队管理操作手册
员工自助操作手册
账号权限操作手册
审批流配置操作手册
生态(IM)操作手册
第三方集成操作手册
常见问题
升级公告
签名规则
最后更新于 2023/12/18   阅读数 431
  • 签名

为了保证接口调用的安全性及防止数据被篡改,使用接口参数签名的方式生成签名信息,随请求携带,发送给接收方。

  • 验签

接收方为了验证数据的完整性和正确性,利用与发送方同样的签名方式生成签名信息,然后与请求中携带的签名信息比对,如果一致则请求合法,否则请求不合法。

 

1.签名步骤

应用过程中, 接口调用流程如下:

 

2.签名计算过程

为了验证用户的请求合法性,使用 RSA 验签的方式对用户的请求参数 sign 进行验证

签名 sign 的生成方法如下:

第 1 步:将请求 query 参数中的所有不包含 sign 参数的数据,按照字典顺序对参数进行排序,生成 url 格式的字符串作为待签名数据。

示例 1 (无 username 参数):

apiCode=0001&entCode=1&nonce=999&timestamp=1565244098737

示例 2 (有 username 参数):

apiCode=0001&entCode=1&nonce=999&timestamp=1565244098737&userName=xiao@qq.com

第 2 步:对上述字符串做 md5WithRsa 算法进行签名,得到 sign。

第 3 步:最后把 sign 放入 url 请求参数中即可

1.在发送HTTP请求之前,由于签名sign中有特殊字符,需要对其做URLencode编码处理,否则验签失败

2.RestTemplate发送http请求会自动解码,建议使用okhttp、httpclient组件

3.Postman调用(员工任职数据接口)示例

curl --location --request POST 'https://api.mokahr.com/api-platform/hcm/oapi/v1/batch/data?apiCode=接口编码&entCode=租户编码&nonce=k2ajsqaa&timestamp=1660187065416&userName=moxiaoka@moka.com&sign=URLencode()的签名串' \
--header 'Authorization: Basic base64(apiKey)' \
--header 'Content-Type: application/json' \
--data-raw '{
    "uuidList": [],
    "employeeNoList": [],
    "telephoneList": [],
    "officeEmailList": [],
    "startDate": "",
    "endDate": "",
    "pageSize": 200,
    "pageNum": 1
}'
1
2
3
4
5
6
7
8
9
10
11
12
13
json5

4.签名算法 Java 版

第一步:创建 Base64 编码工具类

/**
 * Base64 编码工具类
 * @author Moka
 * @date 2022/5/10
 * @apiNote
 */
public class Base64 {
    private static char[] Base64Code = {
            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
            'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
            'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
            'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
            'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
            'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7',
            '8', '9', '+', '/' };
    private static byte[] Base64Decode = {
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
            -1, -1, -1, 62, -1, 63, -1, 63, 52, 53,
            54, 55, 56, 57, 58, 59, 60, 61, -1, -1,
            -1, 0, -1, -1, -1, 0, 1, 2, 3, 4,
            5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
            15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
            25, -1, -1, -1, -1, -1, -1, 26, 27, 28,
            29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
            39, 40, 41, 42, 43, 44, 45, 46, 47, 48,
            49, 50, 51, -1, -1, -1, -1, -1 };
    public static String encode(byte[] b)
    {
        int code = 0;
        if (b == null)
            return null;
        StringBuffer sb = new StringBuffer((b.length - 1) / 3 << 6);
        for (int i = 0; i < b.length; i++)
        {
            code |= b[i] << 16 - i % 3 * 8 &amp; 255 << 16 - i % 3 * 8;
            if ((i % 3 != 2) &amp;&amp; (i != b.length - 1))
                continue;
            sb.append(Base64Code[((code &amp; 0xFC0000) >>> 18)]);
            sb.append(Base64Code[((code &amp; 0x3F000) >>> 12)]);
            sb.append(Base64Code[((code &amp; 0xFC0) >>> 6)]);
            sb.append(Base64Code[(code &amp; 0x3F)]);
            code = 0;
        }
        if (b.length % 3 > 0)
            sb.setCharAt(sb.length() - 1, '=');
        if (b.length % 3 == 1)
            sb.setCharAt(sb.length() - 2, '=');
        return sb.toString();
    }
    public static byte[] decode(String code)
    {
        if (code == null)
            return null;
        int len = code.length();
        if (len % 4 != 0)
            throw new IllegalArgumentException("Base64 string length must be 4*n");
        if (code.length() == 0)
            return new byte[0];
        int pad = 0;
        if (code.charAt(len - 1) == '=')
            pad++;
        if (code.charAt(len - 2) == '=')
            pad++;
        int retLen = len / 4 * 3 - pad;
        byte[] ret = new byte[retLen];
        for (int i = 0; i < len; i += 4)
        {
            int j = i / 4 * 3;
            char ch1 = code.charAt(i);
            char ch2 = code.charAt(i + 1);
            char ch3 = code.charAt(i + 2);
            char ch4 = code.charAt(i + 3);
            int tmp = Base64Decode[ch1] << 18 | Base64Decode[ch2] << 12 | Base64Decode[ch3] << 6 | Base64Decode[ch4];
            ret[j] = (byte)((tmp &amp; 0xFF0000) >> 16);
            if (i < len - 4)
            {
                ret[(j + 1)] = (byte)((tmp &amp; 0xFF00) >> 8);
                ret[(j + 2)] = (byte)(tmp &amp; 0xFF);
            }
            else {
                if (j + 1 < retLen)
                    ret[(j + 1)] = (byte)((tmp &amp; 0xFF00) >> 8);
                if (j + 2 < retLen)
                    ret[(j + 2)] = (byte)(tmp &amp; 0xFF);
            }
        }
        return ret;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
java

第二步:创建 RAS 加解密工具类

import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
/**
 * RSA 加解密工具类
 * @author Moka
 * @date 2022/5/10
 * @apiNote
 */
public class RSAUtils {
    /**
     * 加密算法 RSA
     */
    public static final String KEY_ALGORITHM = "RSA";
    /**
     * 签名算法
     */
    public static final String SIGNATURE_ALGORITHM = "MD5withRSA";
    /**
     * 获取公钥的 key
     */
    private static final String PUBLIC_KEY = "RSAPublicKey";
    /**
     * 获取私钥的 key
     */
    private static final String PRIVATE_KEY = "RSAPrivateKey";
    /**
     * RSA 最大加密明文大小
     */
    private static final int MAX_ENCRYPT_BLOCK = 117;
    /**
     * RSA 最大解密密文大小
     */
    private static final int MAX_DECRYPT_BLOCK = 128;
    /**
     * <p>
     * 用私钥对信息生成数字签名
     * </p>
     *
     * @param data 已加密数据
     * @param privateKey 私钥(BASE64 编码)
     *
     * @return
     * @throws Exception
     */
    public static String sign(byte[] data, String privateKey) throws Exception {
        byte[] keyBytes = Base64.decode(privateKey);
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        PrivateKey privateK = keyFactory.generatePrivate(pkcs8KeySpec);
        Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
        signature.initSign(privateK);
        signature.update(data);
        return Base64.encode(signature.sign());
    }
    /**
     * <p>
     * 校验数字签名
     * </p>
     *
     * @param data 已加密数据
     * @param publicKey 公钥(BASE64 编码)
     * @param sign 数字签名
     *
     * @return
     * @throws Exception
     *
     */
    public static boolean verify(byte[] data, String publicKey, String sign)
            throws Exception {
        byte[] keyBytes = Base64.decode(publicKey);
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        PublicKey publicK = keyFactory.generatePublic(keySpec);
        Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
        signature.initVerify(publicK);
        signature.update(data);
        return signature.verify(Base64.decode(sign));
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
java

 第三步:创建测试类

import cn.hutool.core.util.RandomUtil;
import java.net.URLEncoder;

/**
 * 测试类
 * @author Moka
 * @date 2022/5/10
 * @apiNote
 */
public class RSATester {

    static String publicKey = "替换 Moka 提供得 publicKey";
    static String privateKey = "替换 Moka 提供得 privateKey";

    public static void main(String[] args) throws Exception {
        long timestamp = System.currentTimeMillis();
        String randomString = RandomUtil.randomString(8);
        String userName = "hanshg@mokahr.com";
        //String s = "apiCode=25af48e84326d81f95d9021652bd4036&amp;entCode=9d94eb859a6742d98e32fce040bbb8dc&amp;nonce=" + randomString + "&amp;timestamp=" + timestamp + "&amp;userName=" + userName;
        String s = "apiCode=接口编码&amp;entCode=租户编码&amp;nonce=" + randomString + "&amp;timestamp=" + timestamp;
        System.err.println(s);
        String sign = RSAUtils.sign(s.getBytes(), privateKey);
        System.out.println("签名:" + URLEncoder.encode(sign, "UTF-8"));
        boolean status = RSAUtils.verify(s.getBytes(), publicKey, sign);
        System.out.println("验证结果:" + status);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
java

 

4.签名算法 Python 版

from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5
from Crypto.Hash import MD5
import base64

def my_hash(data):
    return MD5.new(data.encode('utf-8'))

def rsa_sign(data):
    private_key_file = open('private.pem', 'r')
    pri_key = RSA.importKey(private_key_file.read())
    signer = PKCS1_v1_5.new(pri_key)
    hash_obj = my_hash(data)
    signature = base64.b64encode(signer.sign(hash_obj))
    private_key_file.close()
    return signature

def rsa_verify(signature, data):
    public_key_file = open('public.pem', 'r')
    pub_key = RSA.importKey(public_key_file.read())
    hash_obj = my_hash(data)
    verifier = PKCS1_v1_5.new(pub_key)
    public_key_file.close()
    return verifier.verify(hash_obj, base64.b64decode(signature))

if __name__ == '__main__':
    test_data = "apiCode=0fe827b0ce0c2f1249642119bc3efcdb&amp;entCode=0fe827b0ce0c2f1249642119bc3efcdb&amp;nonce=99999915&amp;timestamp=1632742060453&amp;userName=qyzhg113@core.qyzhg.com"
    print(rsa_sign(test_data).decode('utf-8'))
    print(rsa_verify(rsa_sign(test_data), test_data))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
python
未能解决您的问题?请联系