NCC单点登录常见问题

831 阅读14分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第12天,点击查看活动详情

1. 单点登陆后白屏或仍需要密码登陆

解决方法:
可以根据以下情况检查:

(1) 如果是2005、2105版本的,需要打单点登陆白屏免密码输入的补丁。

(2) 如果打了补丁,单点登陆路径里面不要用localhost,应该用127.0.0.1,有些小伙伴把补丁打到home里面了,注意本地开发时hotwebs\nccloud\WEB-INF\classes这里应该是空的,建议可以把类放到自己工程里面。

(3) 检查一下用户状态:看用户能否通过登陆界面正常登陆,若用户状态不正常可能出问题。

(4) 如果是2111及以后版本多账套的话把busicentercode即账套编码传递过去。

2. 单点登陆提示找不到页面:抱歉,您请求的页面出错啦

image.png  解决方法:  拼接的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..*

image.png

如果已经配置了白名单依然报错,尝试删除配置文件的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

image.png  解决方法:
问题原因是跨域拦截了。如果打开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,多个之间用英文逗号分割。参考下面截图: 

image.png

配置示例如下:

....//省略部分
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 

image.png

7. 如何通过单点登陆打开某一个节点

7.1 跳转普通的单据的节点

7.1.1 需要先确定要跳转到节点的url

这里以人员节点为例,打开【人员】节点后,可以看到浏览器地址栏有一段page=10140PSN-10140PSN_main

按F12 打开浏览器控制台找到【应用或Application】->【会话存储空间或session storage】->右边密钥找到NCCAPPURL,下面可能会有多个key和url,我们只需要找到需要页面的10140PSN-10140PSN_main即可。

image.png 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=人员信息

image.png

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

可参考下图方式:

image.png

取到的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();