# 慧眼企业认证
# 业务介绍
移动端实名验证产品,主要的应用在 Android/IOS 场景中,提供了以下功能:
- 用户通过应用输入的手机号码,验证手机号码是否正确。
- 用户通过应用传入的身份证正反面,结构化识别证件照。
- 用户通过应用新录制一个视频,来进行活体检测的判断,并解析出人脸图片,并与上传的身份证上的人脸进行比对。
- 用户通过应用输入工商四要素进行核验,判断企业信息的真实性。
- 用户通过应用可以在四种企业认证方式中进行意愿认证。分别为短信认证、对公打款、纸质材料上传、人脸活体检测。
- 短信认证:用户通过应用输入法定代表人手机号进行运营商三要素认证,对法定代表人进行获取短信验证码验证。
- 对公打款:用户通过应用输入对公打款账号,然后线下进行对公打款,进行对公打款验证。
- 纸质材料上传:用户通过应用上传需要审核的公司纸质材料,后台进行纸质材料审核验证。
- 人脸活体检测:用户通过应用输入法定代表人手机号,对法定代表人进行人脸活体认证。
交互流程:
用户通过页面填写手机号,OCR 识别,视频活体验证,企业信息登记,企业意愿认证,认证完成。
这里为了方便您的理解,请点击如下链接查看演示:
# 获取token
根据传入的核验信息生成 Token
# 一、请求说明
请求地址:http://eapi.spiderid.cn/openapi/verification,https://eapi.spiderid.cn/openapi/verification
服务接口名称(即公共参数method的值): verification.ent.getToken
请求方式:GET,POST
# 二、请求参数
名称 | 类型 | 是否必须 | 描述 |
---|---|---|---|
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 验证状态
# 一、请求说明
请求地址:http://eapi.spiderid.cn/openapi/verification,https://eapi.spiderid.cn/openapi/verification
服务接口名称(即公共参数 method 的值): verification.ent.getStatus
请求方式:GET,POST
# 二、请求参数
名称 | 类型 | 是否必须 | 描述 |
---|---|---|---|
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 时,才能获取到认证结果。
# 一、请求说明
请求地址:http://eapi.spiderid.cn/openapi/verification,https://eapi.spiderid.cn/openapi/verification
服务接口名称(即公共参数method的值): verification.ent.getResult
请求方式:GET,POST
# 二、请求参数
名称 | 类型 | 是否必须 | 描述 |
---|---|---|---|
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/IOS 完整流程
# 第一步 获取 Android/IOS 认证入口
下载我们提供的 SDK,根据示例代码,设置运行环境为 Android/IOS,获取到 Android/IOS 认证会话入口
req.setRunEnv("SDK_ANDROID"); //设置运行环境为 Android
req.setRunEnv("SDK_IOS"); //设置运行环境为 IOS
{
"code": 0,
"message": "success",
"requestId": "f1206006c0cb52faff0001276867",
"data": {
"authToken": "b7e19872c8cddb8ee60ed9296a7f8f916436b190062fbced91b7197b45bb1976",
"expireTime": 1611660094231
}
}
# 第二步 进行认证操作
将第一步获取到的authToken
复制粘贴到身份核验页面的测试 Token 后面,点击企业认证进入首页
身份核验页面:
进入首页后,勾选本人已阅读并同意,然后点击立即认证
根据用户在获取 Android/IOS 认证入口所填写的参数,我们会返回以下页面:
- 用户首先进入短信验证页面,输入手机号,获取验证码,并选择用户身份。点击提交验证进入 OCR 识别页面。
- OCR 识别页面(用户点击开始拍照或从相册上传,在身份证国徽面和人像面上传完成后,会自动将数据填充至下面的信息栏中,若识别信息不正确,用户可以自行修改,若识别信息正确,点击下一步,进入视频活体验证页面)。
拍照上传页面:
从相册上传页面:
视频活体验证页面(首先要确定为本人操作,然后根据页面提示完成视频活体录入,如果认证失败,可以提交人工审核,如果认证成功,进入企业信息登记页面)。
确认为本人操作:
- 企业信息登记页面(用户需要输入工商四要素进行核验,核验成功进入企业认证页面)。
- 企业认证页面(根据应用配置,用户需要选择企业认证方式,根据不同的认证方式进入不同的认证页面)。
若第一步选择的用户身份为法定代表人,则可以进行三类认证方式,分别为短信认证、对公打款、纸质材料上传。
若第一步选择的用户身份为经办人,则可以进行四类认证方式,分别为短信认证、对公打款、纸质材料上传、人脸活体检测(其中短信认证和人脸活体检测由法定代表人完成)。
短信认证页面
用户若为经办人则需要输入法定代表人的手机号点击下一步,法定代表人会收到短信,查看授权书,然后根据页面提示获取验证码完成验证,验证通过后法定代表人对授权书进行签字;
经办人短信认证页面:
法定代表人短信认证页面:
法定代表人设置手写笔记页面(用户需要在页面设置手写笔记)
- 对公打款页面(用户需要确定自己知情并同意,然后输入对公打款账号,向银企直联打款,校验成功完成验证)。
- 纸质材料上传页面(用户需要根据页面提示上传对应的纸质材料,然后点击确认上传,上传完成需人工审核,审核成功完成验证)
- 人脸活体检测页面(经办人输入法定代表人的手机号码,法定代表人将收到短信,打开链接,查看授权书,法定代表人进行人脸活体检测,验证通过,法定代表人对授权书进行签字,完成验证)。
经办人发送短信页面(用户需要输入法定代表人手机号,应用会对法定代表人发送一条短信,进行活体认证)
法定代表人短信验证页面(经办人发送短信给法定代表人,法定代表人打开链接)
人脸活体检测授权书页面(用户获取到授权书之后点击下一步,进入人脸活体检测录制并上传页面)
人脸活体检测录制页面(用户根据页面提示完成人脸活体检测,进入活体检测等待验证页面)
人脸活体检测法定代表人设置手写笔记页面(用户根据页面提示完成人脸活体检测,进入活体检测等待验证页面)
# 人工审核
个人实名认证:
若视频活体上传验证成功,进入核验,核验失败的情况下,可以申请人工审核。
活体检测人工审核提交成功页面:
活体检测人工审核不通过页面:
纸质材料上传人工审核提交成功页面:
纸质材料上传人工审核不通过页面:
# 第三步 结果页面跳转
# 成功页面举例
用户身份证上传成功页面(用户上传个人的身份证)
纸质纸质材料上传成功页面(用户根据页面提示完成纸质材料上传)
法定代表人签字授权成功页面(法定代表人根据收到的链接完成视频活体的验证授权,授权成功后会通过短信通知经办人)
企业认证成功页面
# 错误页面举例
用户在操作过程中,可能会由于不当错误,到导致页面出现报错信息,我们提供几个较为常见的错误页面。
身份证识别失败页面:
身份活体识别验证失败页面:
法定代表人H5页面身份活体识别验证失败页面:
经办人短信认证失败页面:
对公打款认证失败页面
企业认证失败页面
← 慧眼个人认证