# H5活体认证流程说明
# 第一步 获取H5认证入口
下载我们提供的sdk,根据示例代码,获取到H5视频入口
{
"code": 0,
"requestId":"dsd24...",
"message": "success",
"data": {
"verifyToken":"K76xV0WilukeYZF3sMNbdnw00b3Xf8gw",
"verifyDurationSeconds":1800,
"verifyPageUrl":"https://h5iv.unitid.cn/auth/auth-liveness/mnnhcI0hekX9YnyTx4LumK5GnRLr9iaE"
}
}
# 第二步 进行认证操作
直接访问第一步获取到的verifyPageUrl,根据开发者在获取H5认证入口所填写的参数不同,我们会返回两种不同的页面:
- 如果开发者在获取H5认证入口已经将开发者姓名和身份证号码输入,则会直接进入到录制视频的页面;
- 如果开发者在获取H5认证入口时,并没有传入这两个参数,我们会先提供一个填写姓名和身份证号的页面,在开发者填写完相应的信息后,会进入到录制视频的页面。
填写开发者信息的页面(开发者在填写完相应数据后,点击下一步,会进入录制视频页面)
录制视频页面(开发者点击开始录制按钮,录制2-5秒钟的视频,上传即可)
# 第三步 结果页面跳转
# 成功页面举例
返回结果页面(视频上传成功后,页面会返回验证信息)
# 错误页面举例
开发者在操作过程中,可能会由于不当错误,到导致页面出现报错信息,我们提供几个较为常见的错误页面。
连接失效页面
活体检测超过限制次数页面
# 第四步 下载认证信息
通过我们提供的sdk,可以根据ticketId下载该认证会话的结果。
# 回调通知接口示例
当H5活体认证流程结束后,平台服务器会向开发者提供的callback
参数地址向开发者发送HTTP通知。平台服务器得到正确的确认信息后,会停止通知,避免重复向callback
参数地址发送通知。
开发者在接收到参数时,需要对参数进行验签,在确认无误的情况下,向H5活体认证系统返回"success"字符串。
回调通知接口示例
/**
* 回调通知示例
*
* @param request
* @param response
* @throws IOException
*/
@RequestMapping(value = "/notify", method = {RequestMethod.POST})
public String notify(HttpServletRequest request, HttpServletResponse response) throws IOException {
Map<String, String> paranMap= Maps.newHashMap();
//公共参数
paranMap.put("appKey",request.getParameter("appKey"));
paranMap.put("signType",request.getParameter("signType"));
paranMap.put("notifyTime",request.getParameter("notifyTime"));
paranMap.put("notifyId",request.getParameter("notifyId"));
paranMap.put("version",request.getParameter("version"));
paranMap.put("sign",request.getParameter("sign"));
//业务参数
paranMap.put("ticketId",request.getParameter("ticketId"));
//验签
boolean res = SignUtil.signCheck(paranMap, "app密钥", SignUtil.CHARSET,request.getParameter("signVersion"));
if(res){
//验签成功
// 可以获取认证结果了
//返回通知成功
return "success";
}
// 验签失败 可以给一个异常
return 'fail';
}
/**
* 页面跳转示例
*
* @param request
* @param response
* @throws IOException
*/
@ApiOperation("用户认证后页面跳转")
@RequestMapping(value = "/return", method = {RequestMethod.GET})
public void returnUrl(HttpServletRequest request, HttpServletResponse response) throws IOException {
Map<String, String> paranMap = Maps.newHashMap();
//公共参数
paranMap.put("appKey", request.getParameter("appKey"));
paranMap.put("signType", request.getParameter("signType"));
paranMap.put("notifyTime", request.getParameter("notifyTime"));
paranMap.put("notifyId", request.getParameter("notifyId"));
paranMap.put("version", request.getParameter("version"));
paranMap.put("ticketId", request.getParameter("ticketId"));
paranMap.put("sign", request.getParameter("sign"));
System.out.println(paranMap.toString());
//验签
boolean res = SignUtil.signCheck(paranMap, "PSZEye8EaSg3TOWot8En4Br0s9NTshcm", SignUtil.CHARSET,request.getParameter("signType"));
if (res) {
//验签成功
}
// 跳转地址
response.sendRedirect("http://www.xxxx.cn/");
}
验签工具类
public class SignUtil {
public final static String SIGN_TYPE_HMAC_SHA_1 = "HmacSHA1";
public final static String SIGN_TYPE_HMAC_SHA_256 = "HmacSHA256";
public final static String REQUEST_PUBLIC_PARAMS_SIGN = "sign";
public final static String CHARSET = "UTF-8";
private static final Object LOCK = new Object();
/**
* Prototype of the Mac instance.
*/
private static Mac macInstance;
/**
* 验签
*
* @param paranMap
* @param secretKey
* @param charset
* @param signType
* @return
*/
public static boolean signCheck(Map<String, String> paranMap, String secretKey, String charset, String signType) {
// 字典排序
String sign = paranMap.get(REQUEST_PUBLIC_PARAMS_SIGN);
String content = getSignCheckContentV1(paranMap);
if (SIGN_TYPE_HMAC_SHA_1.equals(signType)) {
return sign.equals(computeSignature(secretKey, content, charset, SIGN_TYPE_HMAC_SHA_1));
} else {
return sign.equals(computeSignature(secretKey, content, charset, SIGN_TYPE_HMAC_SHA_256));
}
}
public static String getSignCheckContentV1(Map<String, String> params) {
if (params == null) {
return null;
}
params.remove(REQUEST_PUBLIC_PARAMS_SIGN);
return getSignatureContent(params);
}
public static String getSignatureContent(Map<String, String> paranMap) {
return getSignContent(getSortedMap(paranMap));
}
public static Map<String, String> getSortedMap(Map<String, String> paranMap) {
Map<String, String> sortedParams = Maps.newTreeMap();
paranMap.keySet().stream()
.sorted()
.forEach(sortKey -> sortedParams.put(sortKey, paranMap.get(sortKey)));
return sortedParams;
}
/**
* @param sortedParams
* @return
*/
public static String getSignContent(Map<String, String> sortedParams) {
StringBuffer content = new StringBuffer();
List<String> keys = new ArrayList<String>(sortedParams.keySet());
Collections.sort(keys);
for (int i = 0; i < keys.size(); i++) {
String key = keys.get(i);
String value = sortedParams.get(key);
if (StringUtils.isNotEmpty(key) && StringUtils.isNotEmpty(value)) {
content.append(key).append(value);
}
}
return content.toString();
}
public static String computeSignature(String key, String data, String charset, String algorithm) {
try {
byte[] signData = sign(key.getBytes(charset), data.getBytes(charset), algorithm);
return byte2hex(signData);
} catch (UnsupportedEncodingException ex) {
throw new RuntimeException("Unsupported algorithm: " + charset, ex);
}
}
private static byte[] sign(byte[] key, byte[] data, String algorithm) {
try {
// Because Mac.getInstance(String) calls a synchronized method, it could block on
// invoked concurrently, so use prototype pattern to improve perf.
if (macInstance == null) {
synchronized (LOCK) {
if (macInstance == null) {
macInstance = Mac.getInstance(algorithm);
}
}
}
Mac mac = null;
try {
mac = (Mac) macInstance.clone();
} catch (CloneNotSupportedException e) {
// If it is not clonable, create a new one.
mac = Mac.getInstance(algorithm);
}
mac.init(new SecretKeySpec(key, algorithm));
return mac.doFinal(data);
} catch (NoSuchAlgorithmException ex) {
throw new RuntimeException("Unsupported algorithm: " + algorithm, ex);
} catch (InvalidKeyException ex) {
throw new RuntimeException("Invalid key: " + key, ex);
}
}
/**
* 把字节流转换为十六进制表示方式。
*/
protected static String byte2hex(byte[] bytes) {
StringBuilder sign = new StringBuilder();
for (int i = 0; i < bytes.length; i++) {
String hex = Integer.toHexString(bytes[i] & 0xFF);
if (hex.length() == 1) {
sign.append("0");
}
sign.append(hex.toUpperCase());
}
return sign.toString();
}