本文档主要介绍了通过HTTPS原生调用方式在服务端集成金融级实人认证的方法。

调用方式

服务请求地址:https://saf.cn-shanghai.aliyuncs.com

协议:HTTPS

方式:POST

发起认证请求

发起认证请求时,传入通用参数,并在ServiceParameters的json字符串里传入以下字段:

名称 类型 是否必选 描述 示例值
method String 发起认证请求的操作。

取值:init

init
sceneId String 认证场景ID,该ID在控制台创建认证场景后自动生成。 1000000006
outerOrderNo String 客户服务端自定义的业务唯一标识,用于后续定位排查问题时使用。值最长为32位长度的字母和数字组合,请确保唯一。 e0c34a77f5ac40a5aa5e6ed20c353888
bizCode String 认证场景码和商户发起认证的接入端有关:
  • 当商户在iOS或安卓平台发起认证时,认证场景码为FACE_SDK
  • 当商户在小程序中H5页面中发起认证时,认证场景码为FACE
FACE_SDK
identityType String 身份信息的参数类型,必须传入CERT_INFO。 CERT_INFO
certType String 证件类型。当前支持身份证,必须传入IDENTITY_CARD。 IDENTITY_CARD
certNo String 用户身份证件号码。 330103xxxxxxxxxxxx
certName String 用户姓名。 张三
returnUrl String 商户业务页面回调的目标地址。
  • 如您不需要回调商户业务页面,您可以在此处传入空字符串。
  • 当您采用端外唤起支付宝认证页面接入,且希望您的用户唤起支付宝完成认证后,能够跳回您的应用页面,您需要在此参数下传入您应用的Scheme。详细内容请参见回跳原应用
https://www.aliyun.com

发起认证请求后,您会收到返回信息。返回参数列表如下:

名称 类型 是否必选 描述 示例值
certifyId String 认证ID,刷脸认证的唯一标识。 7eff3ad26a9c7b68c511b9f35eb1a354
certifyUrl String 认证流程入口Url。 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": "7eff3ad26a9c7b68c511b9f35eb1a354"
    },
    "message": "OK"
}

查询认证结果

查询认证结果时,传入通用参数,并在ServiceParameters的json字符串里传入以下字段:

名称 类型 是否必选 描述 示例值
method String 查询人脸比对结果的操作。

取值:query

query
certifyId String 认证ID,需与发起认证请求时返回的certifyId保持一致。 7eff3ad26a9c7b68c511b9f35eb1a354
sceneId String 认证场景ID,需与发起认证请求时的sceneId保持一致。 1000000006

发起查询请求后,您会收到返回信息。返回参数列表如下:

名称 类型 是否必选 描述 示例值
passed String 是否通过认证。取值:
  • T:认证通过。
  • F:未通过认证。
T
identityInfo String 认证的主体信息,一般的认证场景返回为空。 "IdentityInfo": "{"cert_type":"IDENTITY_CARD","cert_no":"330103xxxxxxxxxxxx","cert_name":"张三"}"
materialInfo String 认证主体附件信息,主要为图片类材料,一般的认证场景都是返回空。 "MaterialInfo": "{\"facial_picture_front\":{\"verify_score\":\"80.107902181204\",\"quality_score\":\"82\",\"FEATURE_FACE\":\"/9j/4AAQSkZJRgABAQAASABIAAD/4QBMRX\"}}"

通用参数

使用HTTPS方式在服务端调用金融级实人认证时,您必须传入以下通用参数:
名称 类型 是否必选 描述
Format String 返回值的类型。取值:
  • JSON(默认值)
  • XML
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。

例如,2014-7-29T12:00:00Z(为北京时间2014年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 认证场景配置不存在。
405
  • 身份认证记录不存在。
  • 您的年龄未满14周岁,不允许使用此产品。
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>
调用接口发起认证和查询认证结果:
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 {
        //阿里云账号的appKey和appSecret。
        String appKey = "<accessKey>";
        String appSecret = "<accessSecret>";
        String safUrl = "https://saf.cn-shanghai.aliyuncs.com";

        //参数。
        Map<String, String> serviceParameters = new HashMap<String, String>();
        //发起认证请求。
        serviceParams.put("method",  "init");
        serviceParams.put("sceneId",  "1000000006");
        serviceParams.put("outerOrderNo",  "e0c34a77f5ac40a5aa5e6ed20c353888");
        serviceParams.put("bizCode", "FACE");
        serviceParams.put("identityType", "CERT_INFO");
        serviceParams.put("certType", "IDENTITY_CARD");
        serviceParams.put("certNo", "330103xxxxxxxxxxxx");
        serviceParams.put("certName", "张三");
        serviceParams.put("returnUrl", "https://www.aliyun.com");

        /*
        // 查询认证结果。
        serviceParams.put("method",  "query");
        serviceParams.put("certifyId", "7eff3ad26a9c7b68c511b9f35eb1a354");
        serviceParams.put("sceneId", "1000000006");
        */

        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;
    }
}