持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第12天,点击查看活动详情
1. 单点登陆后白屏或仍需要密码登陆
解决方法:
可以根据以下情况检查:
(1) 如果是2005、2105版本的,需要打单点登陆白屏免密码输入的补丁。
(2) 如果打了补丁,单点登陆路径里面不要用localhost,应该用127.0.0.1,有些小伙伴把补丁打到home里面了,注意本地开发时hotwebs\nccloud\WEB-INF\classes这里应该是空的,建议可以把类放到自己工程里面。
(3) 检查一下用户状态:看用户能否通过登陆界面正常登陆,若用户状态不正常可能出问题。
(4) 如果是2111及以后版本多账套的话把busicentercode即账套编码传递过去。
2. 单点登陆提示找不到页面:抱歉,您请求的页面出错啦
解决方法: 拼接的url错误,检查如果是 http://ip:port/uap/... 地址应该为http://ip:port/nccloud/resources/uap/...
3. 单点登录时服务返回401
报错信息: java.io.IOException: Server returned HTTP response code: 401 for URL: http://192.168.48.133:9903/service/genThirdPartyAccessToken at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1894) at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1492)
解决方法: 原因是home\ierp\sf\nccssoConfig.xml文件中未配置白名单。如下图所示,在listParam里面配置允许访问的客户端ip,注意是客户端ip,不是第三方服务器ip。可以使用号进行匹配,例如192.168..*
如果已经配置了白名单依然报错,尝试删除配置文件的listParam或者把ip地址配置成*...* 即不校验ip地址。尝试删除sm_oauth_security对应表的client_security。
必要时可以到ncc.sso.bs.DefaultNCCSSOAuthenticator里面跟一下代码。
4. 单点登陆提示“未找到第三方系统”
解决方法:
这个问题在于token的多次使用或token错误。单点登陆的token只能使用一次,用过后就失效了,这点需要注意。
打开类ThirdPartyLoginVerfiyAction,可以找到如下代码:
INCCSSOService nccSSOService = ServiceLocator.find(INCCSSOService.class);
NCCSSORegInfo regInfo = nccSSOService.fetchSSORegInfo(access_token);
if (regInfo == null) {
result.put(IUserLoginHandler.RSL_Code, IUserLoginHandler.TOKEN_WRONG);
result.put(IUserLoginHandler.RSL_MSG, "未找到第三方系统");
return result;
}
关于查找token的代码,如果找到源头可以看到,token信息会存在map中,获取后就remove掉了:
public NCCSSORegInfo fetchSSORegInfo(String access_token) {
return regInfoMap.remove(access_token);
}
5. 单点登录用拼好的url地址提示“访问xxxxd 的请求遭到拒绝” 您未获授权,无法查看此网页。 HTTP ERROR 403
解决方法:
问题原因是跨域拦截了。如果打开home\hotwebs\nccloud\WEB-INF\web.xml可以看到有很多的filter配置,也就是说每当有一次向后台的NCC的服务请求时,会按照web.xml里面配置的filter顺序依次进行过滤,只有经过滤筛选符合NCC安全要求的才能进入系统,调用各个业务领域的action代码。包括我们文档前面提到的AppActionAuthorizeFilter也是在这里面配置的。
在filter中有一个nccloud.framework.core.filter.CorsFilter是跨域请求处理的。类里面可以看到它通过String referer = request.getHeader("referer"); 判断服务请求方。从类里面我们也可以找到对应的配置文件为home\hotwebs\nccloud\WEB-INF\corsfilter.properties ,把配置文件里的referer按逗号分割成数组,只要请求的路径部分包含配置文件里的路径即可。当然如果用了域名应该配置域名而不是ip,多个之间用英文逗号分割。参考下面截图:
配置示例如下:
....//省略部分
referer=http://IP地址1:port,https://IP地址2:port,http://域名1,https://域名2:port
6. 单点登陆token的失效时间是多少?能否配置?
NCC轻量端在home\ierp\sf\nccssoConfig.xml里面配置regTimeout即可,重量端单点token失效时间在home\ierp\sf\ssoConfig.xml的regTimeout。 配置文件初始化的类:ncc.sso.bs.NCCSSOConfigParser
7. 如何通过单点登陆打开某一个节点
7.1 跳转普通的单据的节点
7.1.1 需要先确定要跳转到节点的url
这里以人员节点为例,打开【人员】节点后,可以看到浏览器地址栏有一段page=10140PSN-10140PSN_main
按F12 打开浏览器控制台找到【应用或Application】->【会话存储空间或session storage】->右边密钥找到NCCAPPURL,下面可能会有多个key和url,我们只需要找到需要页面的10140PSN-10140PSN_main即可。
ifr=%252Fuapbd%252Fpsninfo%252Fpsndoc%252Fmain%252Findex.html&c=10140PSN&p=10140PSN_main&ar=0001Z010000000004F8W&n=%25E4%25BA%25BA%25E5%2591%2598&b1=%25E5%258A%25A8%25E6%2580%2581%25E5%25BB%25BA%25E6%25A8%25A1%25E5%25B9%25B3%25E5%258F%25B0&b2=%25E5%259F%25BA%25E7%25A1%2580%25E6%2595%25B0%25E6%258D%25AE&b3=%25E4%25BA%25BA%25E5%2591%2598%25E4%25BF%25A1%25E6%2581%25AF 这个url里面都包含了哪些信息? 我们对这段url进行两次url的解码后可以得到下面的这段字符串 ifr=/uapbd/psninfo/psndoc/main/index.html&c=10140PSN&p=10140PSN_main&ar=0001Z010000000004F8W&n=人员&b1=动态建模平台&b2=基础数据&b3=人员信息
ifr:页面的路径,c:应用编码,p:页面编码,ar:appregister缩写,应用主键,n:人员,b1:动态建模平台,b2:基础数据,b3:人员信息 这些都是平台在确定小应用、打开小应用时所需要的信息。
7.1.2 拼接url
我们在获取tocken后需要拼接跳转的路径, 将上图中的复制出来的url替换到单点登录中redirect_uri即可,注意替换的是page=…这段 ,要保留page之前的所有 包括ifr?
示例代码:
/**
* 单点登陆到单据详情,单据节点打开对应的单据
* 这里只是拼了单点登陆的url,前端节点需要this.props.getUrlParam('id')获取单据id
* 将id查询出来后对单据进行展示
* @param token 单点登陆获取到的token
* */
private static String getBillDetailRedirectUrl(String token) {
String billId="1001A110000000034ULM";//单据id
String appcode="10140CUSTPF";//单据应用编码
String pagecode="10140CUSTPF_custpfcard";//单据页面编码
StringBuffer redirectUrl = new StringBuffer();
redirectUrl.append("http://127.0.0.1:8080/nccloud/resources/workbench/public/common/main/index.html#/ifr?ifr=");
redirectUrl.append( encodeURI(encodeURI("/nccloud/resources/uapbd/customer/custapply/main/index.html#/card?id="+billId+"&status=browse&scene=linksce")));//这里拼的路径是节点打开后iframe 标签src的路径。
redirectUrl.append("&b1="+encodeURI(encodeURI("动态建模平台")));//b1,面包屑导航,首页后第一级
redirectUrl.append("&b2="+encodeURI(encodeURI("基础数据")));//b2,面包屑导航,第二级
redirectUrl.append("&b3="+encodeURI(encodeURI("客户信息")));//b3,面包屑导航,第三级
redirectUrl.append("&n="+encodeURI(encodeURI("客户申请单")));//n,菜单名字
redirectUrl.append("&c="+appcode);//应用编码
redirectUrl.append("&p="+pagecode);//页面编码
redirectUrl.append("&ar=0001Z010000000004OF5");//应用注册对应的小应用主键,这里示例代码用的客户申请单
String fullUrl ="http://127.0.0.1:8080/nccloud/resources/uap/rbac/thirdpartylogin/main/index.html?accesstoken="+token+"&redirect_uri="+redirectUrl.toString();
return fullUrl;
}
7.2 跳转到审批详情
7.2.1 与前面类似,先确定审批中心详情界面的url
可参考下图方式:
取到的url示例:
ifr=%252Fnccloud%252Fresources%252Fuap%252Fmsgcenter%252Fmessage%252FapproveDetail%252Findex.html%2523%252F%253Fpk_message%253D1001Z31000000005X1SE%2526pageMsgType%253Dworklist%2526appcode%253D10160501%2526pagecode%253D10160501APPROVE%2526c%253D10143502P%2526p%253D10143502P_TreeFlowP%2526n%253D%25E6%25A0%2591%25E5%258D%25A1%25E6%25B5%258B%25E8%25AF%2595%25E5%25AE%25A1%25E6%2589%25B9%2526checknote%253D&p=10160501APPROVE&n=%25E5%25AE%25A1%25E6%2589%25B9%25E4%25B8%25AD%25E5%25BF%2583&b1=%25E5%258A%25A8%25E6%2580%2581%25E5%25BB%25BA%25E6%25A8%25A1%25E5%25B9%25B3%25E5%258F%25B0&b2=%25E6%25B5%2581%25E7%25A8%258B%25E7%25AE%25A1%25E7%2590%2586&b3=%25E5%25AE%25A1%25E6%2589%25B9%25E7%25AE%25A1%25E7%2590%2586&c=10160501&ar=0001Z0100000000042SD
与前面类似通过url进行两次解析可以得到下面字符串:
ifr=/nccloud/resources/uap/msgcenter/message/approveDetail/index.html#/?pk_message=1001Z31000000005X1SE&pageMsgType=worklist&appcode=10160501&pagecode=10160501APPROVE&c=10143502P&p=10143502P_TreeFlowP&n=树卡测试审批&checknote=&p=10160501APPROVE&n=审批中心&b1=动态建模平台&b2=流程管理&b3=审批管理&c=10160501&ar=0001Z0100000000042SD
7.2.2 获取到上图的url后 把他拷贝出来,ifr=...就是最后我们要的url
这里面需要传几个重要的参数(pk_message、pageMsgType、appcode、pagecode、c、p)具体含义和获取方式如下:
(1) pk_message:消息中心的主键值(sm_msg_approve),具体获取方式可直接调用接口:nccloud.message.itf.IApproveMessageQueryService中 queryNCMessages(JSONObject paramJSONObject) 这个接口,具体的参数意义可找到对应的实现类(nccloud.message.bs.MessageQueryServiceImpl)进行查看
(2) pageMsgType:对应消息中心的消息类型(sm_msg_approve中message_type字段),获取方式同(1),这两个字段调用一次接口都能直接获取
(3) appcode:审批中心详情模板 应用编码,此处从上图中复制出来的Url已自带,不必修改
(4) pagecode:审批中心详情模板 页面编码,此处从上图中复制出来的Url已自带,不必修改
(5) c:对应你当前单据的审批详情 应用编码,此处从上图中复制出来的Url已自带,不必修改
(6) p:对应你当前单据的审批详情 页面编码,此处从上图中复制出来的Url已自带,不必修改
7.2.3 从图中复制出来的url可能还有一些其他的信息
根据自己需求(除了第2点的几个参数外,其余可删除--若页面异常则需要加上(目前没发现))。当通过第2点获取相关参数值后,把对应的值替换到上图获取的url对应位置即可组装成最终的URL。 随后直接替换单点登录中redirect_uri(替换注意事项同第一点中的替换一致) 即可实现需求。
示例代码:
/**
* 单点登录到审批详情页面
* @param token 单点登陆获取到的token
* */
private static String getMsgCenterRedirectUrl(String token) {
String pk_message = "1001ZZ1000000009GKZZ";//消息中心的主键值(sm_msg_approve)
String appcode="10140CUSTPFA";//单据对应的审批联查应用编码,示例代码使用的是客户申请单应用编码
String pagecode="10140CUSTPF_custpfcard";//单据对应的审批联查的页面编码,示例代码使用的是客户申请单页面编码
String appname="客户申请单审批";//单据对应的审批联查菜单名字
StringBuffer redirectUrl = new StringBuffer();
redirectUrl.append("http://127.0.0.1:8080/nccloud/resources/workbench/public/common/main/index.html#/ifr?ifr=");
StringBuffer billUrl = new StringBuffer();
//审批中心页面路径,固定值
billUrl.append("/nccloud/resources/uap/msgcenter/message/approveDetail/index.html#/?");
billUrl.append("pk_message=" + pk_message );//pk_message,消息中心的主键值
//pageMsgType:对应消息中心的消息类型(sm_msg_approve中message_type字段)
//appcode:审批中心应用编码,固定值10160501
//pagecode:审批中心页面编码,固定值10160501APPROVE
billUrl.append("&pageMsgType=worklist&appcode=10160501&pagecode=10160501APPROVE&");
//要审批的单据信息,c:单据审批应用编码,p:单据审批页面模板编码,n:单据审批对应菜单名
billUrl.append("c=" + appcode + "&p=" + pagecode + "&n=" + appname);
billUrl.append("&checknote=");
//审批中心的参数,url2不要一起加密
StringBuffer param = new StringBuffer();
param.append("&p=10160501APPROVE&n="+encodeURI(encodeURI("审批中心")));
param.append("&b1="+encodeURI(encodeURI("动态建模平台")));//b1,面包屑导航,固定值
param.append("&b2="+encodeURI(encodeURI("流程管理")));//b2,面包屑导航,固定值
param.append("&b3="+encodeURI(encodeURI("审批管理")));//b3,面包屑导航,固定值
param.append("&c=10160501");//审批中心应用编码,固定值
param.append("&ar=0001Z0100000000042SD");//审批中心应用的主键(sm_appregister),这里是固定值
//需要打开的界面的参数,需要一起加密
redirectUrl.append(encodeURI(encodeURI(billUrl.toString()))+param.toString());
String fullUrl ="http://127.0.0.1:8080/nccloud/resources/uap/rbac/thirdpartylogin/main/index.html?accesstoken="+token+"&redirect_uri="+redirectUrl.toString();
return fullUrl;
}
7.3 完整示例代码
package nc.demo.third;
import java.io.DataOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.net.URLConnection;
import org.apache.commons.codec.binary.Base64;
import nccloud.security.impl.SignatureTookKit;
public class PostThirdPartyAccessTokenTest {
/** main方法 */
public static void main(String[] args) throws Exception {
String dsname = "design";//数据源的编码,本地开发为design
String usercode = "ncc1";//用户的编码,需要用户可以正常登陆
String client_id = "TestThird";//第三方系统id,
String client_security = "e3389149e73c02be45fffe27c4fc476bb5086dc10c1a0c399dcde08339a85fea";//第三方秘钥
String busicentercode = "develop";//账套编码,本地开发时为develop
String url="http://127.0.0.1:8080/service/genThirdPartyAccessToken";
String security = genKey(usercode,usercode + client_security + (System.currentTimeMillis() + "").substring(0, 6));
String write="type=type_security&dsname="+dsname+"&usercode="+usercode+"&client_id="+client_id+"&security="+security+"&busicentercode="+busicentercode;
write=write.replaceAll("\\+","%2B");//避免出现特殊符号问题。
//获取token
String token = getToken(url,write);
if("".equals(token)) {
System.out.println("获取token失败");
return;
}
System.out.println("获取的token为:"+token);
System.out.println("单点登陆路径为:"+getBaseRedirectUrl(token));
System.out.println("审批详情单点登陆路径为:"+getMsgCenterRedirectUrl(token));
System.out.println("单点登陆到单据详情:"+getBillDetailRedirectUrl(token));
}
/**
* 单点登录到首页:http://127.0.0.1:8080/nccloud
* @param token 单点登录获取到的token
* */
private static String getBaseRedirectUrl(String token) {
//单点成功后的跳转路径
String redirect_uri = "http://127.0.0.1:8080/nccloud";
//单点登陆时的全路径,可以直接粘贴到浏览器进行访问。
String fullUrl="http://127.0.0.1:8080/nccloud/resources/uap/rbac/thirdpartylogin/main/index.html?accesstoken="+token+"&redirect_uri="+redirect_uri;
return fullUrl;
}
/**
* 单点登录到审批详情页面
* @param token 单点登陆获取到的token
* */
private static String getMsgCenterRedirectUrl(String token) {
String pk_message = "1001ZZ1000000009GKZZ";//消息中心的主键值(sm_msg_approve)
String appcode="10140CUSTPFA";//单据对应的审批联查应用编码,示例代码使用的是客户申请单应用编码
String pagecode="10140CUSTPF_custpfcard";//单据对应的审批联查的页面编码,示例代码使用的是客户申请单页面编码
String appname="客户申请单审批";//单据对应的审批联查菜单名字
StringBuffer redirectUrl = new StringBuffer();
redirectUrl.append("http://127.0.0.1:8080/nccloud/resources/workbench/public/common/main/index.html#/ifr?ifr=");
StringBuffer billUrl = new StringBuffer();
//审批中心页面路径,固定值
billUrl.append("/nccloud/resources/uap/msgcenter/message/approveDetail/index.html#/?");
billUrl.append("pk_message=" + pk_message );//pk_message,消息中心的主键值
//pageMsgType:对应消息中心的消息类型(sm_msg_approve中message_type字段)
//appcode:审批中心应用编码,固定值10160501
//pagecode:审批中心页面编码,固定值10160501APPROVE
billUrl.append("&pageMsgType=worklist&appcode=10160501&pagecode=10160501APPROVE&");
//要审批的单据信息,c:单据审批应用编码,p:单据审批页面模板编码,n:单据审批对应菜单名
billUrl.append("c=" + appcode + "&p=" + pagecode + "&n=" + appname);
billUrl.append("&checknote=");
//审批中心的参数,url2不要一起加密
StringBuffer param = new StringBuffer();
param.append("&p=10160501APPROVE&n="+encodeURI(encodeURI("审批中心")));
param.append("&b1="+encodeURI(encodeURI("动态建模平台")));//b1,面包屑导航,固定值
param.append("&b2="+encodeURI(encodeURI("流程管理")));//b2,面包屑导航,固定值
param.append("&b3="+encodeURI(encodeURI("审批管理")));//b3,面包屑导航,固定值
param.append("&c=10160501");//审批中心应用编码,固定值
param.append("&ar=0001Z0100000000042SD");//审批中心应用的主键(sm_appregister),这里是固定值
//需要打开的界面的参数,需要一起加密
redirectUrl.append(encodeURI(encodeURI(billUrl.toString()))+param.toString());
String fullUrl ="http://127.0.0.1:8080/nccloud/resources/uap/rbac/thirdpartylogin/main/index.html?accesstoken="+token+"&redirect_uri="+redirectUrl.toString();
return fullUrl;
}
/**
* 单点登陆到单据详情,单据节点打开对应的单据
* 这里只是拼了单点登陆的url,前端节点需要this.props.getUrlParam('id')获取单据id
* 将id查询出来后对单据进行展示
* @param token 单点登陆获取到的token
* */
private static String getBillDetailRedirectUrl(String token) {
String billId="1001A110000000034ULM";//单据id
String appcode="10140CUSTPF";//单据应用编码
String pagecode="10140CUSTPF_custpfcard";//单据页面编码
StringBuffer redirectUrl = new StringBuffer();
redirectUrl.append("http://127.0.0.1:8080/nccloud/resources/workbench/public/common/main/index.html#/ifr?ifr=");
redirectUrl.append( encodeURI(encodeURI("/nccloud/resources/uapbd/customer/custapply/main/index.html#/card?id="+billId+"&status=browse&scene=linksce")));//这里拼的路径是节点打开后iframe 标签src的路径。
redirectUrl.append("&b1="+encodeURI(encodeURI("动态建模平台")));//b1,面包屑导航,首页后第一级
redirectUrl.append("&b2="+encodeURI(encodeURI("基础数据")));//b2,面包屑导航,第二级
redirectUrl.append("&b3="+encodeURI(encodeURI("客户信息")));//b3,面包屑导航,第三级
redirectUrl.append("&n="+encodeURI(encodeURI("客户申请单")));//n,菜单名字
redirectUrl.append("&c="+appcode);//应用编码
redirectUrl.append("&p="+pagecode);//页面编码
redirectUrl.append("&ar=0001Z010000000004OF5");//应用注册对应的小应用主键,这里示例代码用的客户申请单
String fullUrl ="http://127.0.0.1:8080/nccloud/resources/uap/rbac/thirdpartylogin/main/index.html?accesstoken="+token+"&redirect_uri="+redirectUrl.toString();
return fullUrl;
}
/**调用接口获取token
*
* @param url 要调用的接口
* @param write 需要写出的数据
* @return token值
* */
private static String getToken(String url,String write) {
OutputStream outStream=null;
DataOutputStream dataOutput=null;
InputStream inStream = null;
String ret="";
try {
URL preUrl = new URL(url);
URLConnection con = preUrl.openConnection();
//设置为true,后面可以调用getOutputStream并传输数据
con.setDoOutput(true);
//不使用http缓存
con.setUseCaches(false);
//设置http请求头
con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
con.setRequestProperty("Content-Length", "10000" );
HttpURLConnection httpCon = (HttpURLConnection) con;
//这里使用post方式调用接口
httpCon.setRequestMethod("POST");
//获取输出流
outStream = httpCon.getOutputStream();
dataOutput = new DataOutputStream(outStream);
//接口需要的参数作为字节序列写入输出流
dataOutput.writeBytes(write);
dataOutput.flush();
//获取输入流
inStream = httpCon.getInputStream();
//读取服务返回的数据
int ch;
while ((ch = inStream.read()) != -1) {
ret += String.valueOf((char) ch);
}
} catch (Exception e) {
System.out.println(e);
} finally {
//关闭服务输入流和输出流
if (dataOutput != null) {
try {
dataOutput.close();
} catch (Exception e2) {
e2.printStackTrace();
}
}
if (outStream != null) {
try {
outStream.close();
} catch (Exception e2) {
e2.printStackTrace();
}
}
if (inStream != null) {
try {
inStream.close();
} catch (Exception e2) {
e2.printStackTrace();
}
}
}
//返回读取的token
return ret;
}
private static String genKey(String userid,String key) throws Exception{
return new Base64().encodeToString(SignatureTookKit.digestSign(userid.getBytes(), key.getBytes()));
}
private static String encodeURI(String str) {
try {
return URLEncoder.encode(str, "UTF-8");
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return "";
}
}
8. 单点登陆到审批详情后,点返回按钮不起作用,无法返回到列表
手动打开节点看看是否正常,如果手动打开正常但是单点登陆进去不正常说明单点登陆时拼接url错误,检查路径。
9. 无法获取数据源
如果需要连接数据库,对数据库进行操作,则必须设置NCC环境变量
tring UserDataSource = PropertiesUtils.getInstance().getStrValue("UserDataSource");
String GroupId = PropertiesUtils.getInstance().getStrValue("GroupId");
String BizCenterCode = PropertiesUtils.getInstance().getStrValue("BizCenterCode");
InvocationInfoProxy.getInstance().setGroupId(GroupId);
InvocationInfoProxy.getInstance().setUserDataSource(UserDataSource);
InvocationInfoProxy.getInstance().setBizCenterCode(BizCenterCode);
10. 在本地开发环境测试通过,但在服务器上报错token is null
解决方法:
在服务器上需在代码里设置下token
ISecurityTokenCallback sc = NCLocator.getInstance().lookup(ISecurityTokenCallback.class);
sc.token("WSSystem".getBytes(), "WSSystem".getBytes());
InvocationInfoProxy poxy = InvocationInfoProxy.getInstance();