You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

112 lines
4.0 KiB

package idverify
import (
"context"
"encoding/json"
"fmt"
"net/http"
"strings"
"time"
"github.com/aliyun/alibaba-cloud-sdk-go/sdk"
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/credentials"
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
)
// 阿里云 实人认证 - 身份二要素核验 Id2MetaVerify
// 文档:https://help.aliyun.com/zh/id-verification/information-verification/developer-reference/vatsl9lfmbwe74iv
//
// Action Id2MetaVerify
// Version 2019-03-07
// Domain cloudauth.aliyuncs.com(也可用 cloudauth.cn-beijing / cn-shanghai.aliyuncs.com)
// 入参 ParamType(normal|sm2) / UserName(姓名) / IdentifyNum(身份证号)
// 出参 Code(200 成功) / Message / ResultObject.BizCode(1=一致,2=不一致)
const (
aliyunDefaultRegion = "cn-shanghai"
aliyunDefaultDomain = "cloudauth.aliyuncs.com"
aliyunAPIVersion = "2019-03-07"
aliyunAction = "Id2MetaVerify"
aliyunBizCodeMatched = "1" // 校验一致
aliyunBizCodeMismatch = "2" // 校验不一致
)
type aliyunVerifier struct {
client *sdk.Client
domain string
}
// aliyunResp 只取需要的字段;其余忽略。
type aliyunResp struct {
RequestId string `json:"RequestId"`
Code string `json:"Code"`
Message string `json:"Message"`
ResultObject struct {
BizCode string `json:"BizCode"`
} `json:"ResultObject"`
}
func newAliyun(fields map[string]string) (Verifier, error) {
if err := requireFields(fields, "access_key_id", "access_key_secret"); err != nil {
return nil, err
}
region := field(fields, "region")
if region == "" {
region = aliyunDefaultRegion
}
domain := field(fields, "domain")
if domain == "" {
domain = aliyunDefaultDomain
}
c := sdk.NewConfig()
c.HttpTransport = &http.Transport{IdleConnTimeout: 10 * time.Second}
c.Timeout = 10 * time.Second
cred := credentials.NewAccessKeyCredential(field(fields, "access_key_id"), field(fields, "access_key_secret"))
client, err := sdk.NewClientWithOptions(region, c, cred)
if err != nil {
return nil, fmt.Errorf("idverify/aliyun: 初始化客户端失败: %w", err)
}
return &aliyunVerifier{client: client, domain: domain}, nil
}
func (v *aliyunVerifier) Provider() string { return ProviderAliyun }
func (v *aliyunVerifier) Verify(ctx context.Context, realname, idcardno string) (Result, error) {
req := requests.NewCommonRequest()
req.Method = http.MethodPost
// ⚠️ 必须显式设成 HTTPS:NewCommonRequest 默认走 HTTP,而 Cloudauth 强制 SSL,
// 用 HTTP 调会被拒并返回 InvalidProtocol.NeedSsl("lack of ssl protect"),
// 表象是一个笼统的 SDK.ServerError,很容易被误当成凭据或额度问题。
req.Scheme = "https"
req.Domain = v.domain
req.Version = aliyunAPIVersion
req.ApiName = aliyunAction
req.QueryParams["ParamType"] = "normal" // 明文传参;sm2 需另配国密公钥,当前不启用
req.QueryParams["UserName"] = strings.TrimSpace(realname)
req.QueryParams["IdentifyNum"] = strings.TrimSpace(idcardno)
resp, err := v.client.ProcessCommonRequest(req)
if err != nil {
return Result{}, fmt.Errorf("idverify/aliyun: 调用失败: %w", err)
}
body := resp.GetHttpContentString()
var r aliyunResp
if err := json.Unmarshal([]byte(body), &r); err != nil {
return Result{}, fmt.Errorf("idverify/aliyun: 响应解析失败: %w", err)
}
// Code 非 200 表示调用层面失败(鉴权/参数/额度),结论未知——绝不能当成"不一致"。
if r.Code != "200" {
return Result{}, fmt.Errorf("idverify/aliyun: 调用返回 Code=%s Message=%s RequestId=%s", r.Code, r.Message, r.RequestId)
}
switch r.ResultObject.BizCode {
case aliyunBizCodeMatched:
return Result{Matched: true, BizCode: r.ResultObject.BizCode, Message: r.Message}, nil
case aliyunBizCodeMismatch:
return Result{Matched: false, BizCode: r.ResultObject.BizCode, Message: r.Message}, nil
default:
// 文档只定义了 1/2;出现别的值说明接口有变更,按"结论未知"处理而不是默默判不一致。
return Result{}, fmt.Errorf("idverify/aliyun: 未知 BizCode=%q RequestId=%s", r.ResultObject.BizCode, r.RequestId)
}
}