第三方业务系统与密钥宝APP登录,签名,签章改造接口文档,供第三方的业务系统进行参照与接口实现。
登录二维码的生成包括:自助生成二维码、网关请求二维码
扫描自助生成二维码之后的业务交互接口包括:扫码交互接口、接收登录、签名请求、签章请求、注销登录请求接口
本手册适用于对接密钥宝APP登录,签名,签章的业务系统开发人员。
第三方web系统自助生成二维码,用于第三方WEB系统结合移动证书完成登录,签名,签章功能。
说明:交互地址reqInfo可自定义,sessionId参数名称可以自定义
缓存数据Permission
说明:该信息在生成二维码请求时放入缓存中,将信息按sessiondid作为主键保存用于登录认证或签名,签章请求时使用。
根据页面所选择的功能不同,产生的permission中信息不同
public class Permission {
private String sessionId;// session id
private String functionId;// 业务类型
private String url;// 发送地址
private String txt;// 发送内容
private String sign;// 签名值
private String systemFlag;// 应用名称
private String busDes;// 业务描述
}
(1)生成二维码参考示例代码:com.sxca.webdemo.web.QRController
@RequestMapping(value = "/getQR")
public void getQR(String f,String txt,String sign,HttpServletRequest request,
HttpServletResponse response, Model model) {
String basePath = request.getScheme()+"://"+request.getServerName()+":"+
request.getServerPort()+request.getContextPath()+"/";
String path = null;
String sessionid = request.getSession().getId();// 获取sessionid
String hashTxt = HashUtil.getInstance().getHashMD5(txt,"MD5");// 获取需要签名的原文
Permission p = null;
path = basePath+"loginBySign/doLogin";
p = new Permission(sessionid, f, path, hashTxt, sign,systemFlag,"系统登录");
}else if("2".equals(f)) {
path = basePath+"sign/doSign";
p = new Permission(sessionid, f, path, hashTxt, sign,systemFlag,"文件审批签名");
}else{
path = basePath+"seal/doSeal";
p = new Permission(sessionid, f, path, hashTxt, sign,systemFlag,"文件审批盖章");
}
CacheUtil.getInstance().put(sessionid, p.toString());// 存入缓存
QRUtils.getInstance().encoderQRCoder(basePath +"QR/reqInfo?sessionId=" + sessionid,response);
}
说明:在生成二维码时,将与当前Session相关的需要与密钥宝APP交互的信息存入缓存。具体请参考附录一:Cache工具类com.sxca.webdemo.util.CacheUtil
(2)其他参考示例代码
A.登录
获取登录二维码js
function loginBySign() {
funcSelect="loginBySign";
$("#loginSelect").addClass("display");
$("#qrcodemsg").addClass("display");
$("#scan").removeClass("display");
$("#qrcodeimg").removeClass("display");
$("#returnBtn").removeClass("display");
$("#scan_img").attr("src","/mybweb/a/QR/getQR?f=1&txt=<%=Math.random()%>&num=" + (Math.random() * 10000 + 1));
reqLogin();
QRintervalID = setTimeout(function(){checkQR();}, 60000);
}
检查登录状态JS:
function reqLogin() {
<%–注:以下请求地址可自由调整,与后台对应即可–%>
clearTimeout(intervalID);
$.post("/mybweb/a/loginBySign/reqLogin?num=" + (Math.random() * 10000 + 1), function(data) {
if (data != null && data != "noUser")
location.href = "/mybweb/a/loginBySign/loginSuccess";
else
intervalID = setTimeout(function(){reqLogin();}, 3000);
});
}
检查登录状态后台代码:com.sxca.webdemo.web.QRLoginBySignController
/*该方法地址与JS中请求地址一致即可*/
@RequestMapping(value = "/reqLogin")
@ResponseBody
public String reqLogin(HttpServletRequest request, HttpServletResponse response) {
String result = null;
HttpSession session = SessionUtils.getInstance().getSession(request.getSession().getId());
Object user = session.getAttribute("user");
if (user == null)
result = "noUser";
else
result = "hasUser";
return result;
}
B.签名
展示签名界面js
function sign_init() {
funcSelect="sign";
$("#loginSelect").addClass("display");
$("#input").removeClass("display");
$("#scan").removeClass("display");
$("#returnBtn").removeClass("display");
}
获取签名二维码js
function sign() {
var txt = $("#txt").val();
if(txt){
$("#input").addClass("display");
$("#qrcodemsg").addClass("display");
$("#qrcodeimg").removeClass("display");
$("#returnBtn").removeClass("display");
$("#scan_img").attr("src","/mybweb/a/QR/getQR?f=2&txt="+txt+"&num=" + (Math.random() * 10000 + 1));
reqSign();
QRintervalID = setTimeout(function(){checkQR();}, 60000);
}else {
alert("请先输入签名内容!");
}
}
获取签名结果js
function reqSign() {
<%–注:以下请求地址可自由调整,与后台对应即可–%>
clearTimeout(intervalID);
$.post("/mybweb/a/sign/reqSign?num=" + (Math.random() * 10000 + 1), function(data) {
if (data != null && data != "no")
location.href = "/mybweb/a/sign/signSuccess";
else
intervalID = setTimeout(function(){reqSign();}, 3000);
});
}
检查签名结果后台代码
@RequestMapping(value = "/reqSign")
@ResponseBody
public String reqSign(HttpServletRequest request, HttpServletResponse response) {
String result = null;
HttpSession session = SessionUtils.getInstance().
getSession(request.getSession().getId());
Object rsv = session.getAttribute("rsv");
Object permission = session.getAttribute("permission");
if (rsv != null&&permission != null)
result = "has";
result = "no";
return result;
}
B.签章
获取签章二维码js
function seal() {
funcSelect="seal";
$("#loginSelect").addClass("display");
$("#qrcodemsg").addClass("display");
$("#scan").removeClass("display");
$("#qrcodeimg").removeClass("display");
$("#returnBtn").removeClass("display");
$("#scan_img").attr("src","/mybweb/a/QR/getQR?f=3&txt=<%=Math.random()%>&num=" + (Math.random() * 10000 + 1));
reqSeal();
QRintervalID = setTimeout(function(){checkQR();}, 60000);
}
获取签章结果js
function reqSeal() {
<%–注:以下请求地址可自由调整,与后台对应即可–%>
clearTimeout(intervalID);
$.post("/mybweb/a/seal/reqSeal?num=" + (Math.random() * 10000 + 1), function(data) {
if (data != null && data != "no")
location.href = "/mybweb/a/seal/sealSuccess";
else
intervalID = setTimeout(function(){reqSeal();}, 3000);
});
}
检查签章结果后台代码
@RequestMapping(value = "/reqSeal")
@ResponseBody
public String reqSeal(HttpServletRequest request, HttpServletResponse response) {
String result = null;
HttpSession session = SessionUtils.getInstance().
getSession(request.getSession().getId());
result = (String) session.getAttribute("result");
if (result == null)
result = "no";
result = "has";
return result;
}
第三方WEB系统通过上面描述方式进行二维码的生成,手机APP扫描页面二维码后,交互请求的接口,即二维码中包含的地址。
接口传入 : sessionId
接口传出 : sessionid所对应的需要与密钥宝APP交互的信息JSON串
sessionId
{"resultCode":"0","msg":"{\"sessionId\":\"6604701F3F57521A5F146CC68FE97E92\",\"functionId\":\"1\",\"url\":\"http://localhost:9080/DoLogin\",\"txt\":\"HqMw9+nEEccWQ+wJiuEXYw\\u003d\\u003d\",\"appId\":\"001\"}"}
扫描二维码请求示例,校验二维码是否过期并获取Session相关信息(部分示例代码)
@RequestMapping(value = "/reqInfo")
@ResponseBody
public String reqInfo(String sessionId) throws IOException {
ResultValue rsv = null;
if (sessionId == null || sessionId.length() == 0) {
rsv = new ResultValue("1", "传输数据无效,请验证后重新扫描!");
} else {
String json = (String) CacheUtil.getInstance().get(sessionId);
if (json == null || json.length() == 0) {
rsv = new ResultValue("1", "二维码数据已过期,请刷新页面重新扫描。");
} else
rsv = new ResultValue("0", json);
}
return rsv.toString();
}
具体请参考:com.sxca.webdemo.web.QRController
接受手机传递的请求,指定相应的session进行处理。接收到在密钥宝App中处理后的信息,在该接口中进行登录,签名验证。
验签通过后,第三方业务系统根据自身权限进行设置,可从证书中获取部分信息进行权限校验。
接口传入 : data(Permission对象的json串,内含移动证书对原文加密后的结果)
接口传出 : 请求结果JSON串
data
返回数据实例:
{"resultCode":"0","msg":"用户登录成功!","url":"http://localhost:9080/DoLogout?sessionid=6604701F3F57521A5F146CC68FE97E92","system":"山西CA测试","checkUrl":"http://localhost:9080/CheckLogin?sessionid=6604701F3F57521A5F146CC68FE97E92"}
登录请求示例,校验证书是否过期并返回相关信息(部分示例代码):
@RequestMapping(value = "/doLogin")
@ResponseBody
public String doLogin(HttpServletRequest request, HttpServletResponse response) throws IOException {
request.setCharacterEncoding("utf-8");
String data = request.getParameter("data");
ResultValue rsv = null;
if (isNull(data)) {
rsv = new ResultValue("1", "未获取到操作数据!");
} else {
//手机传递登录参数
Permission p = Permission.getPermission(data);
if (isNull(p.getSign())) {//加密数据
rsv = new ResultValue("1", "未获取到加密数据!");
} else {
Result rs = VerifyUtils.getInstance().verify(p.getSign());
if (rs != null) {
if (!rs.isFlag()) {
// System.out.println("验签失败"+rs.getErrMsg());
rsv = new ResultValue("1", rs.getErrMsg());
} else if (rs.getErrCode().equals("10702040")) {
rsv = new ResultValue("1", "证书已过期!");
} else {
//验签通过后,验内容
if (rs.getTxt().equals(p.getTxt())) {
boolean flag = authMession(p.getSessionId(),rs);
if(flag){
rsv = new ResultValue("0", "用户登录成功!");
String u = request.getRequestURL().toString();
u = u.replace(request.getServletPath(), "/");
rsv.setUrl(u+"loginBySign/doLogout?sessionId="+
p.getSessionId()); rsv.setCheckurl(u+"loginBySign/checkLogin?sessionId="+
p.getSessionId());
}else{
rsv = new ResultValue("1", "用户登录失败!");
}
} else {
rsv = new ResultValue("1", "非法操作!");
}
}
} else {
rsv = new ResultValue("1", "数据验证失败!");
}
//删去缓存中的permission信息
CacheUtil.getInstance().remove(p.getSessionId());
}
}
return rsv.toString();
}
具体请参考:com.sxca.webdemo.web.QRLoginBySignController
接受手机传递的请求,指定相应的session进行处理。接收到在密钥宝App中处理后的信息,在该接口中进行登录,签名验证。
验签通过后,第三方业务系统返回签名结果
接口传入 : data(Permission对象的json串,内含移动证书对原文加密后的结果)
接口传出 : 请求结果JSON串
data
返回数据实例:
{"resultCode":"0","msg":"签名成功!"}
接收验签请求,校验是否签名成功(部分示例代码):
@RequestMapping(value = "/doSign")
@ResponseBody
public String doSign(HttpServletRequest request, HttpServletResponse response) throws IOException {
request.setCharacterEncoding("utf-8");
String data = request.getParameter("data");
ResultValue rsv = null;
if (isNull(data)) {
rsv = new ResultValue("1", "未获取到操作数据!");
} else {
//手机传递登录参数
Permission p = Permission.getPermission(data);
if (isNull(p.getSign())) {//加密数据
rsv = new ResultValue("1", "未获取到加密数据!");
} else {
Result rs = VerifyUtils.getInstance().verify(p.getSign());
if (rs != null) {
if (!rs.isFlag()) {
rsv = new ResultValue("1", rs.getErrMsg());
} else if (rs.getErrCode().equals("10702040")) {
rsv = new ResultValue("1", "证书已过期!");
} else {
//验签通过后,验内容
if (rs.getTxt().equals(p.getTxt())) {
rsv = new ResultValue("0", "签名成功!");
} else {
rsv = new ResultValue("1", "验签名失败!");
}
}
} else {
rsv = new ResultValue("1", "数据验证失败!");
}
setAttr(p,rsv);
//删去缓存中的permission信息
CacheUtil.getInstance().remove(p.getSessionId());
}
}
return rsv.toString();
}
具体请参考:com.sxca.webdemo.web.QRSignController
描述
接受手机传递的请求,指定相应的session进行处理。接收到在密钥宝App中处理后的信息,在该接口中进行登录,签名,签章验证。
验签通过后,第三方业务系统根据自身配置进行图片选择
接口传入 : data(Permission对象的json串,内含移动证书对原文加密后的结果)
接口传出 : 请求结果JSON串
请求地址
请求参数
data
返回数据
返回数据实例:
{"resultCode":"0","msg":"签名成功!"}
实现代码示例
接收验签请求,校验是否签名成功,并设置签章图片(部分示例代码):
@RequestMapping(value = "/doSeal")
@ResponseBody
public String doSeal(HttpServletRequest request, HttpServletResponse response) throws IOException {
request.setCharacterEncoding("utf-8");
String data = request.getParameter("data");
ResultValue rsv = null;
if (isNull(data)) {
rsv = new ResultValue("1", "未获取到操作数据!");
} else {
//手机传递登录参数
Permission p = Permission.getPermission(data);
if (isNull(p.getSign())) {//加密数据
rsv = new ResultValue("1", "未获取到加密数据!");
} else {
Result rs = VerifyUtils.getInstance().verify(p.getSign());
if (rs != null) {
if (!rs.isFlag()) {
rsv = new ResultValue("1", rs.getErrMsg());
} else if (rs.getErrCode().equals("10702040")) {
rsv = new ResultValue("1", "证书已过期!");
} else {
//验签通过后,验内容
if (rs.getTxt().equals(p.getTxt())) {
rsv = new ResultValue("0", "签名成功!");
} else {
rsv = new ResultValue("1", "验签名失败!");
}
}
} else {
rsv = new ResultValue("1", "数据验证失败!");
}
setPicSrc(p,rs,rsv);
//删去缓存中的permission信息
CacheUtil.getInstance().remove(p.getSessionId());
}
}
return rsv.toString();
}
具体请参考:com.sxca.webdemo.web.QRSealController
接受手机传递的注销请求,将指定用户从session中进行销毁。该接口地址是否第三方业务系统提供,在登录请求中登录成功后,将该地址返回给密钥宝APP,该接口中除了响应参数是固定不能变的,请求信息可由第三方业务系统定义,实际注销即可,以下接口示例仅供参考。
接口传入 : sessionid
接口传出 : 请求结果JSON串
data
返回数据实例:
{"resultCode":"0","msg":"注销成功!"}
接收注销登录请求,销毁Session(部分示例代码):
@RequestMapping(value = "/doLogout")
public void doLogout(String sessionId,HttpServletRequest request, HttpServletResponse response) throws IOException {
ResultValue rsv = null;
PrintWriter out = response.getWriter();
if (sessionId == null || sessionId.length() == 0) {
rsv = new ResultValue("1", "传输数据无效,请验证后重新扫描!");
} else {
HttpSession session = SessionUtils.getInstance().getSession(sessionId);
if(session!=null){
session.removeAttribute("user");
rsv = new ResultValue("0", "注销成功");
}else{
rsv = new ResultValue("0", "会话不存在");
}
}
out.print(rsv.toString());
out.flush();
out.close();
}
具体请参考:com.sxca.webdemo.web.QRLoginBySignController
第三方web系统接入网关,第三方应用轮询证书在网关登录认证状态,认证成功后,获取到登录证书的信息,第三方业务系统根据证书信息获取本地用户相应的权限角色等信息进行操作。
第三方应用请求网关生成二维码参考示例代码:com.sxca.webdemo.web. QRLoginByGetWayController
@RequestMapping(value = "/getQR")
public void getQR(HttpServletRequest request, HttpServletResponse response) throws IOException {
String appId = "WebDemo_MobileCertST_OAuth";
String generateQRCodeURL = "http://218.26.169.142:6180/jit_qrcode_generate";
response.setContentType("image/jpeg");
byte[] data = this.gengrateQRCode(generateQRCodeURL,appId,response);
OutputStream out = response.getOutputStream();
out.write(data);
out.flush();
out.close();
}
说明:第三方应用请求网关生成二维码后,web系统会去网关轮询此二维码的状态,直到二维码被扫描成功或者二维码过期。
详情请参考附录三。
public class CacheUtil {
private static final Logger logger = LoggerFactory
.getLogger(CacheUtil.class);
private static CacheUtil util = null;
private static final String CACHENAME = "CAUSERCACHE";
private static CacheManager cacheManager = null;
private static Cache cache = null;
private CacheUtil() {
logger.info(CACHENAME + "缓存初始化");
cacheManager = CacheManager.create();
cache = cacheManager.getCache(CACHENAME);
}
public static CacheUtil getInstance() {
if (util == null)
util = new CacheUtil();
return util;
}
/**
* 插入缓存
*/
public void insert(String key, String value) {
cache.put(new Element(key, value));
}
/**
* 查找缓存
*/
public String find(String key) {
String value = null;
try {
value = cache.get(key).getObjectValue().toString();
} catch (Exception e) {
value = null;
logger.error("查找缓存出错:" + e.getMessage());
}
return value;
}
/**
* 删除缓存
*/
public void remove(String key) {
try {
cache.remove(key);
} catch (Exception e) {
logger.error("删除缓存出错:" + e.getMessage());
}
}
}
/**
* session监听器 当有链接进入时,将session存入全局变量中 当有连接销毁时,将session移除
*
*/
public class SXCASessionListener implements HttpSessionListener {
private SessionUtils sessionUtils = SessionUtils.getInstance();
@Override
public void sessionCreated(HttpSessionEvent event) {
sessionUtils.addSession(event.getSession());
}
@Override
public void sessionDestroyed(HttpSessionEvent event) {
sessionUtils.deleteSession(event.getSession());
}
}
在web.xml中添加:
<listener>
<display-name>session监听器</display-name>
<listener-class>com.sxca.listener.SXCASessionListener</listener-class>
</listener>
注:Session的增删改查通过session工具类实现(具体参考demo)
(1)二维码生成
/** * 生成二维码 * @param url * @return */ private byte[] gengrateQRCode(String url,String appFlag,HttpServletResponse response){Map<String,Object> propsMap = new HashMap<String,Object>(); propsMap.put("Service_Type", "qrcode_generate"); byte[] sb = null; HttpClient httpClient=new HttpClient(); PostMethod postMethod = new PostMethod(url);//POST请求 postMethod.setRequestHeader("appFlag", appFlag); Set<String> keySet=propsMap.keySet(); NameValuePair[] postData=new NameValuePair[keySet.size()]; int index=0; for(String key:keySet){ postData[index++]=new NameValuePair(key,propsMap.get(key).toString()); } postMethod.addParameters(postData); int statusCode; try{ statusCode = httpClient.executeMethod(postMethod);//发送请求 if (statusCode == HttpStatus.SC_OK) { sb = postMethod.getResponseBody(); org.apache.commons.httpclient.Cookie[] cookies = httpClient.getState().getCookies(); for (org.apache.commons.httpclient.Cookie cookie : cookies) { Cookie c = new Cookie(cookie.getName(),cookie.getValue()); response.addCookie(c); } } else { System.err.println("Response Code: " + statusCode); } }catch (HttpException e){ e.printStackTrace(); }catch (IOException e){ e.printStackTrace(); }finally{ postMethod.releaseConnection();//关闭连接 } return sb; }
(2)二维码状态查询
/**
* 查询二维码状态
* @param url
* @return
*/
private String queryQRCodeState(String url){
Map<String,Object> propsMap = new HashMap<String,Object>();
InputStream ins = null;
StringBuilder sb = new StringBuilder();
HttpClient httpClient=new HttpClient();
PostMethod postMethod = new PostMethod(url);//POST请求
Set<String> keySet=propsMap.keySet();
NameValuePair[] postData=new NameValuePair[keySet.size()];
int index=0;
for(String key:keySet){
postData[index++]=new NameValuePair(key,propsMap.get(key).toString());
}
postMethod.addParameters(postData);
int statusCode;
try{
statusCode = httpClient.executeMethod(postMethod);//发送请求
if (statusCode == HttpStatus.SC_OK) {
ins = postMethod.getResponseBodyAsStream();
byte[] b = new byte[1024];
int r_len = 0;
while ((r_len = ins.read(b)) > 0) {
sb.append(new String(b, 0, r_len, postMethod
.getResponseCharSet()));
}
} else {
System.err.println("Response Code: " + statusCode);
}
}catch (HttpException e){
e.printStackTrace();
}catch (IOException e){
e.printStackTrace();
}finally{
postMethod.releaseConnection();//关闭连接
}
return sb.toString();
}
原文链接:http://miyaobao.cn/mybweb/f/apiIos