# H5活体认证流程说明

# 第一步 获取H5认证入口

下载我们提供的sdk,根据示例代码,获取到H5视频入口

点我查看获取认证会话入口API

 {
   "code": 0,
    "requestId":"dsd24...",
    "message": "success",
    "data": {
        "verifyToken":"K76xV0WilukeYZF3sMNbdnw00b3Xf8gw",
        "verifyDurationSeconds":1800,
        "verifyPageUrl":"https://h5iv.unitid.cn/auth/auth-liveness/mnnhcI0hekX9YnyTx4LumK5GnRLr9iaE"
    }
}

# 第二步 进行认证操作

直接访问第一步获取到的verifyPageUrl,根据开发者在获取H5认证入口所填写的参数不同,我们会返回两种不同的页面:

  1. 如果开发者在获取H5认证入口已经将开发者姓名和身份证号码输入,则会直接进入到录制视频的页面;
  2. 如果开发者在获取H5认证入口时,并没有传入这两个参数,我们会先提供一个填写姓名和身份证号的页面,在开发者填写完相应的信息后,会进入到录制视频的页面。

填写开发者信息的页面(开发者在填写完相应数据后,点击下一步,会进入录制视频页面)

填写开发者信息的页面(开发者在填写完相应数据后,点击下一步,会进入录制视频页面)

录制视频页面(开发者点击开始录制按钮,录制2-5秒钟的视频,上传即可)

录制视频页面

# 第三步 结果页面跳转

# 成功页面举例

返回结果页面(视频上传成功后,页面会返回验证信息)

返回结果页面

# 错误页面举例

开发者在操作过程中,可能会由于不当错误,到导致页面出现报错信息,我们提供几个较为常见的错误页面。

连接失效页面

返回结果页面

活体检测超过限制次数页面

返回结果页面

# 第四步 下载认证信息

通过我们提供的sdk,可以根据ticketId下载该认证会话的结果。

点我查看获取认证会话结果API

# 回调通知接口示例

当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();
    }

最后更新于: 4/8/2020, 4:55:16 PM