# 慧眼企业认证

# 业务介绍

移动端实名验证产品,主要的应用在 Android/IOS 场景中,提供了以下功能:

  • 用户通过应用输入的手机号码,验证手机号码是否正确。
  • 用户通过应用传入的身份证正反面,结构化识别证件照。
  • 用户通过应用新录制一个视频,来进行活体检测的判断,并解析出人脸图片,并与上传的身份证上的人脸进行比对。
  • 用户通过应用输入工商四要素进行核验,判断企业信息的真实性。
  • 用户通过应用可以在四种企业认证方式中进行意愿认证。分别为短信认证、对公打款、纸质材料上传、人脸活体检测。
    • 短信认证:用户通过应用输入法定代表人手机号进行运营商三要素认证,对法定代表人进行获取短信验证码验证。
    • 对公打款:用户通过应用输入对公打款账号,然后线下进行对公打款,进行对公打款验证。
    • 纸质材料上传:用户通过应用上传需要审核的公司纸质材料,后台进行纸质材料审核验证。
    • 人脸活体检测:用户通过应用输入法定代表人手机号,对法定代表人进行人脸活体认证。

交互流程:

用户通过页面填写手机号,OCR 识别,视频活体验证,企业信息登记,企业意愿认证,认证完成。

这里为了方便您的理解,请点击如下链接查看演示:

Android/IOS认证流程说明

# 获取token

根据传入的核验信息生成 Token

# 一、请求说明

# 二、请求参数

名称 类型 是否必须 描述
runEnv String 运行环境(详情见字段解释)
notifyUrl String 服务器通知地址(点我查看详细使用)
personName String 办理人姓名
personIdcard String 办理人证件号
personMobile String 办理人手机号码
  • 字段解释

  • runEnv 运行环境类型介绍

类型字段 介绍
SDK_ANDROID 以 SDK 运行,支持 Android
SDK_IOS 以 SDK 运行,支持 IOS

请求示例:

https://eapi.spiderid.cn/openapi/verification?
&runEnv=XXX
&<[公共请求参数]>

# 三、响应参数

data 结果信息 类型 描述
authToken String 认证 Token
expireTime Long 过期时间

# 四、成功示例

JSON示例

{
  "code": 0,
  "data": {
    "authToken": "75c55b624f2b8ce1b60e314cf7336b35bcf27cbdf40be03b13236b892e4672b0",
    "expireTime": 1611649618449,
  },
  "message": "success",
  "requestId": "f1205..."
}

# 五、失败示例

JSON示例

{
  "code": 10033,
  "requestId":"f1205...",
  "message": "App配置缺失"
}

# 获取验证状态

根据 Token 验证状态

# 一、请求说明

# 二、请求参数

名称 类型 是否必须 描述
authToken String 认证token

请求示例:

https://eapi.spiderid.cn/openapi/verification?
token=XXX
&<[公共请求参数]>

# 三、响应参数

data 结果信息 类型 描述
status String 认证状态 (详情见字段解释)
incorrect Integer 返回码 (详情见字段解释)
message String 对返回码的描述
flow String 当前认证流程
  • 字段解释

  • status 认证状态介绍

字段 状态介绍
CREATE 未认证
ACTION 认证中
OVER 已认证
INVALID 已过期
  • incorrect 返回码介绍
字段 状态介绍
100 成功
101 认证记录不存在
  • flow 当前认证流程
字段 状态介绍
INIT 初始
AGREEMENT 认证协议
PERSION_VALID 个人核验
ENT_VALID 企业核验
WISH_VALID 意愿认证
SUCCESS 认证完成

# 四、成功示例

JSON示例

{
  "code": 0,
  "data": {
    "flow": "SUCCESS",
    "h5Url": "string",
    "incorrect": 100,
    "message": "string",
    "status": "OVER"
  },
  "message": "string",
  "requestId": "f1205..."
}

# 五、失败示例

JSON示例

{
    "code": 10001,
    "message": "系统错误",
    "requestId":"f1205..."
}

# 获取验证结果

根据 Token 获取验证结果,状态为 OVER 时,才能获取到认证结果。

# 一、请求说明

# 二、请求参数

名称 类型 是否必须 描述
authToken String 认证 Token

请求示例:

https://eapi.spiderid.cn/openapi/verification?
token=XXX
&<[公共请求参数]>

# 三、响应参数

data 结果信息 类型 描述
incorrect Integer 返回码 (详情见字段解释)
message String 对状态码的描述
verifyStatus String 验证状态 (详情见字段解释)
entName String 企业名称
uscCode String 统一社会信用代码
personType String 申办人员类型
larName String 法定代表人姓名
larIdcard String 法定代表人身份证号码
larMobile String 法定代表人手机号码
agnetName String 经办人姓名
agnetIdcard String 经办人身份证号码
agnetMobile String 经办人手机号码
larAuthzPdfUrl String 法定代表人授权书下载地址
bankNumber String 打款认证对公账户号
documents List 认证文档纸质材料
authEvc Object 认证流程事件证书信息
personAuthEvc Object 申请人身份认证事件证书信息
successTime Date 认证成功时间
expireTime Date 认证过期时间
  • 字段解释

  • incorrect 返回码介绍

字段 状态介绍
100 成功
101 认证记录不存在
102 认证状态非法
  • verifyStatus 验证状态
字段 状态介绍
OVER 完成认证
  • personType 申办人员类型
字段 字段解释
AGENT 经办人
LAR 法定代表人
  • WishAuthDocVO 认证文档纸质材料介绍 (documents 的对象)
字段 字段解释
name 文件名称
fileSize 文件大小
type 文件类型
url 文件地址
  • authEvc 认证流程事件证书信息
字段 字段解释
digest 摘要值
certificate 事件证书
signature 证书签名结果
  • personAuthEvc 申请人身份认证事件证书信息
字段 字段解释
digest 摘要值
certificate 事件证书
signature 证书签名结果

# 四、成功示例

JSON示例

{
  "code": 0,
  "data": {
    "agnetIdcard": "string",
    "agnetMobile": "string",
    "agnetName": "string",
    "authEvc": {
      "certificate": "string",
      "digest": "string",
      "signature": "string"
    },
    "bankNumber": "string",
    "documents": [
      {
        "fileSize": 0,
        "name": "string",
        "type": "string",
        "url": "string"
      }
    ],
    "entName": "string",
    "expireTime": "2021-01-22T08:15:14.497Z",
    "incorrect": 0,
    "larAuthzPdfUrl": "string",
    "larIdcard": "string",
    "larMobile": "string",
    "larName": "string",
    "message": "string",
    "personAuthEvc": {
      "certificate": "string",
      "digest": "string",
      "signature": "string"
    },
    "personType": "AGENT",
    "successTime": "2021-01-22T08:15:14.497Z",
    "uscCode": "string",
    "verifyStatus": "CREATE"
  },
  "message": "string",
  "requestId": "f1205..."
}

# 五、失败示例

JSON示例

{
  "code": 10001,
  "requestId":"f1205...",
  "message": "系统错误"
}

# SDK 请求示例

# 获取Token

        //提供的url
        String url = "http://eapi.spiderid.cn/openapi/verification";
        //您的appKey
        String appkey = "XXX";
        //您的appSecret
        String secretKey = "XXX";

        //1.原客户端
        ApiClient apiClient = new DefaultApiClient(url, appkey, secretKey);
        //2.调用出错,自动重试客户端
        //AutoRetryApiClient apiClient = new AutoRetryApiClient(url, appkey, secretKey); 

        VerificationEntGetTokenRequest req = new VerificationEntGetTokenRequest();

        //运行环境
        //SDK_ANDROID: 以SDK运行 支持Android
        //SDK_IOS: 以SDK运行  IOS
        req.setRunEnv("SDK_ANDROID");

        //服务器通知地址(选填)
        //req.setNotifyUrl("xxx");
        //办理人身份证号
        //req.setPersonIdcard("xxx");
        //办理人姓名
        //req.setPersonName("xxx");
        //办理人手机号码
        //req.setPersonMobile("xxx");

        try {
            VerificationEntGetTokenResponse response = apiClient.execute(req);
            //后续业务处理
            if(response.isSuccess()){
                // 接口调用正常
            }else{
                // 接口调用失败
                int errorCode = response.getErrorCode(); //获取失败状态码
                String msg = response.getMsg(); //获取错误消息
            }            
        } catch (ApiException e) {
            e.printStackTrace();
        }      

# 获取验证状态

        //提供的url
        String url = "http://eapi.spiderid.cn/openapi/verification";
        //您的appKey
        String appkey = "XXX";
        //您的appSecret
        String secretKey = "XXX";

        //1.原客户端
        ApiClient apiClient = new DefaultApiClient(url, appkey, secretKey);
        //2.调用出错,自动重试客户端
        //AutoRetryApiClient apiClient = new AutoRetryApiClient(url, appkey, secretKey);

        VerificationEntGetStatusRequest req = new VerificationEntGetStatusRequest();

        req.setAuthToken("XXX");

        try {
            VerificationEntGetStatusResponse response = apiClient.execute(req);
            //后续业务处理
            if(response.isSuccess()){
                // 接口调用正常
            }else{
                // 接口调用失败
                int errorCode = response.getErrorCode();//获取失败状态码
                String msg = response.getMsg();//获取错误消息
            }
        } catch (ApiException e) {
            e.printStackTrace();
        }

# 获取验证结果

        //提供的url
        String url = "http://eapi.spiderid.cn/openapi/verification";
        //您的appKey
        String appkey = "XXX";
        //您的appSecret
        String secretKey = "XXX";

        //1.原客户端
        ApiClient apiClient = new DefaultApiClient(url, appkey, secretKey);
        //2.调用出错,自动重试客户端
        //AutoRetryApiClient apiClient = new AutoRetryApiClient(url, appkey, secretKey);

        VerificationEntGetResultRequest req = new VerificationEntGetResultRequest();

        req.setAuthToken("XXX");

        try {
            VerificationEntGetResultResponse response = apiClient.execute(req);
            //后续业务处理
            if(response.isSuccess()){
                // 接口调用正常
            }else{
                // 接口调用失败
                int errorCode = response.getErrorCode();//获取失败状态码
                String msg = response.getMsg();//获取错误消息
            }
        } catch (ApiException e) {
            e.printStackTrace();
        }

# 回调通知接口示例

当慧眼实名认证流程结束后,平台服务器会向开发者提供的notifyUrl参数地址向开发者发送 HTTP 通知。平台服务器得到正确的确认信息后,会停止通知,避免重复向notifyUrl参数地址发送通知。

开发者在接收到参数时,需要对参数进行验签,在确认无误的情况下,向实名认证系统返回"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("t", request.getParameter("t"));
        paranMap.put("nonce", request.getParameter("nonce"));
        paranMap.put("v", request.getParameter("v"));
        paranMap.put("event", request.getParameter("event"));
        paranMap.put("signMethod", request.getParameter("signMethod"));
        paranMap.put("sign", request.getParameter("sign"));

        //业务参数
        paranMap.put("authToken", request.getParameter("authToken"));
        paranMap.put("verifyStatus", request.getParameter("verifyStatus"));

        String appSecret = "应用密钥";

        //验签
        boolean res = SignUtil.signCheck(paranMap, appSecret, SignUtil.CHARSET, request.getParameter("signMethod"));
        if (res) {
            //验签成功
            // 认证结果
            String verifyStatus = request.getParameter("verifyStatus");
            if("CREATE".equals(verifyStatus)){
                //未认证
            }else if("ACTION".equals(verifyStatus)){
                //认证中
            }else if("OVER".equals(verifyStatus)){
                //完成认证
                //提供的url
                String url = "http://eapi.spiderid.cn/openapi/verification";
                //您的appKey
                String appkey = "************";
                //您的appSecret
                String secretKey = "***********";
                
                //1.原客户端
                ApiClient apiClient = new DefaultApiClient(url, appkey, secretKey);
                try {
                    //获取认证结果
                    VerificationGetResultResponse response = apiClient.execute(request.getParameter("authToken"));
                    //后续业务处理
                    System.out.println(JSON.toJSONString(response));

                } catch (ApiException e) {
                    e.printStackTrace();
                }               
            }else if("INVALID".equals(verifyStatus)){
                //已失效
            }
            //返回通知成功
            return "success";
        }
        // 验签失败 可以给一个异常
        return "fail";
    }

# 验签工具类

package cn.unitid.witeye.verification.utils;

import com.google.common.collect.Maps;
import org.apache.commons.lang3.StringUtils;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

/**
 * @Classname SignUtil
 * @Description
 * @Date 2020/8/18
 * @Created by hyzheng
 */
public class SignUtils {
    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 appSecret
     * @param charset
     * @param signType
     * @return
     */
    public static boolean signCheck(Map<String, String> paranMap, String appSecret, 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(appSecret, content, charset, SIGN_TYPE_HMAC_SHA_1));
        } else {
            return sign.equals(computeSignature(appSecret, 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();
    }
}

# Android/IOS 认证流程说明

# Android/IOS 慧眼身份核验 SDK 链接如下:

Android 慧眼身份核验 SDK

IOS 慧眼身份核验 SDK

# Android/IOS 完整流程

# 第一步 获取 Android/IOS 认证入口

下载我们提供的 SDK,根据示例代码,设置运行环境为 Android/IOS,获取到 Android/IOS 认证会话入口

req.setRunEnv("SDK_ANDROID"); //设置运行环境为 Android
req.setRunEnv("SDK_IOS");  //设置运行环境为 IOS

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

{
    "code": 0,
    "message": "success",
    "requestId": "f1206006c0cb52faff0001276867",
    "data": {
        "authToken": "b7e19872c8cddb8ee60ed9296a7f8f916436b190062fbced91b7197b45bb1976",
        "expireTime": 1611660094231
    }
}

# 第二步 进行认证操作

将第一步获取到的authToken复制粘贴到身份核验页面的测试 Token 后面,点击企业认证进入首页

身份核验页面:

身份核验页面

进入首页后,勾选本人已阅读并同意,然后点击立即认证

认证首页

根据用户在获取 Android/IOS 认证入口所填写的参数,我们会返回以下页面:

  1. 用户首先进入短信验证页面,输入手机号,获取验证码,并选择用户身份。点击提交验证进入 OCR 识别页面。

短信验证页面

  1. OCR 识别页面(用户点击开始拍照或从相册上传,在身份证国徽面和人像面上传完成后,会自动将数据填充至下面的信息栏中,若识别信息不正确,用户可以自行修改,若识别信息正确,点击下一步,进入视频活体验证页面)。

ocr识别页面

拍照上传页面:

拍照上传页面

从相册上传页面:

从相册上传页面

  1. 视频活体验证页面(首先要确定为本人操作,然后根据页面提示完成视频活体录入,如果认证失败,可以提交人工审核,如果认证成功,进入企业信息登记页面)。

    确认为本人操作:

确认为本人操作页面

视频活体验证页面

  1. 企业信息登记页面(用户需要输入工商四要素进行核验,核验成功进入企业认证页面)。

企业信息登记页面

  1. 企业认证页面(根据应用配置,用户需要选择企业认证方式,根据不同的认证方式进入不同的认证页面)。

企业认证方式选择页面

若第一步选择的用户身份为法定代表人,则可以进行三类认证方式,分别为短信认证、对公打款、纸质材料上传。

若第一步选择的用户身份为经办人,则可以进行四类认证方式,分别为短信认证、对公打款、纸质材料上传、人脸活体检测(其中短信认证和人脸活体检测由法定代表人完成)。

  1. 短信认证页面

    用户若为经办人则需要输入法定代表人的手机号点击下一步,法定代表人会收到短信,查看授权书,然后根据页面提示获取验证码完成验证,验证通过后法定代表人对授权书进行签字;

    经办人短信认证页面:

经办人短信认证页面

法定代表人短信认证页面:

法定代表人短信认证页面

法定代表人设置手写笔记页面(用户需要在页面设置手写笔记)

法定代表人设置手写笔记页面

  1. 对公打款页面(用户需要确定自己知情并同意,然后输入对公打款账号,向银企直联打款,校验成功完成验证)。

对公打款输入账号页面

对公打款页面

  1. 纸质材料上传页面(用户需要根据页面提示上传对应的纸质材料,然后点击确认上传,上传完成需人工审核,审核成功完成验证)

纸质材料上传页面

  1. 人脸活体检测页面(经办人输入法定代表人的手机号码,法定代表人将收到短信,打开链接,查看授权书,法定代表人进行人脸活体检测,验证通过,法定代表人对授权书进行签字,完成验证)。

经办人发送短信页面(用户需要输入法定代表人手机号,应用会对法定代表人发送一条短信,进行活体认证)

经办人发送短信页面

法定代表人短信验证页面(经办人发送短信给法定代表人,法定代表人打开链接)

法定代表人短信验证页面

人脸活体检测授权书页面(用户获取到授权书之后点击下一步,进入人脸活体检测录制并上传页面)

人脸活体检测授权书页面

人脸活体检测录制页面(用户根据页面提示完成人脸活体检测,进入活体检测等待验证页面)

人脸活体检测录制页面

人脸活体检测法定代表人设置手写笔记页面(用户根据页面提示完成人脸活体检测,进入活体检测等待验证页面)

人脸活体检测法定代表人设置手写笔记页面

# 人工审核

个人实名认证:

若视频活体上传验证成功,进入核验,核验失败的情况下,可以申请人工审核。

活体检测人工审核提交成功页面:

活体检测人工审核提交成功页面

活体检测人工审核不通过页面:

活体检测人工审核不通过页面

纸质材料上传人工审核提交成功页面:

文件上传人工审核提交成功页面

纸质材料上传人工审核不通过页面:

纸质材料上传人工审核不通过页面

# 第三步 结果页面跳转

# 成功页面举例

用户身份证上传成功页面(用户上传个人的身份证)

用户身份证上传成功页面

纸质纸质材料上传成功页面(用户根据页面提示完成纸质材料上传)

纸质纸质材料上传成功页面

法定代表人签字授权成功页面(法定代表人根据收到的链接完成视频活体的验证授权,授权成功后会通过短信通知经办人)

法定代表人H5页面视频活体授权成功页面

企业认证成功页面

企业认证成功页面

# 错误页面举例

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

身份证识别失败页面:

身份证识别失败页面

身份活体识别验证失败页面:

身份活体识别验证失败页面

法定代表人H5页面身份活体识别验证失败页面:

法定代表人H5页面身份活体识别验证失败页面

经办人短信认证失败页面:

经办人短信认证失败页面

对公打款认证失败页面

对公打款认证失败页面

企业认证失败页面

企业认证失败页面

最后更新于: 1/28/2021, 5:16:04 PM