写在前面:
最近项目当中涉及到了和浙里办(浙江政务服务网)的登录对接。业务流程其实也挺简单,一句话变可概括:我们系统平台上接收来自对方系统平台上发送过来的企业诉求,放在我们平台上进行流程处理,将处理结果推送回对方平台。由于对方系统的用户账号体系是浙江政务服务网的账号体系。需求里还加了能通过我们平台免登直接进入到对方系统平台。
前面的业务流程,双方约定好具体业务字段参数,开链接ip白名单便可以实现。主要是后面和浙江政务服务网的法人登录对接跳转到对方平台上,并实现已登录状态。这就涉及到了和政务服务网的对接。整个流程之前没接触过,包括和对方公司开发人员对接沟通交流,全流程下来,收获挺大的。趁此机会,打个卡记录一下!
第一步
我方项目经理向这浙江政务服务网提出申请。申请通过,政务服务网与我方进行联系发送应用接入编号(projectId)和密钥(projectSecret)及Demo并组建钉钉群组以便后续技术交流。(在后面浙江政务服务网发给我们的对接文档中有说明。)
第二步
2.1单点登录流程
①用户登录浙江政务服务网后点击办事事项链接;
②政务服务网重定向至法人统一认证系统;
③统一身份认证系统检查是否存在当前用户的会话,如果存在,获取其ssotoken;若不存在,创建会话并生成ssotoken;
④统一身份认证系统重定向至部门办事系统[法人信息回调地址]并推送ssotoken和ssoticket;
⑤部门办事系统调用“[验证令牌并获取用户的登录信息]”接口,采用Http接口与法人统一身份认证系统交互,传入ssotoken和projectId获取用户登录信息。法人统一身份认证系统验证ssotoken有效性,验证通过后返回用户登录信息;
⑥成功获取法人信息,完成登录,跳转到具体办事页面。
2.2单点登录地址
2.2.1功能说明:
应用提供单位可根据需求在具体业务环节发起单点登录,通过单点登录规则拼接授权页面链接引导用户打开授权。
2.2.2单点登录地址规则:
单点登录地址规则如下:
正式环境:
https://esso./opensso/spsaehandler/metaAlias/sp?spappurl=回调地址
测试环境:
http://essotest./opensso/spsaehandler/metaAlias/sp?spappurl=回调地址
2.2.3请求方式:
单点登录地址请求方式:Get
2.2.4返回参数:
spappurl参数说明:
附注:
如果用户单点登录后希望展示具体的办事页面地址,可以在spappUrl回调地址中含带goto=“具体办事页面地址”,否则用户单点登录后跳转到首页。注意需要url编码。
浙江政务服务网单点登录参考链接(假设回调地址为http://localhost:8080/ssocheck.jsp,登录后希望跳转到页面):
正式环境:
https://esso./opensso/spsaehandler/metaAlias/sp?spappurl=http://localhost:8080/ssocheck.jsp?goto=
测试环境:
http://essotest./opensso/spsaehandler/metaAlias/sp?spappurl=http://localhost:8080/ssocheck.jsp?goto=
2.3回调地址
2.3.1功能说明:
该地址是实现业务系统开发[回调地址]接收SSO传递过来的令牌及票据,以便之后实现页面系统登录功能。
2.3.2回调地址Url:
业务系统自定义
2.3.3回调方式:
POST
2.3.4回调参数:
Request参数列表:
获取令牌及票据的方法示例:
// 获取ssotoken令牌
request.getParameter("ssotoken");
// 获取ssoticket票据
request.getParameter("ssoticket")
附注:
ssotoken是用于获取登录信息的临时令牌,有效时间为1分钟。票据是在跨session,跨域的应用系统接入时候用到,如通过https://esso./opensso/spsaehandler/metaAlias/sp?spappurl=应用系统法人登录入口&ssoticket=法人登录的票据,则可以实现跨session跨域的单点登录;
2.4验证令牌并获取用户的登录信息
2.4.1功能说明:
以请求签名验签机制验证该应用是否具有访问令牌权限。如果有访问权限,将返回绑定该令牌的用户信息。
2.4.2接口类型:
http
2.4.3请求方式:
post
2.4.4接口调用地址:
正式环境地址:
https://ssoapi./rest/user/query
测试环境地址:
http://essotest.:8080/rest/user/query
2.4.5请求头信息设置:
所有HTTP接口调用时均需要设置Header请求头信息,其格式如下:
x-esso-project-id:11115****8
x-esso-signature:042183****************************a3d1eb58
Content-Type:application/json
Charset:UTF-8
附注:
x-esso-project-id为申请的应用ProjectId
x-esso-signature为以应用ProjectSecret作为密钥对请求Body做HmacSHa256摘要计算获取的十六进制字符串,其中请求Body为不含空格和换行符的JSON字符串。
2.4.6输入参数:
附注:POST请求参数类型为application/json类型
请求Body示例:
{"token":"12f5e1c6-883b-40fd-a55f-42c22424eb3f"}
2.4.7返回参数:
第三步
前面两步,大致介绍了如何跟政务服务网对接,以及获取相应的参数信息。
之前在对接的时候一直捉摸不透 【回调地址】 和 【验证令牌并获取用户的登录信息】它们有什么用处以及它们之间的相互关系。经过一阵调试,发布上线。对它们的理解也较为清晰明了。
其实就是一串URL。这串URL主要分为A:单点登录地址B:回调地址C:跳转地址三部分。Aspappurl=Bgoto=C
单点登录地址:
正式环境地址:
https://ssoapi./rest/user/query
测试环境地址:
http://essotest.:8080/rest/user/query
回调地址:
主要用来接收法人登录成功后 返回的ssotoken令牌和ssoticket票据。再拿这两个参数去请求 验证令牌并获取用户的登录信息 接口。如果验证 成功,则它能返回 该登录企业的信息。
跳转地址:
可以在goto后面加上登录成功后跳转的具体url地址。
注:
回调地址需要提交给政务服务网进行备案审核配置。所以需事先写好接口url发布上线。
它回调地址也分正式 和 测试。
“测试环境的projectID和projectSecret可以在对接资料的demo获取,正式的projectID和projectSecret需要提供正式的认证回调地址给天谷技术人员配置后给您生成。”
注:
之前说到需求,需要在我们平台上通过浙江政务服务网登录成功后直接跳转到对方平台上。因为对方平台的账号体系是浙江政务服务网的账号体系,所以本想将回调地址指向对方平台的回调地址即可实现。但对方那边人说“这个用户体系有一个垮平台免登陆的机制,是这样的两个平台如果都完成了用户体系对接,是可以相互免登跳转的,两个平台无需有数据交互。而且也不需要加票据了”
所以,只需对接浙江政务服务网的登录即可,拿其返回的ssotoken去验证令牌并获取用户的登陆信息,再指向跳转地址。(无需加ssoticket票据)
上代码
配置application.yml和GlobalConfig.java
#浙里办 法人登录zlb:#测试环境---验证令牌并获取用户的登录信息接口调用地址testQueryUrl: http://essotest.:8080/rest/user/query#正式环境---验证令牌并获取用户的登录信息接口调用地址releaseQueryUrl: https://ssoapi./rest/user/query#测试项目ID(公共应用ID) 11115XXXXXXprojectId: 11XXXXXXX#测试项目Secret(公共应用Secret) 55ea54dac3f497c43344a9904f4aa1aeprojectSecret: 10XXXXXXXXXXXXXXXXXXXXXXXXXXXX//===================浙里办 法人登录地址===============@Value("${zlb.testQueryUrl}")private String zlbTestQueryUrl;@Value("${zlb.releaseQueryUrl}")private String zlbReleaseQueryUrl;@Value("${zlb.projectId}")private String zlbProjectId;@Value("${zlb.projectSecret}")private String zlbProjectSecret;//===================浙里办 法人登录地址===============public static String ZLB_TEST_QUERY_URL;public static String ZLB_RELEASE_QUERY_URL;public static String ZLB_PROJECT_ID;public static String ZLB_PROJECT_SECRET;@Overridepublic void afterPropertiesSet() throws Exception {//=====浙里办 法人登录地址======ZLB_TEST_QUERY_URL = zlbTestQueryUrl;ZLB_RELEASE_QUERY_URL = zlbReleaseQueryUrl;ZLB_PROJECT_ID = zlbProjectId;ZLB_PROJECT_SECRET = zlbProjectSecret;}
回调地址接口
该回调地址接口需要浙江政务服务网备案审核。
能获得登录成功后的企业用户信息。结合自己系统做一定的业务逻辑操作。
@PostMapping("/getSSOTokenByZLB")public void getSSOTokenByZLB(HttpServletRequest request, HttpServletResponse response){response.setCharacterEncoding("UTF-8");// 跳转UrlString redirectUrl = "";// ssoticket是跨session用的,在原先链接里面加上&ssoticket=就可以实现跨session登录logger.info("--------------------------ssoticket {}--------------------------",request.getParameter("ssoticket"));// 从Request请求的参数中获取ssotokenString ssotoken = request.getParameter("ssotoken");logger.info("--------------------------ssotoken:{}--------------------------",ssotoken);// 取具体办事事项地址(若此项有值,成功登录后请跳转此地址到具体事项,否则跳转系统首页)String gotoUrl = request.getQueryString();if (null != gotoUrl && !gotoUrl.trim().equals("")) {// 清理事项地址前的“goto=”标识gotoUrl = gotoUrl.substring(5);logger.info("--------------------------具体业务办理地址 {}--------------------------",gotoUrl);}// 验证令牌并获取用户的登录信息net.sf.json.JSONObject jsonObj = doQuery(ssotoken, GlobalConfig.ZLB_PROJECT_ID, GlobalConfig.ZLB_PROJECT_SECRET);int errCode = jsonObj.getInt("errCode");// errCode = 0 表示认证成功if (0 == errCode) {// 验证成功String info = jsonObj.getString("info");logger.info("--------------------------验证令牌并获取用户的登录信息接口返回数据 {}--------------------------",info);net.sf.json.JSONObject legalInfo = net.sf.json.JSONObject.fromObject(info);// 第三步:// 企业名称String companyName = legalInfo.get("CompanyName").toString();if (null != companyName) {logger.info("--------------------------企业名称 {}--------------------------",companyName);}//注册地址String companyAddress = null;Object companyAddressObj = legalInfo.get("CompanyAddress");if(null != companyAddressObj){companyAddress = companyAddressObj.toString();logger.info("--------------------------注册地址 {}--------------------------",companyAddress);}// 统一社会信用代码String uniscid = null;Object uniscidObj = legalInfo.get("uniscid");if (null != uniscidObj) {uniscid = uniscidObj.toString();logger.info("--------------------------统一社会信用代码 {}--------------------------",uniscid);}// 用户id,政务服务网账号唯一标识String userid = null;Object useridObj = legalInfo.get("userId");if (null != useridObj) {userid = useridObj.toString();logger.info("--------------------------政务服务网账号唯一标识userId {}--------------------------",userid);}} else {// 验证失败,跳转登录失败的页面gotoUrl = "fail.html";}response.setHeader("refresh", "1;URL=" + gotoUrl);}
验证令牌并获取用户的登录信息
和上面的回调地址接口 相配合 以请求签名验签机制验证该应用是否具有访问令牌权限。如果有访问权限,将返回绑定该令牌的用户信息。
/**** 验证令牌并获取用户的登录信息** @param ssotoken* @return*/public net.sf.json.JSONObject doQuery(String ssotoken, String projectId, String projectSecret) {JSONObject param = new JSONObject();param.put("token", ssotoken);// 获取请求签名值String signature = DigestHelper.getSignature(param.toString(), projectSecret);// 设置请求的Headers头信息LinkedHashMap<String, String> headers = new LinkedHashMap<String, String>();headers.put("x-esso-project-id", projectId);headers.put("x-esso-signature", signature);headers.put("Content-Type", "application/json");headers.put("Charset", "UTF-8");net.sf.json.JSONObject jsonObj = sendPOST(GlobalConfig.ZLB_TEST_QUERY_URL, param.toString(), headers);if (null != jsonObj) {int errCode = jsonObj.getInt("errCode");String msg = jsonObj.getString("msg");if (0 == errCode) {/* 用户信息 */String info = jsonObj.getString("info");System.out.println("用户信息 = " + info);} else {System.out.println("验证令牌并获取用户的登录信息失败:" + msg);}}return jsonObj;}/**** 向指定URL发送POST方法的请求** @param apiUrl* @param data* @param headers* @return*/public net.sf.json.JSONObject sendPOST(String apiUrl, String data, LinkedHashMap<String, String> headers) {StringBuffer strBuffer = null;String result = null;net.sf.json.JSONObject jsonObj = null;try {// 建立连接URL url = new URL(apiUrl);/* 获取客户端向服务器端传送数据所依据的协议名称 */String protocol = url.getProtocol();if ("https".equalsIgnoreCase(protocol)) {/* 获取HTTPS请求的SSL证书 */try {SSLUtils.ignoreSsl();} catch (Exception e) {e.printStackTrace();}}HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();// 需要输出httpURLConnection.setDoOutput(true);// 需要输入httpURLConnection.setDoInput(true);// 不允许缓存httpURLConnection.setUseCaches(false);httpURLConnection.setRequestMethod("POST");// 设置Headersif (null != headers) {for (String key : headers.keySet()) {httpURLConnection.setRequestProperty(key, headers.get(key));}}// 连接会话httpURLConnection.connect();// 建立输入流,向指向的URL传入参数DataOutputStream dos = new DataOutputStream(httpURLConnection.getOutputStream());// 设置请求参数dos.write(data.getBytes("UTF-8"));dos.flush();dos.close();// 获得响应状态int http_StatusCode = httpURLConnection.getResponseCode();String http_ResponseMessage = httpURLConnection.getResponseMessage();if (HttpURLConnection.HTTP_OK == http_StatusCode) {strBuffer = new StringBuffer();String readLine = new String();BufferedReader responseReader = new BufferedReader(new InputStreamReader(httpURLConnection.getInputStream(), "UTF-8"));while ((readLine = responseReader.readLine()) != null) {strBuffer.append(readLine);}responseReader.close();result = strBuffer.toString();if (null == result || result.length() == 0) {throw new Exception("获取企业(法人)信息失败");} else {jsonObj = net.sf.json.JSONObject.fromObject(result);}} else {throw new Exception(MessageFormat.format("请求失败,失败原因: Http状态码 = {0} , {1}", http_StatusCode, http_ResponseMessage));}// 断开连接httpURLConnection.disconnect();} catch (MalformedURLException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();} catch (Exception e) {e.printStackTrace();}return jsonObj;}
测试:
http://essotest./opensso/spsaehandler/metaAlias/sp?spappurl=XXXXXX/getSSOTokenByZLB?goto=
输入发给我们联调的公共测试账号和密码。发现后台能输出该测试企业的一些基本信息。
OK~登录成功,获取成功,跳转成功。