1、背景
在一些第三方系统中希望集成K/3 Cloud系统,在第三方系统登录后能直接进入K/3 Cloud系统,而无须再次通过登录界面录入数据中心,用户名和密码,这时基于安全性需要,需要提供客制化的会话串来实现与第三方系统之间的映射校验。
2、实现
在K/3
Cloud客户端或者Web程序启动时,调用StartAppVerifyService服务进行校验,校验可配置用户定制逻辑组件接入进行校验。如果允许接入,则按用户定制组件逻辑实现K/3
Cloud登录校验,校验通过,则直接按指定用户进入K/3 Cloud系统主控界面。
3、客户定制逻辑组件编写(详细代码参考6)
3.1、新建C#组件库工程,配置为NetFramework4.0支持。
3.2、引用K/3 Cloud WebSite/Bin目录下的如下4个组件:
Kingdee.BOS.dll,
Kingdee.BOS.DataEntity.dll,
Kingdee.BOS.ServiceFacade.ServicesStub.dll,
Kingdee.BOS.ServiceFacade.KDServiceFx.dll。
3.3、添加新类文件,命名为MacVerify(例如),并继承接口 Kingdee.BOS.ServiceFacade.ServicesStub.StartAppVerifyServiceBase抽象类,实现接口行数 StartAppResult Verify(KDServiceContext context, string ipmac)。其中ipmac数据会包含属性customArgs,该属性既是客制化参数,由第三方应用传入,传入方式如下三种方式, 组件中的customArgs参数内容来源于下面定制参数的ud入口参数内容
/// 例如:
/// 【映射用户登入信息方式】
/// WEB的Silverlight: http://xxxx/k3cloud/silverlight/index.aspx?ud=|563073d2b90b4f|Developer|hr|cf6e86aed7d40b1534cf9a1ea557a073626a30ac|1458613257|2052
/// WEB的HTML5:http://xxxx/k3cloud/html5/index.aspx?ud=|563073d2b90b4f|Developer|hr|cf6e86aed7d40b1534cf9a1ea557a073626a30ac|1458613257|2052
/// 客户端命令行:Kingdee.BOS.XPF.App.exe ud=|563073d2b90b4f|Developer|hr|cf6e86aed7d40b1534cf9a1ea557a073626a30ac|1458613257|2052&LoginUrl=http:/./.xxx.xxx.xxx.xxx/k3cloud/ (其中LoginUrl参数是客户端登录的地址,也就是指定应用服务器地址, 如果没有指定该参数,就默认为上次登录过的服务器地址)
/// 简单通行证方式:ud=|dbid|username|appid|signeddata|timestamp[|lcid], 演示可以是明文,正式上线最好做数据加密。
/// 简单通行证的上面密码协议格式自定义,在插件中进行二开解析
///基于安全要求,必须做用户密码验证,请二开做好用户密码映射和加密。密码部分可以扩展用户管理界面,实现密码管理的映射。这样是最安全的。
3.4、MacVerify中可定制客户逻辑,最终返回 StartAppReslut对象即可。如果ResultType==Success正常登录,如果ResultType==Failure,则提示Message中的文本内容。通过customArgs进行数据中心,用户匹配,再调用基类的DoLoginCore方法实现K/3 Cloud的登录校验,或获取返回值的UserToken到StartAppReslut对象中。返回不需处理该UserToken,直接返回StartAppReslut对象即可实现自动登录了。
3.5、需要特别注意的是,不要在插件中直接throw new Exception(),要用返回方式2提示退出,或者方式4提示重定向来显示错误的内容,包括 exception.Message + exception.StackTrace; 一旦插件throw异常,框架会忽略异常后进入登录界面。
4、部署
4.1、拷贝插件到应用服务器的站点目录WebSite/Bin目录下。
4.2、修改配置文件应用服务器的站点目录WebSite/App_Data/Common.config,在appsettings节点下增加如下项目:
<!--启动服务插件-->
<add key ="StartAppPlugin" value="Kingdee.BOS.ServiceFacade.StartApp.MacVerify,Kingdee.BOS.ServiceFacade.StartApp" />
<!--用户参数配置,默认跟随系统,例如中文GB2312-->
<add key ="StartAppPluginUDConfig" value="{encoding:'utf-8'}" />
特别注意:
注意是应用服务器,不是管理中心服务器;
不要只写类名,组件名就不写了。上面value的格式是【类的全名,组件名】
Kingdee.BOS.ServiceFacade.StartApp这个组件和其中的类仅是功能测试和演示使用。二次开发的插件不要使用Kingdee, Kingdee.BOS,Kingdee.BOS.ServiceFacade, Kingdee.BOS.ServiceFacade.StartApp.MacVerify这些命名空间和类名。如果想直接使用这个演示组件内容,建议拷贝文章中的代码内容后,修改命名空间和类名为贵公司的命名规则,不用使用Kingdee命名空间,避免后续功能补丁升级时的覆盖影响。
5、第三方接入方式
实现插件启动登录逻辑(参考后面6的插件实现)后,即可用下面方式实现免登录直接进入K/3 Cloud系统主控。
实现登录服务处理接口,在程序启动界面加入参数ud=|dbid|username|appid|signeddata|timestamp[|lcid]即可实现自动用[用户登录[数据中心
例如:
WEB的Silverlight:
http://xxxx/k3cloud/silverlight/index.aspx?[url=]ud=|dbid|username|appid|signeddata|timestamp[|lcid][/url]
WEB的HTML5:http://xxxx/k3cloud/html5/index.aspx?ud=|dbid|username|appid|signeddata|timestamp[|lcid]
客户端命令行:Kingdee.BOS.XPF.App.exe ud=|dbid|username|appid|signeddata|timestamp[|lcid]
6、第三方接入插件代码编写:
按【3、客户定制逻辑组件编写】完成插件工程配置后,修改启动插件,继承基类StartAppVerifyServiceBase,并实现StartAppResult Verify(KDServiceContext context, string ipmac)接口;
【例子代码全文如下:】
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections.Concurrent;
using Kingdee.BOS.Authentication;
using Kingdee.BOS.JSON;
using Kingdee.BOS.ServiceFacade.KDServiceFx;
using Kingdee.BOS.ServiceFacade.ServicesStub;
using Kingdee.BOS.Resource;
using Kingdee.BOS.Util;
namespace Kingdee.BOS.ServiceFacade.StartApp
{
public class MacVerify : StartAppVerifyServiceBase
{
/// <summary>
/// 客制化参数映射到用户信息,或域签名串映射到用户信息存储池,也可以是数据库
/// </summary>
static ConcurrentDictionary<string, string> customArgsMapUser = new ConcurrentDictionary<string, string>();
static MacVerify()
{
customArgsMapUser["testautologinwithdemo"] = "demo|888888|CPDEMOV5";
}
/// <summary>
/// 随机测试标识,仅用于模拟各种启动校验结果的返回判定
/// </summary>
static int randomFlag = 0;
/// <summary>
/// 实现登录服务处理接口,在程序启动界面加入参数 "ud=TestAutoLoginWithDemo" 即可实现自动用Demo用户登录CPDEMOV5数据中心
/// 例如:
/// WEB的Silverlight: http://xxxx/k3cloud/silverlight/index.aspx?ud=TestAutoLoginWithDemo
/// WEB的HTML5:http://xxxx/k3cloud/html5/index.aspx?ud=TestAutoLoginWithDemo
/// 客户端命令行:Kingdee.BOS.XPF.App.exe ud=TestAutoLoginWithDemo
/// </summary>
public override StartAppResult Verify(KDServiceContext context, string ipmac)
{
var ret = this.Verify(ipmac);
if (ret.ResultType == StartAppResultType.Success)
{
var data = JSONObject.Parse(ipmac);
VerifyLogin(context, ret, data);
}
return ret;
}
/// <summary>
/// 实现登录校验的例子的私有函数
/// </summary>
private void VerifyLogin(KDServiceContext context, StartAppResult ret, JSONObject data)
{
//实现自动登录功能
var customArgs = data.GetValue<string>("customArgs", "");//ud用户定义参数将会传递到服务端插件的customArgs中
var isAutoLogin = !string.IsNullOrWhiteSpace(customArgs);
if (isAutoLogin && context != null)
{
try
{
//不一定非得从data中传进来这些信息,
//也可以通过Mac映射到数据库中的某个记录,从中读取登录信息,
//包括用户名、密码和数据库id,已经对应的语种id
//这里只是Demo而已
var userInfoStr = "";
var key = customArgs.ToLowerInvariant();
customArgsMapUser.TryGetValue(key, out userInfoStr);
if (string.IsNullOrWhiteSpace(userInfoStr))
userInfoStr = "";
var userInfo = userInfoStr.Split('|');
var un = userInfo.Length > 2 ? userInfo[0] : "";
var pw = userInfo.Length > 2 ? userInfo[1] : "";
var dbname = userInfo.Length > 2 ? userInfo[2] : "";
var pluginLoginInfo = new PluginLoginInfo()
{
//AcctID = "xxxxxx", //AcctID如果传递,将不再通过AcctName查找AcctID了。
//LoginType = PluginLoginType.Common, //默认为用户名密码的通用登录方式。 //PluginLoginInfo.AuthenticateType = Kingdee.BOS.Authentication.AuthenticationType.DynamicPwdAuthentication, // 动态密码验证方式
AcctName = dbname,
Username = un,
Password = pw,
ClientInfo = GetClientInfo(context, data) //基类静态工具方法
};
//调用基类静态工具方法实现登录验证,并返回ret.UserToken,给客户端实现自动登录
DoLoginCore(context, ret, pluginLoginInfo);
//登录后的参数传入,具体使用参考【自动打开单据】
if (!string.IsNullOrWhiteSpace(ret.SessionId))
{
var isoKey = ret.SessionId + "_AutoLoginArgs";
//登陆成功后,加入自定义参数
HttpContext.Current.Application.Set(isoKey, "PUR_PurchaseOrder|100001");
}
}
catch (Exception ex)
{
ret.ResultType = StartAppResultType.Failure;
ret.FailureCallBackType = StartAppResultCallBackType.Message;
ret.Message = ex.Message + "\r\n" + ex.StackTrace;
}
}
}
/// <summary>
/// 实现基类访问校验接口,如果没有该业务需求,可以不实现,使用基类的接口即可。
/// </summary>
public override StartAppResult Verify(string ipmac)
{
randomFlag++;
StartAppResult ret = null;
var data = JSONObject.Parse(ipmac);
var flag = randomFlag % 5;
switch (flag)
{
case 4:
// 提示后退出并重定向到指定页面
ret = GetResult(StartAppResultCallBackType.ExitMsg_Redirect, ipmac);
break;
case 3:
// 退出并重定向到指定页面(不做任何提示)
ret = GetResult(StartAppResultCallBackType.Exit_Redirect, ipmac);
break;
case 2:
// 退出并提示
ret = GetResult(StartAppResultCallBackType.ExitMsg, ipmac);
break;
case 1:
// 提示信息(不退出程序)
ret = GetResult(StartAppResultCallBackType.Message, ipmac);
break;
default:
ret = new StartAppResult()
{
ResultType = StartAppResultType.Success
};
randomFlag = 0;
break;
}
return ret;
}
/// <summary>
/// 构造返回结果
/// </summary>
/// <param name="fcbType">返回回调类型</param>
/// <returns></returns>
private static StartAppResult GetResult(StartAppResultCallBackType fcbType, string attMsg)
{
var ret = new StartAppResult()
{
FailureCallBackType = fcbType,
// 成功Sucess:正常登录
ResultType = StartAppResultType.Failure,
// 外部网址:RedirectUrl = "http://www.kingdee.com"
// 空白页:RedirectUrl = "about:blank"
// 针对WPF端,如果需要打开指定应用程序,可以这么返回 CMD://notepad.exe;
// 失败后重定向URL,可以是相对路径也可以是绝对路径,相对路径已K3Cloud为基地址,例如:http://xxxx/k3cloud/
RedirectUrl = "help/clienthelp.htm",
ExitType = StartAppResultExitType.Shutdown,
Title = ResManager.LoadKDString("温馨提示", "0022883030028663", SubSystemType.BOS),
Message = ResManager.LoadKDString("网站正在维护中 ... ...", "0022883030028642", SubSystemType.BOS) +
"\r\n" + attMsg
};
return ret;
}
}
}
// 增加下面函数,实现简单通行证,就可以不需要知道ERP系统的密码了。
/// <summary>
/// 实现登录校验的例子的私有函数::简单通行证方式
/// 简单通行证方式:ud=|dbid|username|appid|signeddata|timestamp[|lcid], 演示可以是明文,正式上线最好进行编码加密。
/// </summary>
/// <param name="context"></param>
/// <param name="ret"></param>
/// <param name="data"></param>
private void VerifyLoginBySimplePassport(KDServiceContext context, StartAppResult ret, JSONObject data)
{
//实现自动登录功能
var customArgs = data.GetValue<string>("customArgs", "");//ud用户定义参数将会传递到服务端插件的customArgs中
var isAutoLogin = !string.IsNullOrWhiteSpace(customArgs);
var userInfoStr = customArgs;
if (!userInfoStr.StartsWith("|"))
{
// 如果参数没有使用Base64编码,这段解码逻辑可以去掉
var arg = userInfoStr.Base64ToBytes();
userInfoStr = UTF8Encoding.Default.GetString(arg); //KDHttpUtility.RequestUtility.HtmlDecode(userInfoStr);
}
var userInfo = userInfoStr.Split('|');
//协议为6个参数|dbid|username|appid|signeddata|timestamp[|lcid]
if (isAutoLogin && userInfo.Length < 6)
{
isAutoLogin = false;
ret.ResultType = StartAppResultType.Failure;
ret.Message = "自动登陆参数格式错误!";
}
if (isAutoLogin && context != null)
{
try
{
//包括用户名、密码和数据库id,已经对应的语种id
var dbid = userInfo[1];
var userName = userInfo[2];
var appId = userInfo[3];
var signedData = userInfo[4];
long timestamp = 0; long.TryParse( userInfo[5],out timestamp);
var lcid = 2052;
if (userInfo.Length > 6 && !int.TryParse(userInfo[6], out lcid))
{
lcid = 2052;
}
var password = "888888";
var pluginLoginInfo = new PluginLoginInfo()
{
PasswordIsEncrypted = false,
Password = password,//任意字符串,但不能为空
// 此处是强制使用【简单证书】登陆
AuthenticateType = AuthenticationType.SimplePassportAuthentication,
ClientInfo = GetClientInfo(context, data),
AcctID = dbid, //AcctID如果传递,将不再通过AcctName查找AcctID了。
//LoginType = PluginLoginType.Common, //默认为用户名密码的通用登录方式。
//AcctName = dbid,//AcctID如果不传递,将通过AcctName查找AcctID。
Username = userName,
Lcid = lcid,
AppId = appId,
Timestamp = timestamp,
////appSecret 公钥字符串,来自administrator管理中第三方系统登陆授权功能分配的加密钥匙
//Kingdee.BOS.Util.SHA1Util.GetSignature(
// new string[] {
// dbId, usserName, appId, appSecret, timestamp.ToString()
// });
SignedData = signedData
};
//调用基类工具方法实现登录验证,并返回ret.UserToken,给客户端实现自动登录
DoLoginCore(context, ret, pluginLoginInfo);
//登录后的参数传入,具体使用参考【自动打开单据】
if (!string.IsNullOrWhiteSpace(ret.SessionId))
{
var isoKey = ret.SessionId "_AutoLoginArgs";
//登陆成功后,加入自定义参数
HttpContext.Current.Application.Set(isoKey, "PUR_PurchaseOrder|100001");
}
}
catch (Exception ex)
{
ret.ResultType = StartAppResultType.Failure;
ret.FailureCallBackType = StartAppResultCallBackType.Message;
ret.Message = ex.Message "\r\n" ex.StackTrace;
}
}
}
7、协议内容解析(演示例子中的参数协议,具体实际应用可以自定义,但基本参数不能缺少):
参数格式:ud=|dbid|username|appid|signeddata|timestamp[|lcid],其中ud为参数名;协议格式:|dbid|username|appid|signeddata|timestamp[|lcid]
dbid:数据中心的ID;username:用户名称;
appid:应用程序ID,通过Administrator登录数据中心后,在【系统管理】分类的【第三方系统登录授权】功能里面进行新增维护;
signeddata:数据签名串,通过公钥和用户数据进行运算得到,在后面有详细说明;
// signeddata公钥字符串,来自administrator管理中【第三方系统登陆授权】功能分配的加密钥匙
// 参数中secret为【第三方系统登陆授权】中自动分配的【应用密钥】
var signedData = Kingdee.BOS.Util.SHA1Util.GetSignature(
new string[] {
dbId, userName, appId, secret, timeStamp.ToString()
});
timestamp:登录时间戳(Unix时间戳,定义为从格林威治时间1970年01月01日00时00分00秒起至现在的总秒数);
lcid(可选):语言ID,中文2052(默认),英文1033,繁体3076
.
ps: 如果使用的是其他语言,比如java等,可以参照下面算法生成数据签名串[signedData]:
/// <summary>
/// SHA1签名
/// </summary>
/// <param name="arr"></param>
/// <returns></returns>
public static string GetSignature(string[] arr)
{
//1. 将数组进行排序
//2. 将数组拼接成一个字符串进行sha1加密
arr = arr.OrderBy(z => z).ToArray();
var arrString = string.Join("", arr);
var sha1 = SHA1.Create();
var sha1Arr = sha1.ComputeHash(Encoding.UTF8.GetBytes(arrString));
StringBuilder enText = new StringBuilder();
foreach (var b in sha1Arr)
{
enText.AppendFormat("{0:x2}", b);
}
return enText.ToString();
}
8,管理第三方系统登陆授权。
1、Administrator登陆K/3 Cloud数据中心;
2、打开【系统管理- 第三方系统登陆授权】功能;
3、新增或修改对应外部接入系统的密钥和APPID;如下图:
答疑
1、管理员账户Administrator无法自动登录系统?
答:管理员administrator帐号不允许自动登录。只有业务账户才允许自动登录系统。
2、为何生成的链接无法登录系统?
【答】:
(1)检查有没有部署插件,启用插件;
(2)确认是6.0版本,并安装了最新功能补丁;
(3)修改完Common.config后,需要重启IIS,启动命令为IISRESET;
3、如何获取数据库的ID?
【答】:
(1)由管理员查询管理中心数据库;
(2)通过“管理第三方系统登陆授权”测试功能“生成Silverlight入口链接”功能获取;
4、数据库ID会变吗?
【答】:在数据库注册到管理中心后产生一个固定ID。一旦重新注册该数据库,就会为数据库重新分配新的ID。
5、SingedData生成过程中数据的排序方式是什么?
【答】:字符串正向排序规则。
6、用户名或者其他参数存在中文或其他Unicode字符怎么办?
【答】:简单的方法就是针对生成好的ud参数整体进行基于Utf-8的Base64的编码,然后在启动校验插件中进行base64解码。(在K/3Cloud的出厂Demo插件已经默认支持Base64解码了。),
例如: 【http://xxxx/k3cloud/html5/index.aspx?ud=|563073d2b90b4f|Developer|hr|cf6e86aed7d40b1534cf9a1ea557a073626a30ac|1458613257|2052】
中的ud参数部分进行整体的基于Utf-8的Base64编码成
【http://xxxx/k3cloud/html5/index.aspx?ud=fDU2MzA3M2QyYjkwYjRmfERldmVsb3BlcnxocnxjZjZlODZhZWQ3ZDQwYjE1MzRjZjlhMWVhNTU3YTA3MzYyNmEzMGFjfDE0NTg2MTMyNTd8MjA1Mg==】
即可。
7、仅替换某些DLL,不执行补丁包升级是否可以?
【答】:不可以,必须以安装正式补丁方式进行升级。不允许采用手工替换组件的方式。
8、登陆后显示具体指定的表单页面?
【答】:可以的,需要写二开动作
A,在自动登录插件DoCoreLogin执行成功后,可以把表单参数写入Session;这些参数都可以组合到自定参数ud里面
//隔离参数键值
var isoKey = ret.SessionId "_AutoLoginArgs";
//登陆成功后,加入自定义参数
HttpContext.Current.Application.Set(isoKey, "ConsoleSimpleModeXXXXX");
B,在主控表单BOS_MainConsoleSutra二开插件,判断上面的ConsoleSimpleMode参数后,仅加载上面参数的StartFormId参数对应表单即可,其他控件Panel面板全部隐藏。
//隔离参数键值
var isoKey = this.Context.SessionId "_AutoLoginArgs";
//获取客制化参数
var cargs = HttpContext.Current.Application.Get(isoKey);
//清理Session
HttpContext.Current.Application.Remove(isoKey);
具体例子参考:https://vip.kingdee.com/article/37644
本文转载自:金蝶社区
作者:yaojunsong@op
原文链接:https://vip.kingdee.com/article/37406
推荐阅读