本文中含有需要您注意的重要提示信息,忽略该信息可能对您的业务造成影响,请务必仔细阅读。
本文主要介绍通过HTTPS原生调用方式在服务端集成金融级实人认证的方法。
调用方式
服务接入点:
https://saf.cn-shanghai.aliyuncs.com
传输协议:HTTPS
请求方式:POST
发起认证请求
发起认证请求时,传入通用参数,并在ServiceParameters的JSON字符串里传入以下字段:
名称 | 类型 | 是否必选 | 描述 | 示例值 |
method | String | 是 | 发起认证请求的操作。 取值:init。 | init |
sceneId | String | 是 | 认证场景ID,该ID在控制台创建认证场景后自动生成。关于如何创建认证场景,请参见添加认证场景。 | 100000**** |
outerOrderNo | String | 是 | 客户服务端自定义的业务唯一标识,用于后续定位排查问题时使用。该值长度为1~32个字符,支持数字和大小写英文字母,请确保唯一。 | e0c34a77f5ac40a5aa5e6ed20c35**** |
bizCode | String | 是 | 认证场景码和商户发起认证的接入端有关:
| FACE_SDK |
identityType | String | 是 | 身份信息的参数类型,必须传入CERT_INFO。 | CERT_INFO |
certType | String | 是 | 用户证件类型。支持的证件类型,请参见方案概述。 不同证件类型,取值均为IDENTITY_CARD。 | IDENTITY_CARD |
certNo | String | 是 | 用户身份证件号码。 | 1******************9 |
certName | String | 是 | 用户姓名。 | 张先生 |
returnUrl | String | 是 | 商户业务页面回调的目标地址。
| https://www.aliyun.com |
encryptType | String | 否 | 传入加密算法,目前仅支持SM2国密算法。 开启加密传输后,需传入加密后的CertName和CertNo。如何加密,请参见参数加密说明。 | SM2 |
发起认证请求后,您会收到返回信息。返回参数列表如下:
名称 | 类型 | 是否必选 | 描述 | 示例值 |
certifyId | String | 是 | 认证ID,刷脸认证的唯一标识。 警告 CertifyId字段为计费统计字段,为了方便后续核对账单,请您在本地留存该字段信息。 初始化接口返回的认证CertifyId在30分钟有效且仅能认证提交一次,请您在有效期内应用,避免重复使用。 | 7eff3ad26a9c7b68c511b9f35eb1**** |
certifyUrl | String | 是 | 认证流程入口Url。 重要 初始化接口返回的认证CertifyUrl在30分钟有效且仅认证仅能提交一次,请您在有效期内使用,避免重复使用。 | https://picker.antcloudauth.aliyuncs.com/gateway.do?... |
返回示例如下:
{
"code": 200,
"requestId": "5CD69C04-8A6B-4CC3-A0C9-D18D5FEFA788",
"data": {
"certifyUrl": "https://picker.antcloudauth.aliyuncs.com/gateway.do?...",
"certifyId": "7eff3ad26a9c7b68c511b9f35eb1****"
},
"message": "OK"
}
查询认证结果
查询认证结果时,传入通用参数,并在ServiceParameters的JSON字符串里传入以下字段:
名称 | 类型 | 是否必选 | 描述 | 示例值 |
method | String | 是 | 查询人脸比对结果的操作。 取值:query。 | query |
certifyId | String | 是 | 认证ID,需与发起认证请求时返回的certifyId保持一致。 | 7eff3ad26a9c7b68c511b9f35eb1**** |
sceneId | String | 是 | 认证场景ID,需与发起认证请求时的sceneId保持一致。 | 100000***** |
encToken | String | 否 | 公钥加密后的AES 256密钥(Base64编码),用于获取加密图片。仅面向持牌金融客户KYC留存的认证图片。如何使用RSA公钥加密AES密钥,请参见支付宝小程序或H5方案密钥加密说明。 | l1tmZUQKdclKdGHzy2hljqltxPYPRo42JjRL04JVHxh9wWP19xHZpVvlKy9lxXRXCiWTKfJAaDfw4pKlZfetvZHdVdjroiXaLt7jx68rpp24wCeWo8wgjMF1lBwJmYZGcTrCVJ+Tonn7CHN7ur11Pn9d4IdkbTi5rBoOx8JITTn6krEH6ssZ5Cj3xFJ4/3PyvloU8UX+FQIPpskYR36jHcqs+Nt4EeTK/wWsAQItl6RfI9FN+8UJ42RVNt/IWHuk3U9aQNxzt7Z+7hbvqpBf/uKp4sgP6fbyYhZ+2tM+KXF1ODOYGenQ65wliaS/C1fTqjUJYcONMEW61Te/de4r4A== |
发起查询请求后,您会收到返回信息。返回参数列表如下:
名称 | 类型 | 是否必选 | 描述 | 示例值 |
passed | String | 是 | 是否通过认证。取值:
| T |
identityInfo | String | 否 | 预留字段,默认返回为空。 | 无 |
materialInfo | String | 否 | 预留字段,默认返回为空。 | 无 |
通用参数
使用HTTPS方式在服务端调用金融级实人认证时,您必须传入以下通用参数:
名称 | 类型 | 是否必选 | 描述 |
Format | String | 是 | 返回值的类型。取值:
|
Version | String | 是 | API版本号,使用日期格式:YYYY-MM-DD。取值:2017-03-31。 |
AccessKeyId | String | 是 | 阿里云颁发给用户的访问服务所用的密钥ID,请参见创建AccessKey。 |
Signature | String | 是 | 签名结果串,关于签名的计算方法,请参见API签名机制。 |
SignatureMethod | String | 是 | 签名方式,取值:HMAC-SHA1。 |
Timestamp | String | 是 | 请求的时间戳。日期格式按照ISO8601标准表示,并需要使用UTC时间。格式:YYYY-MM-DDThh:mm:ssZ。 例如,2022-7-29T12:00:00Z(为北京时间2022年7月29日的20点0分0秒)。 |
SignatureVersion | String | 是 | 签名算法版本,目前版本是1.0。 |
SignatureNonce | String | 是 | 唯一随机数,用于防止网络重放攻击。用户在不同请求间要使用不同的随机数值。 |
Action | String | 是 | 取值必须是ExecuteRequest。 |
Service | String | 是 | 具体服务名,本服务该参数固定为fin_face_verify。 |
ServiceParameters | String | 是 | 具体服务参数的JSON格式串。具体定义见上文各接口参数定义。 |
通过调用不同服务实现不同功能时,实际是通过传入对应服务的ServiceParameters来实现的。
调用服务后,您会收到返回码。以下是返回码列表:
Code | Message |
200 | 成功。 |
401 | 参数非法。 |
402 | 应用配置不存在。 |
403 | 主账号*******,无权调用fin_face_verify服务,或服务已到期、已到最大流量、未购买服务。 |
404 | 认证场景配置不存在。 |
406 | 无效的certifyId。 |
407 | 认证已失效。 |
408 | 开放认证单据已失效。 |
501 | 系统错误。 |
502 | 系统繁忙。 |
503 | 系统错误。 |
完整代码示例(Java语言)
添加POM依赖:
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>19.0</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.2</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>credentials-java</artifactId>
<version>0.2.11</version>
</dependency>
调用接口发起认证和查询认证结果:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.SimpleTimeZone;
import java.util.UUID;
import com.google.common.io.BaseEncoding;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.message.BasicNameValuePair;
public class SafTest {
public static void main(String[] args) throws Throwable {
// 阿里云账号AccessKey拥有所有API的访问权限,建议您使用RAM用户进行API访问或日常运维。
// 强烈建议不要把AccessKey ID和AccessKey Secret保存到工程代码里,否则可能导致AccessKey泄露,威胁您账号下所有资源的安全。
// 本示例通过阿里云Credentials工具从环境变量中读取AccessKey,来实现API访问的身份验证。如何配置环境变量,请参见https://help.aliyun.com/document_detail/378657.html。
com.aliyun.credentials.Client credentialClient = new com.aliyun.credentials.Client();
String appKey = credentialClient.getAccessKeyId();
String appSecret = credentialClient.getAccessKeySecret();
//参数。
Map<String, String> serviceParameters = new HashMap<String, String>();
//发起认证请求。
serviceParameters.put("method", "init");
serviceParameters.put("sceneId", "100000****");
serviceParameters.put("outerOrderNo", "e0c34a77f5ac40a5aa5e6ed20c35****");
serviceParameters.put("bizCode", "FACE");
serviceParameters.put("identityType", "CERT_INFO");
serviceParameters.put("certType", "IDENTITY_CARD");
serviceParameters.put("certNo", "1******************9");
serviceParameters.put("certName", "张先生");
serviceParameters.put("returnUrl", "https://www.aliyun.com");
/*
// 如需开启个人信息加密传输。
serviceParameters.put("encryptType", "SM2");
serviceParameters.put("certNo", "BMjsstxK3S4b1YH*****Pet8ECObfxmLN92SLsNg==");
serviceParameters.put("certName", "BCRD/7ZkNy7Q*****M1BMBezZe8GaYHrLwyJv558w==");
*/
/*
查询认证结果。
serviceParameters.put("method", "query");
serviceParameters.put("certifyId", "7eff3ad26a9c7b68c511b9f35eb1****");
serviceParameters.put("sceneId", "100000****");
*/
Map<String, String> params = new HashMap<>();
// 接口业务参数-固定为fin_face_verify。
params.put("Service", "fin_face_verify");
// 附加上业务详细参数,服务详细参数,具体见文档里的业务参数部分,业务参数整体转换为JSON格式。
params.put("ServiceParameters", com.alibaba.fastjson.JSON.toJSONString(serviceParameters));
doPOPRequest(appKey, appSecret, safUrl, "ExecuteRequest", "2017-03-31", params);
}
public static String doPOPRequest(String appKey, String appSecret, String url, String action, String version, Map<String, String> parameters) throws Throwable {
String encoding = "UTF-8";
Map<String, String> paramsForPOP = new HashMap<>();
// 公共参数-固定。
paramsForPOP.put("AccessKeyId", appKey);
paramsForPOP.put("Format", "JSON");// 固定。
paramsForPOP.put("SignatureMethod", "HMAC-SHA1");// 固定。
paramsForPOP.put("Timestamp", formatIso8601Date(new Date()));// 注意格式是:YYYY-MM-DDThh:mm:ssZ。
paramsForPOP.put("SignatureVersion", "1.0");// 固定。
paramsForPOP.put("SignatureNonce", UUID.randomUUID().toString());// 自行生成一个随机串,每次请求不可重复。
paramsForPOP.put("Version", version);// 固定。
paramsForPOP.put("Action", action);
if (parameters != null) {
parameters.remove("Signature");
paramsForPOP.putAll(parameters);
}
String signature = computeSignature(paramsForPOP, appSecret, encoding);
paramsForPOP.put("Signature", signature);// 把签名附加到post参数里。
String result = httpPost(url, paramsForPOP, encoding);
// 返回值。
System.out.println(result);
return result;
}
private static String httpPost(String url, Map<String, String> paramsForPOP, String encoding) throws IOException {
CloseableHttpClient httpClient = HttpClientBuilder.create().build();
HttpPost httpPost = new HttpPost(url);
List<NameValuePair> urlParameters = new ArrayList<NameValuePair>();
for (Entry<String, String> entry: paramsForPOP.entrySet()) {
urlParameters.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
}
httpPost.setEntity(new UrlEncodedFormEntity(urlParameters, Consts.UTF_8));
CloseableHttpResponse response = httpClient.execute(httpPost);
BufferedReader rd = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
StringBuffer result = new StringBuffer();
String line = "";
while ((line = rd.readLine()) != null) {
result.append(line);
}
return result.toString();
}
private static String formatIso8601Date(Date date) {
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
df.setTimeZone(new SimpleTimeZone(0, "GMT"));// 注意使用GMT时间。
return df.format(date);
}
public static String computeSignature(Map<String, String> parameters, String secret, String encoding)
throws Exception {
// 将参数Key按字典顺序排序。
String[] sortedKeys = parameters.keySet().toArray(new String[] {});
Arrays.sort(sortedKeys);
String separator = "&";
boolean first = true;
// 生成规范化请求字符串。
StringBuilder sb = new StringBuilder();
for (String key : sortedKeys) {
if (first) {
first = false;
} else {
sb.append("&");
}
sb.append(encode(key, encoding)).append("=").append(encode(parameters.get(key), encoding));
}
// 生成用于计算签名的字符串toSign。
StringBuilder toSign = new StringBuilder();
toSign.append("POST").append(separator);
toSign.append(encode("/", encoding)).append(separator);
toSign.append(encode(sb.toString(), encoding));
return getSignature("HmacSHA1", (secret + separator).getBytes(encoding), toSign.toString().getBytes(encoding));
}
private static String encode(String value, String encoding) throws UnsupportedEncodingException {
if (value == null) { return null; }
// 使用URLEncoder.encode编码后,将"+","*","%7E"做替换即满足 API规定的编码规范。
return URLEncoder.encode(value, encoding).replace("+", "%20").replace("*", "%2A").replace("%7E", "~");
}
public static String getSignature(String algorithm, byte[] secret, byte[] data) {
try {
javax.crypto.Mac mac = javax.crypto.Mac.getInstance(algorithm);
mac.init(new javax.crypto.spec.SecretKeySpec(secret, algorithm));
return BaseEncoding.base64().encode(mac.doFinal(data));
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}