云简标准SSO
文档说明
本文旨在提供标准的云简系统的单点登录方式,由云简提供认证服务。
术语
- 加密模式。云简标准SSO支持AES/ECB/PKCS5Padding和AES/CBC/PKCS5Padding两种加密模式,默认AES/ECB/PKCS5Padding。用于加密用户信息。
- 公司编码和密钥。客户在云简系统拥有唯一编码和密钥,用于获取用户标识加密code。
- 用户标识。云简和客户双方需要约定用户的唯一标识(员工号/用户名/邮箱等),默认员工号。
步骤
- 选择加密方式和用户标识。
- 根据云简提供的公司编码(company_code)和密钥(company_key),获取/生成用户标识加密code。
- 拼接SSO跳转链接。
要点
- SSO链接有效期默认十分钟,可配置,需在选用加密模式和用户标识阶段确定。
- 获取用户信息的加密code分两种方式:接口调用/代码实现,详见附录1。
- 拼接SSO跳转链接详见附录2,生产环境与测试环境的链接不同。
附录1
接口调用
接口说明:获取用户信息的加密code
接口地址:common/oauth2/unAuth/authorize
Method:POST
请求参数:
字段 | 类型 | 说明 | 必须 |
---|---|---|---|
bizId | String | 业务唯一识别码 | 是 |
timestamp | long | 时间戳 | 是 |
data.user_id | String[] | 用户唯一标识 | 是 |
data.company_code | String | 公司编码 | 是 |
data.company_key | String | company_key和timestamp MD5加密结果。详见下文。 | 是 |
data.timestamp | long | 时间戳 | 是 |
- 请求示例:
{
"bizId": "634899ef-d591-4829-ac18-0bcc135251ff",
"timestamp": 1605010305740,
"data": {
"user_id": "xxxx@xxx.com",
"company_code": "xxx",
"company_key": "xxx",
"timestamp": "1605010305740"
}
}
- 返回参数:
字段 | 类型 | 说明 |
---|---|---|
resCode | Integer | 状态码 |
resMsg | String | 描述信息 |
bizId | String | 业务唯一识别码 |
data.code | String | 用户加密code |
- 返回示例:
成功:
{
"resCode": 200000,
"resMsg": "生成code成功",
"bizId" : "634899ef-d591-4829-ac18-0bcc135251ff",
"data": {
"code":"jdsPBlZkOFjQKYokgaSTILKC47Ey9q5Rw6gaS8YePvDaRkO8sWWnUMxqTYYIxDU0"
}
}
失败:
{
"resCode": 500000,
"resMsg": "company key 验证失败",
"bizId": "634899ef-d591-4829-ac18-0bcc135251ff "
}
MD5 DEMO
public static String byteArrayToHex(byte[] byteArray) {
char[] hexDigits = {'0','1','2','3','4','5','6','7','8','9', 'a','b','c','d','e','f' };
char[] resultCharArray =new char[byteArray.length * 2];
int index = 0;
for (byte b : byteArray) {
resultCharArray[index++] = hexDigits[b>>> 4 & 0xf];
resultCharArray[index++] = hexDigits[b& 0xf];
}
return new String(resultCharArray);
}
public static String MD5(String str){
MessageDigest md = null;
try {
md = MessageDigest.getInstance("MD5");
} catch (Exception e){
throw new RuntimeException("MD5 Exception");
}
md.update((str).getBytes(StandardCharsets.UTF_8));
return MD5toString.byteArrayToHex(md.digest());
}
代码实现
public static String Encrypt(String sSrc, String sKey, String entryMode) {
entryMode = !Strings.isNullOrEmpty(entryMode) ? entryMode : "AES/ECB/PKCS5Padding";
try {
if (sKey == null) {
log.info("Key为空null");
return null;
}
// 判断Key是否为16位
if (sKey.length() != 16) {
log.info("Key长度不是16位");
return null;
}
byte[] raw = sKey.getBytes("utf-8");
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance(entryMode);//"算法/模式/补码方式"
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
byte[] encrypted = cipher.doFinal(sSrc.getBytes("utf-8"));
//此处使用BASE64做转码功能,同时能起到2次加密的作用。
return URLEncoder.encode(new Base64().encodeToString(encrypted), "utf-8");
} catch (Exception e) {
log.error("加密出错", e);
}
return null;
}
附录2
参数说明如下:
变量 | 必须 | 说明 |
---|---|---|
https://qa... | 是 | 授权后重定向的回调地址,即跳转费控的地址。 qa:测试环境,www:生产环境 |
source | 是 | 固定:new |
companyCode | 是 | 企业在费控的编码 |
position | 否 | 跳转页面。 main:首页, approveList:审批列表页, claimList:单据列表, createClaim:创建单据页, claim:单据详情页, approve:审批详情页, financeApproval:财务审批页, claimView:单据查看, bankflowList:银行流水, deliveryOperation:集中收单, invoiceList:消费记录, purchaseInvoiceList:采购发票, approveHistoryDetail:审批历史详情, approvalHistoryDetail:财务审核历史详情 ,businessTravel:企业消费管理页 |
pathId | 否 | 审批流ID,审批详情和财务审批页额外参数 position=approve&pathId={PATH_ID} position=financeApproval&pathId={PATH_ID} |
documentId | 否 | 单据ID,单据详情页额外参数 position=claim&documentId={DOCUMENT_ID} |
headerTypeId | 否 | 单据类型ID,创建单据页/单据列表页额外参数, position=createClaim&headerTypeId={HEADER_TYPE_ID},或者 position= claimList&headerTypeId={HEADER_TYPE_ID} |
headerId | 否,仅 | 单据头ID,单据查看/审批历史详情/财务审核历史详情页面额外参数 position=claimView&headerId={HEADER_ID},或者 position=approveHistoryDetail&headerId={HEADER_ID},或者 position=approvalHistoryDetail&headerId={HEADER_ID} |
groupNum | 否 | 单据分组的group_num,单据列表页额外参数, 用于显示特定分组的单据列表 position=claimList&groupNum={GROUPNUM} |
embedded | 否 | 跳转网页时, 不需要菜单:Y,需要菜单:N |
autoClose | 否 | 仅用于web Y:在审批/财务审核通过或拒绝之后会自动关闭页面 |
code | 是 | 用户信息加密code |
跳转成功后的首页,如下所示: