HTTPS原生调用

重要

本文中含有需要您注意的重要提示信息,忽略该信息可能对您的业务造成影响,请务必仔细阅读。

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

调用方式

  • 服务接入点:https://saf.cn-shanghai.aliyuncs.com

  • 传输协议:HTTPS

  • 请求方式:POST

  • QPS限量:API独享QPS限量,详情请参见服务端接口QPS限量说明

发起认证请求

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

名称

类型

是否必选

描述

示例值

method

String

发起认证请求的操作。

取值:init

init

sceneId

String

认证场景ID,该ID在控制台创建认证场景后自动生成。关于如何创建认证场景,请参见添加认证场景

100000****

outerOrderNo

String

客户服务端自定义的业务唯一标识,用于后续定位排查问题时使用。该值长度为1~32个字符,支持数字和大小写英文字母,请确保唯一。

e0c34a77f5ac40a5aa5e6ed20c35****

bizCode

String

认证场景码和商户发起认证的接入端有关:

  • 当商户在iOS或安卓平台发起认证时,认证场景码为FACE_SDK

  • 当商户在小程序中H5页面中发起认证时,认证场景码为FACE

FACE_SDK

identityType

String

身份信息的参数类型,必须传入CERT_INFO。

CERT_INFO

certType

String

用户证件类型。支持的证件类型,请参见方案概述

不同证件类型,取值均为IDENTITY_CARD

IDENTITY_CARD

certNo

String

用户身份证件号码。

1******************9

certName

String

用户姓名。

张先生

returnUrl

String

商户业务页面回调的目标地址。

  • 如您不需要回调商户业务页面,您可以在此处传入空字符串。

  • 当您采用端外唤起支付宝H5页面,且希望您的用户唤起支付宝完成认证后,能够跳回您的应用页面,您需要在此参数下传入您应用的Scheme。详细内容请参见回跳原应用

https://www.aliyun.com

encryptType

String

传入加密算法,目前仅支持SM2国密算法。

开启加密传输后,需传入加密后的CertNameCertNo。如何加密,请参见参数加密说明

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:认证通过。

  • F:未通过认证。

T

identityInfo

String

预留字段,默认返回为空。

materialInfo

String

预留字段,默认返回为空。

通用参数

使用HTTPS方式在服务端调用金融级实人认证时,您必须传入以下通用参数:

名称

类型

是否必选

描述

Format

String

返回值的类型。取值:

  • JSON(默认值)

  • XML

Version

String

API版本号,使用日期格式:YYYY-MM-DD。取值:2017-03-31

AccessKeyId

String

阿里云颁发给用户的访问服务所用的密钥ID,请参见创建AccessKey

Signature

String

签名结果串,关于签名的计算方法,请参见接入金融级实人认证服务

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