概述
AI人脸识别,使用官方API:腾讯云人脸核身之独立H5接入。接口官方返回code = 0 表示成功,其他code码值均为对应码值信息,详见错误码。
注意:
1.合作方上送身份信息的计算签名参数与启动人脸核身计算签名参数不一致,有部分区别。
2.wbappid = webankAppId = app_id(如果我没理解错的话,API中介绍命名不同)
3.建议token和signticket一起做下20分钟定时刷新机制,保证缓存里的signticket是最新并且有效的
实现流程
合作方后台上送身份信息
- 前端入参:客户身份证号、客户姓名、from(App || browser)
- 后端固定参数:wbappid = webankAppId = app_id(API中介绍命名不同,注意)、orderNo(可自定义随机生成不唯一)、userId(可自定义随机生成不唯一)、version
- 获取signTicket(通过token)---- 获取方式代码见下
- 计算合作方上送身份信息签名,参数有:wbappid、orderNo、name、idNo、userId、version、signTicket ---- 计算方式代码见下
启动H5人脸核身
- 在合作方成功上送身份信息后,可以获取到h5faceId
- 获取nonce(32位随机数)
- 获取nonceTicket(通过token & userId)
- 计算启动H5人脸核身签名,参数有:wbappid、orderNo、userId、version、h5faceId、nonce、nonceTicket
- 将成功拉起人脸核身验证通过后的回调页面链接配置至配置文件,同时对该链接进行encode编码
- 获取到所有拉起人脸核身所需参数后,向链接https://ida.webank.com/api/web/login拼接上参数:webankAppId、version、nonce、orderNo、h5faceId、url、sign、from、userId。例如:
https://ida.webank.com/api/web/login?webankAppId="+wbappid+"&version=1.0.0&nonce="+nonce+"&orderNo="+orderNo+"&h5faceId="+h5faceId+"&url="+url+"&userId="+userId+"&sign="+sign+"&from="+userVo.getFrom()+"
拼接好后,直接将该链接返回前端去打开即可拉起人脸核身。请注意,该链接仅一次有效!!!
核心代码实现
获取accessToken
@Override
public String getToken(){
String accessTokenTencent = (String) redisService.get("accessTokenTencent");
log.info("获取redis中的accessToken,为:[{}]", accessTokenTencent);
if ("".equals(accessTokenTencent) || accessTokenTencent == null) {
Map<String, String> param = new HashMap<>(16);
String tokenUrl = "https://idasc.webank.com/api/oauth2/access_token?app_id={app_id}&secret={secret" +
"}&grant_type={grant_type}&version=1.0.0";
param.put("app_id", wbappid);
param.put("secret", secret);
param.put("grant_type", "client_credential");
TencentTokenDto tokenDto = template.getForObject(tokenUrl, TencentTokenDto.class, param);
if (!"0".equals(tokenDto.getCode())) {
log.error("获取腾讯token信息错误,errorCde:{},errMessage:{}", tokenDto.getCode(), tokenDto.getMsg());
return "";
}
accessTokenTencent = tokenDto.getAccessToken();
redisService.set("accessTokenTencent", accessTokenTencent, 6800L);
}
log.info("返回有效accessToken,为:[{}]", accessTokenTencent);
return accessTokenTencent;
}
通过accessToken获取signTicket
@Override
public String getSignTicket(String txAccessToken){
String signTicket = (String) redisService.get("signTicket");
log.info("获取redis中的signTicket,为:[{}]", signTicket);
if ("".equals(signTicket) || signTicket == null) {
Map<String, String> param = new HashMap<>(16);
String tokenUrl = "https://idasc.webank.com/api/oauth2/api_ticket?app_id={app_id}&access_token" +
"={access_token}&type={type}&version=1.0.0";
param.put("app_id", wbappid);
param.put("access_token", txAccessToken);
param.put("type", "SIGN");
TencentTicketDto ticketDto = template.getForObject(tokenUrl, TencentTicketDto.class, param);
if (!"0".equals(ticketDto.getCode())) {
log.error("获取腾讯signTicket信息错误,errorCde:{},errMessage:{}", ticketDto.getCode(), ticketDto.getMsg());
return "";
}
signTicket = ticketDto.getTickets().get(0).getValue();
redisService.set("signTicket", signTicket, 3000L);
}
log.info("返回有效signTicket,为:[{}]", signTicket);
return signTicket;
}
合作方上送身份信息计算签名
@Override
public String sign(String orderNo, String idNo, String name, String signTicket, String userId) {
//为计算签名做准备
List<String> list = new ArrayList<>();
String version = "1.0.0";
list.add(wbappid);
list.add(orderNo);
list.add(name);
list.add(idNo);
list.add(userId);
list.add(version);
String sign = getSign(list, signTicket);
return sign;
}
private String getSign(List<String> values, String signTicket) {
if (values == null) {
throw new NullPointerException("values is null");
}
// remove null
values.removeAll(Collections.singleton(null));
values.add(signTicket);
log.info("启动人脸核身签名排序前参数为:[{}]",values);
java.util.Collections.sort(values);
log.info("启动人脸核身签名排序后参数为:[{}]",values);
StringBuilder sb = new StringBuilder();
for (String s : values) {
sb.append(s);
}
return Hashing.sha1().hashString(sb, Charsets.UTF_8).toString().toUpperCase();
}
合作方上送身份信息
@Override
public TxCodeDto sendUserInfo(UserVo userVo){
//获取accessToken
String accessToken = getToken();
//获取signTicket
String signTicket = getSignTicket(accessToken);
//合作方上送计算签名
String sign = sign(userVo.getOrderNo(), userVo.getIdNo(), userVo.getName(), signTicket, userVo.getUserId());
Map<String, String> param = new HashMap<>(16);
param.put("webankAppId", wbappid);
param.put("orderNo", userVo.getOrderNo());
param.put("name", userVo.getName());
param.put("idNo", userVo.getIdNo());
param.put("userId", userVo.getUserId());
param.put("version", "1.0.0");
param.put("sign", sign);
log.debug("合作方上送身份信息参数有:[{}]", param);
String url = "https://idasc.webank.com/api/server/h5/geth5faceid";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<Map> entity = new HttpEntity<>(param, headers);
TxCodeDto txCodeDto = this.template.postForObject(url, entity, TxCodeDto.class);
log.info("合作方上送身份信息接口返回:[{}]", txCodeDto);
return txCodeDto;
}
通过accessToken & userId获取nonceTicket
@Override
public String getNonceTicket(String txAccessToken, String userId){
String nonceTicket = (String) redisService.get("nonceTicket");
log.info("获取redis中的nonceTicket,为:[{}]", nonceTicket);
if ("".equals(nonceTicket) || nonceTicket == null) {
Map<String, String> param = new HashMap<>(16);
String tokenUrl = "https://idasc.webank.com/api/oauth2/api_ticket?app_id={app_id}&access_token" +
"={access_token}&type={type}&version=1.0.0&user_id={user_id}";
param.put("app_id", wbappid);
param.put("access_token", txAccessToken);
param.put("type", "NONCE");
param.put("user_id", userId);
log.info("获取腾讯NONCE ticket,为:[{}]", param);
TencentTicketDto ticketDto = template.getForObject(tokenUrl, TencentTicketDto.class, param);
if (!"0".equals(ticketDto.getCode())) {
log.error("获取腾讯NONCE ticket信息错误,errorCde:{},errMessage:{}", ticketDto.getCode(), ticketDto.getMsg());
return "";
}
nonceTicket = ticketDto.getTickets().get(0).getValue();
redisService.set("nonceTicket", ticketDto.getTickets().get(0).getValue(), 100L);
}
log.info("返回有效nonceTicket,为:[{}]", nonceTicket);
return nonceTicket;
}
启动人脸核身计算签名
private String faceSign(String orderNo, String userId, String nonceTicket, String h5faceId, String nonce) {
//为计算签名做准备
List<String> list = new ArrayList<>();
list.add(wbappid);
list.add(orderNo);
list.add(userId);
list.add("1.0.0");
list.add(h5faceId);
list.add(nonce);
String sign = getSign(list, nonceTicket);
log.info("启动人脸核身返回签名为:[{}]",sign);
return sign;
}
启动人脸核身获取启动链接
@Override
public String startCheckFace(UserVo userVo){
//随机生成32位唯一用户ID和订单ID
String userId = getOrderNoOrUserIdRandom(32,"userId");
String orderNo = getOrderNoOrUserIdRandom(32,"orderNo");
userVo.setOrderNo(orderNo);
userVo.setUserId(userId);
String requestUrl = "";
try {
//获取accessToken
String accessToken = getToken();
//上送合作方用户信息
TxCodeDto txCodeDto = sendUserInfo(userVo);
if(!"0".equals(txCodeDto.getCode())){
log.info("启动人脸核身--上送合作方用户信息异常,异常原因为:[{}]]",txCodeDto.getMsg());
return requestUrl;
}
//获取h5/geth5faceid 接口返回的唯一标识
String h5faceId = txCodeDto.getResult().getH5faceId();
//获取32位随机数
String nonce = getRandom();
//获取nonceTicket
String nonceTicket = getNonceTicket(accessToken, userId);
//启动人脸核身计算签名
String sign = faceSign(orderNo, userId, nonceTicket,h5faceId,nonce);
//成功拉起人脸识别并识别成功或失败后的回调路径
String path = "http://127.0.0.1:8080/#/start?state=";
log.debug("人脸核身通过后的回调地址-拼接路径加密前:url = [{}]",path);
String url = getURLEncoderString(path);
log.debug("人脸核身通过后的回调地址-拼接路径加密后:url = [{}]",url);
requestUrl = "https://ida.webank.com/api/web/login?webankAppId="+wbappid+"&version=1.0.0&nonce="+nonce+"&orderNo="+orderNo+"&h5faceId="+h5faceId+"&url="+url+"&userId="+userId+"&sign="+sign+"&from="+userVo.getFrom()+"";
} catch (Exception e) {
log.error("启动人脸核身异常,异常原因为:[{}]",e.getMessage());
}
log.info("启动人脸核身--请求路径为:[{}]]",requestUrl);
return requestUrl;
}
postMan调用示例:
H5人脸核身结果跳转
验证结果之前端获取结果验证签名
/**
* 前端获取结果验证签名
* @param checkVo
*/
@Override
public String checkSign(CheckVo checkVo) {
//获取accessToken
String accessToken = getToken();
//获取signTicket
String signTicket = getSignTicket(accessToken);
List<String> list = new ArrayList<>();
list.add(wbappid);
list.add(checkVo.getOrderNo());
list.add(checkVo.getCode());
String sign = getSign(list, signTicket);
log.info("前端获取结果验证签名值为:[{}]",sign);
return sign;
}
服务端验证结果
/**
* 服务端验证结果
* @param checkVo
* @return
*/
@Override
public ServerResultDto serverCheck(CheckVo checkVo) {
//注意,随机数需要保持一致
checkVo.setRandom(getRandom());
String sign = serverSign(checkVo);
Map<String, String> param = new HashMap<>(16);
String url = "https://idasc.webank.com/api/server/sync?app_id={app_id}&nonce={nonce}&order_no={order_no}&version=1.0.0&sign={sign}&get_file={get_file}";
param.put("app_id", wbappid);
param.put("nonce", checkVo.getRandom());
param.put("order_no", checkVo.getOrderNo());
param.put("sign", sign);
param.put("get_file", "1");
log.debug("服务端验证结果参数有:[{}]", param);
TxServerDto txServerDto = this.template.getForObject(url,TxServerDto.class,param);
if (!"0".equals(txServerDto.getCode())) {
log.error("服务端验证结果信息错误,errorCde:{},errMessage:{}", txServerDto.getCode(), txServerDto.getMsg());
return null;
}
return txServerDto.getResult();
}