# AI比价系统单点登录

# 一、基本介绍

用户系统可以通过生成登录链接的方式单点登录到慧讯网AI比价数据系统。

# 二、使用方法

# 登录方式

用户系统通过特殊规则生成登录链接,用户点击该链接即会单点登录到慧讯网AI比价数据系统。

生成链接有效时间1小时,只能使用一次。

# 前置条件

  • 用户系统需要提前申请 appKeyappSecret(应用程序密钥,用户在购买慧讯网AI比价数据系统API服务时获得的唯一识别密钥)。
  • 用户系统中需要提前同步用户的账号信息到慧讯网AI比价数据系统。

# 链接格式

生成的单点登录链接格式:
https://srm.iccchina.com/#/open/auto_login?token={secretDigest}&appKey={appKey}&timestamp={timestamp}

# 链接参数说明

参数 类型 | 是否必须 说明
secretDigest String ✔️
appKey String ✔️
timestamp 当前时间戳(13位) ✔️
redirect_uri 登录后重定向的地址 ✖️

secretDigest为 通过 aes-256-ctr模式 加密 用户的 UUID 生成的密钥。

其中UUID为自身OA系统中账号的唯一性约束(每个用户对应一个,同步组织结构时会传递到慧讯网AI比价数据系统)。secretKey 作为密钥(32位)。timestamp 为当前时间戳,需要同步在链接中传递,timestamp + '000' 作为偏移(凑够16位即可)。

加密方式详见下文示例代码。

# 链接生成示例

https://srm.iccchina.com/#/open/auto_login?token=8123497494&appKey=12345678901234567890123456789012&timestamp=1744358531893

# 三、secretDigest生成方式

# 生成secretDigest的说明

使用aes-256-ctr模式加密生成secretDigest参数。
其中:
加密的内容为用户的UUID
密钥为appSecret
偏移为timestamp + '000' 凑够16个字节(timestamp默认是13个字节的,如果是其他数量的字节数,一直往后面加0凑够16个字节作为 偏移)。

# 生成secretDigest示例代码(NodeJS)

let crypto = require('crypto')
 
function encrypt(text) {
    if (!text) {
      return ''
    }
    let algorithm = 'aes-256-ctr'
    let password = '12345678901234567890123456789012' // 密钥:appSecret
    let piv = '1234567890123456' // 偏移:timestamp + '000' 凑够16个字节(timestamp默认是13个字节的,如果是其他数量的字节数,一直往后面加0凑够16个字节作为 偏移)
    try {
      let cipher = crypto.createCipheriv(algorithm, password, piv)
      let crypted = cipher.update(text, 'utf8', 'base64')
      crypted += cipher.final('base64')
      return crypted
    } catch (e) {
      console.log(e)
    }
}
let encrypted = encrypt("8123497494")
console.log(encrypted)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 生成secretDigest示例代码(Java)

import java.lang.Math; // headers MUST be above the first class
import java.util.Base64;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.spec.IvParameterSpec;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.nio.charset.StandardCharsets;
 
// one class needs to have a main() method
public class App
{
    private static void log(String s)
    {
        System.out.print("\r\n"+s);
    }
 
    public static SecureRandom IVGenerator() {
        return new SecureRandom();
    }
 
    // arguments are passed using the text field below this editor
    public static void  main(String[] args)
    {
        String valueToEncrypt = "message";
        String key = "12345678901234567890123456789012"; // 密钥:appSecret
        byte[] iv = new String("1234567890123456").getBytes(); // 偏移:timestamp + '000' 凑够16个字节(timestamp默认是13个字节的,如果是其他数量的字节数,一直往后面加0凑够16个字节作为 偏移)
        int ivLength=16;
 
        String encrypted = "";
        String decrypted = "";
 
        //ENCODE part
        SecureRandom IVGenerator = IVGenerator();
        byte[] encryptionValueRaw = key.getBytes();
 
        try {
            Cipher encryptionCipher = Cipher.getInstance("AES/CTR/NoPadding");
            encryptionCipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(encryptionValueRaw, "AES"), new IvParameterSpec(iv));
            //encrypt
            byte[] cipherText = encryptionCipher.doFinal(valueToEncrypt.getBytes());
 
            ByteBuffer byteBuffer = ByteBuffer.allocate(cipherText.length);
            //storing IV in first part of whole message
            //store encrypted bytes
            byteBuffer.put(cipherText);
            //concat it to result message
            byte[] cipherMessage = byteBuffer.array();
            //and encrypt to base64 to get readable value
            encrypted = new String(Base64.getEncoder().encode(cipherText));
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
        //END OF ENCODE CODE
        log("encrypted and saved as Base64 : "+encrypted);
 
        ///DECRYPT CODE :
        try {
            //decoding from base64
            byte[] cipherMessageArr = Base64.getDecoder().decode(encrypted);
            // DECRYPT start
            Cipher decryptionCipher = Cipher.getInstance("AES/CTR/NoPadding");
            IvParameterSpec ivSpec = new IvParameterSpec(iv);
            SecretKeySpec secretKeySpec = new SecretKeySpec(encryptionValueRaw, "AES");
            decryptionCipher.init(Cipher.DECRYPT_MODE,secretKeySpec , ivSpec);
            //decrypt
            byte[] finalCipherText = decryptionCipher.doFinal(cipherMessageArr);
            //converting to string
            String finalDecryptedValue = new String(finalCipherText);
            decrypted = finalDecryptedValue;
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
        log("decrypted from Base64->aes128 : "+decrypted);
        //END OF DECRYPT CODE

    }
}
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

# 四、登录后重定向页面设置

用户登录成功后,会重定向到慧讯网AI比价数据系统,默认为首页

如果需要登录后重定向到其他页面,可以通过redirect_uri参数设置。

# 生成带有重定向地址的单点登录链接示例(Javascript)

let redirect_uri = encodeURIComponent('https://srm.iccchina.com/#/special/price_review/project_show/8814ad42284a816d4da89528b0d87d8d?user_price_id=c2d19f079428f909bf73f73b5f00f705&type=icc_price')
let timestamp = Date.now()
let appKey = '12345678901234567890123456789012'
let secretDigest = '123456789'
let url = `URL_ADDRESSrm.iccchina.com/#/open/auto_login?token=${secretDigest}&appKey=${appKey}&timestamp=${timestamp}&redirect_uri=${redirect_uri}`
1
2
3
4
5

# 重定向地址示例

生成的单点登录链接格式:

https://srm.iccchina.com/#/open/auto_login?token=8123497494&appKey=12345678901234567890123456789012&timestamp=1744358531893&redirect_uri=https%3A%2F%2Fsrm.iccchina.com%2F%23%2Fspecial%2Fprice_review%2Fproject_show%2F8814ad42284a816d4da89528b0d87d8d%3Fuser_price_id%3Dc2d19f079428f909bf73f73b5f00f705%26type%3Dicc_price
1