系统集成技术接入指引
智能问数页面支持被灵活的嵌入至第三方平台中,可以在此对嵌入页面的呈现样式、展示元素进行自定义配置。
实现方式
通过 iframe 分享智能问数页面的方式嵌入;
分享支持 2 种方式:
- 公开分享:即所有互联网用户都能随意访问。
-
token 分享:即用户在访问发布的智能问数 iframe 嵌入页时需要通过 Sugar BI 提供的 Token 参数签名计算规则,生成访问 URL,用此 URL 访问时Sugar BI 会自动进行访问 URL 的 鉴权,鉴权通过之后才能访问智能问数页面,否则不能访问,以此来将Sugar BI智能问数的访问权限与用户的第三方系统权限体系进行集成。
token 分享方式更利于保证数据的安全,需要对用户传递的参数进行签名鉴权,保证计算得到的 URL 参数不能被更改,如果私自更改了传参,页面将无法访问。
集成步骤
公开分享的集成
新建公开分享智能问数 iframe 页面
在「组织管理——智能问数嵌入管理」中,新建嵌入页面,添加问数范围,右侧控制面板「逻辑设置——过程代码分享方式」选项选择「公开」
发布和查看智能问数 iframe 页面
点击「发布生效」后,系统会生成一个可访问的链接,可以使用这个链接直接访问智能问数页面
集成智能问数 iframe 页面
第三方系统集成时可直接嵌入此 URL 即可
token 分享的集成
新建 token 分享智能问数 iframe 页面
在「组织管理——智能问数嵌入管理」中,新建嵌入页面,添加问数范围,右侧控制面板「逻辑设置——过程代码分享方式」选项选择「通过 token 验证」 页面上会自动生成一个 token 字符串,用于后续集成中的签名生成和鉴权
发布和查看智能问数 iframe 页面
点击「发布生效」后,系统会生成一个 URL 注意:这个 URL 不能直接访问和使用智能问数
集成智能问数 iframe 页面
集成流程
- 确定需要签名计算的参数名(即不允许被篡改的参数,一般是访问者的 email 信息);
- 使用签名参数,token,和 3.2.2 中生成的 URL 计算生成最终访问智能问数页面的 URL;
- 使用上一步中计算得到的 URL 访问Sugar BI 智能问数页面,在访问过程中,Sugar BI 系统会自动进行鉴权;
- 如果鉴权通过,将带着该用户信息访问Sugar BI 智能问数页面;
- 当访问者修改了签名参数,再次访问此 URL 时,访问会被拒绝(例如参数zhangsan@baidu.com被修改为lisi@baidu.com,将无法访问)。
参数说明
参数名 | 参数说明 | 参数值 |
---|---|---|
shareID | shareID 是智能问数在分享时自动生成的 url 中的部分:
如:http://127.0.0.1:8000/gbi-iframe/1b89d8d621bca917dd481b965c216dbf 最后一段 1b89d8d621bca917dd481b965c216dbf 就是 shareID |
eg. 1b89d8d621bca917dd481b965c216dbf |
token | 新建智能问数分享页面,右侧控制面板「逻辑设置——过程代码分享方式」选项选择「通过token验证」后页面上生成的token,可直接复制 | eg. 9zUfbrmDJhBhQt8qywjzasZmWNbHnOXg |
sugar_sign_user_email |
需要加入签名的参数,其参数名需以 sugar_sign_ 开头,后面可以带任何有效的参数名字符; 不符合此签名参数规则的参数,将不会进行参数签名校验,允许修改参数值; 签名参数按升序排序。 |
eg. zhangsan@baidu.com |
示例代码
拿到上述参数后,就可以使用以下示例代码,进行智能问数页面的集成开发。
nodejs
const crypto = require('crypto');
const querystring = require('querystring');
const signedQueryParamReg = /^sugar_sign_.*/; // 符合此正则表达式的参数是需要签名的。
let token = 'OAMf7CvniOGgoNijH9mFHEHSAf7****';
let shareID = '1827981ec07ac66f937a88c9e65f****'; // shareID详见前面文档中的说明
const time = Date.now();
const customParams = {
sugar_sign_user_email: 'zhangsan@baidu.com'
};
let signParamsStr = Object.keys(customParams)
.filter(paramName => customParams[paramName] && signedQueryParamReg.test(paramName))
.sort()
.map(param => `${param}=${customParams[param]}`)
.join('&');
let stringToSign = [shareID, time];
signParamsStr && stringToSign.push(signParamsStr);
stringToSign = stringToSign.join('|');
let signature = crypto.createHmac('sha256', token).update(stringToSign).digest().toString('base64');
let queryParams = {
_sugar_time: time,
_sugar_signature: signature
};
Object.keys(customParams).forEach(paramName => {
queryParams[paramName] = customParams[paramName];
});
let url = `https://sugar.baidubce.com/gbi-iframe/${shareID}?${querystring.stringify(queryParams)}`;
console.log(url);
PHP
<?php
$token = "OAMf7CvniOGgoNijH9mFHEHSAf7****";
$shareID = "1827981ec07ac66f937a88c9e65f****"; // shareID详见前面文档中的说明
$time = time()*1000;
$customParams = array(
'sugar_sign_user_email'=>'zhangsan@baidu.com'
);
$sign_array = preg_grep("/^sugar_sign_.*/", array_keys($customParams));
sort($sign_array);
function toPlain($v)
{
global $customParams;
return "$v=$customParams[$v]";
};
$signParamsStr = join("&",array_map("toPlain",$sign_array));
$stringToSign = $shareID.'|'.$time.'|'.$signParamsStr;
$signature = urlencode(base64_encode(hash_hmac('sha256', $stringToSign, $token, true)));
$queryParams = join("&",array_map("toPlain",array_keys($customParams)));
$url = "https://sugar.baidubce.com/gbi-iframe/".$shareID."?_sugar_time=".$time."&_sugar_signature=".$signature."&".$queryParams;
echo $url;
?>
<iframe width=100% height=100% src="<?=$url?>"/>
java
package com.company;
import java.security.*;
import java.util.Date;
import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
import java.net.URLEncoder;
public class TokenTest {
public static String getSignedUrl(String shareID, String token, String signParamsStr) {
Date date = new Date();
Long time = date.getTime();
String stringToSign = shareID + "|" + time;
if (!StringUtils.isEmpty(signParamsStr)) {
stringToSign = stringToSign + "|" + signParamsStr;
}
String signature = HMACSHA256(stringToSign.getBytes(), token.getBytes());
String url = "https://sugar.baidubce.com/gbi-iframe/" + shareID + "?_sugar_time=" + time + "&_sugar_signature=" + signature + "&" + signParamsStr;
return url;
}
/**
* 使用java原生的摘要实现SHA256加密。
* @param str加密后的报文。
* @return
*/
public static String HMACSHA256(byte[] data, byte[] key) {
try {
SecretKeySpec signingKey = new SecretKeySpec(key, "HmacSHA256");
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(signingKey);
return URLEncoder.encode(byte2Base64(mac.doFinal(data)));
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
}
return null;
}
private static String byte2Base64(byte[] bytes){
return Base64.encodeBase64String(bytes);
}
public static void main(String[] args) throws Exception {
String signedQueryParamReg = "^sugar_sign_.*";
Map<String, Integer> customParams = new HashMap<>();
customParams.put("sugar_sign_user_email", 'zhangsan@baidu.com');
String signParamsStr = customParams.entrySet().stream().filter(entry -> Pattern.matches(signedQueryParamReg, entry.getKey()))
.map(entry -> entry.getKey() + "=" + entry.getValue())
.sorted()
.collect(Collectors.joining("&"));
System.out.println(getSignedUrl("shareID", "token", signParamsStr)); // shareID详见前面文档中的说明
}
}
.NET
using System;
using System.Security.Cryptography;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Text;
namespace sugarToken
{
class Program
{
static void Main(string[] args)
{
var dic = new Dictionary<string, string>(); // 自定义参数。
dic.Add("sugar_sign_user_email", "zhangsan@baidu.com"); // sugar_sign_开头,需要签名。
// 分享页前缀,iframe分享shareID、token,自定义参数字典。
Console.WriteLine(GenerateUrl("https://sugar.baidubce.com/gbi-iframe/", "1827981ec07ac66f937a88c9e65f****", "OAMf7CvniOGgoNijH9mFHEHSAf7****", dic));
}
private static string GenerateUrl(string sugarBase, string shareID, string token, Dictionary<string, string> customParams)
{
string pattern = @"^sugar_sign_.*";
string timestamp = GetTimeStamp();
// 参数排序
Dictionary<string, string>.KeyCollection keyCol = customParams.Keys;
List<string> signKeys = new List<string>();
foreach (var item in keyCol.ToList())
{
if (Regex.IsMatch(item, pattern))
{
signKeys.Add(item);
}
}
// 按照key排序
signKeys = signKeys.OrderBy(k => k).ToList();
string paramsSignStr = signKeys.Aggregate("", (total, key) =>
{
if (total != "")
{
total += "&";
}
total += key + "=" + customParams[key];
return total;
});
string signStr = shareID + "|" + timestamp + "|" + paramsSignStr;
var encoding = new System.Text.ASCIIEncoding();
byte[] keyByte = encoding.GetBytes(token);
byte[] messageBytes = encoding.GetBytes(signStr);
string signature;
using (var hmacsha256 = new HMACSHA256(keyByte))
{
byte[] hashmessage = hmacsha256.ComputeHash(messageBytes);
signature = Convert.ToBase64String(hashmessage);
}
var paramDic = new Dictionary<string, string>();
paramDic.Add("_sugar_time", timestamp);
paramDic.Add("_sugar_signature", signature);
foreach (var item in customParams)
{
paramDic.Add(item.Key, item.Value);
}
return sugarBase + shareID + "?" + ParseToString(paramDic);
}
public static string GetTimeStamp()
{
TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
return Convert.ToInt64(ts.TotalMilliseconds).ToString();
}
static public string ParseToString(IDictionary<string, string> parameters)
{
IDictionary<string, string> sortedParams = new SortedDictionary<string, string>(parameters);
IEnumerator<KeyValuePair<string, string>> dem = sortedParams.GetEnumerator();
StringBuilder query = new StringBuilder("");
while (dem.MoveNext())
{
string key = dem.Current.Key;
string value = dem.Current.Value;
if (!string.IsNullOrEmpty(key) && !string.IsNullOrEmpty(value))
{
query.Append(key).Append("=").Append(HttpUtility.UrlEncode(value)).Append("&");
}
}
string content = query.ToString().Substring(0, query.Length - 1);
return content;
}
}
}
Python
import time
import hmac
import base64
import urllib.parse
now = str(round(time.time() * 1000))
token = b'OAMf7CvniOGgoNijH9mFHEHSAf7****'
# shareID详见前面文档中的说明
share_id = '1827981ec07ac66f937a88c9e65f****'
# 自定义参数,其中sugar_sign_开头,需要签名
custome_params = {
"sugar_sign_user_email": 'zhangsan@baidu.com'
}
# 按照key排序
sorted_custome_params = {i: custome_params[i] for i in sorted(custome_params.keys())}
sign_params_str = '&'.join([f"{k}={v}" for k, v in sorted_custome_params.items() if k.startswith("sugar_sign_")])
string_to_sign = '|'.join([share_id, now, sign_params_str])
signature = base64.b64encode(hmac.digest(token, string_to_sign.encode('utf-8'), 'sha256')).decode('utf-8')
signature = urllib.parse.quote(signature)
# 生成最终链接
url = f"https://sugar.baidubce.com/gbi-iframe/{share_id}?_sugar_time={now}&_sugar_signature={signature}&" \
+ urllib.parse.urlencode(sorted_custome_params)
print(url)
结果验证
可以根据用户自己的技术栈选择对应的示例代码,生成的 URL 示例如下:
在 URL 的有效期内(分享未取消或被删除),如果修改了 sugar_sign_user_email 字段的值,将无法访问,可以避免数据泄漏风险
验证工具地址
Sugar BI 官方文档中提供了一个在线验证 URL 正确性的工具,可以使用此工具生成目标 URL 与用户在权限集成过程中代码生成的 URL 进行比对,来判断结果是否正确。您可以访问「签名 URL 计算工具」来生成带签名参数的 URL 示例。