211 lines
8.1 KiB
C#
211 lines
8.1 KiB
C#
using Microsoft.Extensions.Logging;
|
||
using Microsoft.Extensions.Options;
|
||
using System.Security.Cryptography;
|
||
using System.Text;
|
||
using System.Text.Json;
|
||
|
||
namespace XiangYi.Infrastructure.RealName;
|
||
|
||
/// <summary>
|
||
/// 阿里云实名认证服务实现
|
||
/// 使用阿里云身份证二要素核验API(纯服务端接入)
|
||
/// </summary>
|
||
public class AliyunRealNameProvider : IRealNameProvider
|
||
{
|
||
private readonly AliyunRealNameOptions _options;
|
||
private readonly ILogger<AliyunRealNameProvider> _logger;
|
||
private readonly HttpClient _httpClient;
|
||
|
||
// 身份证二要素核验API
|
||
private const string IdCardVerifyHost = "cloudauth.aliyuncs.com";
|
||
private const string ApiVersion = "2019-03-07";
|
||
|
||
public AliyunRealNameProvider(
|
||
IOptions<RealNameOptions> options,
|
||
IHttpClientFactory httpClientFactory,
|
||
ILogger<AliyunRealNameProvider> logger)
|
||
{
|
||
_options = options.Value.Aliyun ?? new AliyunRealNameOptions();
|
||
_logger = logger;
|
||
_httpClient = httpClientFactory.CreateClient("AliyunRealName");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 身份证二要素验证(姓名+身份证号)
|
||
/// </summary>
|
||
public async Task<RealNameResult> VerifyIdCardAsync(string name, string idCard)
|
||
{
|
||
try
|
||
{
|
||
// 验证配置
|
||
if (string.IsNullOrEmpty(_options.AccessKeyId))
|
||
{
|
||
_logger.LogError("阿里云实名认证配置错误: AccessKeyId 为空");
|
||
return RealNameResult.Fail("CONFIG_ERROR", "实名认证服务配置错误,请联系管理员");
|
||
}
|
||
if (string.IsNullOrEmpty(_options.AccessKeySecret))
|
||
{
|
||
_logger.LogError("阿里云实名认证配置错误: AccessKeySecret 为空");
|
||
return RealNameResult.Fail("CONFIG_ERROR", "实名认证服务配置错误,请联系管理员");
|
||
}
|
||
|
||
_logger.LogInformation("阿里云实名认证配置: AccessKeyId={AccessKeyId}, SecretLength={SecretLength}",
|
||
_options.AccessKeyId[..8] + "***",
|
||
_options.AccessKeySecret?.Length ?? 0);
|
||
|
||
var parameters = new SortedDictionary<string, string>
|
||
{
|
||
{ "Action", "Id2MetaVerify" },
|
||
{ "Version", ApiVersion },
|
||
{ "Format", "JSON" },
|
||
{ "AccessKeyId", _options.AccessKeyId },
|
||
{ "SignatureMethod", "HMAC-SHA1" },
|
||
{ "Timestamp", DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ") },
|
||
{ "SignatureVersion", "1.0" },
|
||
{ "SignatureNonce", Guid.NewGuid().ToString("N") },
|
||
{ "ParamType", "normal" },
|
||
{ "IdentifyNum", idCard },
|
||
{ "UserName", name }
|
||
};
|
||
|
||
var signature = GenerateSignature(parameters);
|
||
parameters.Add("Signature", signature);
|
||
|
||
var queryString = string.Join("&", parameters.Select(p => $"{Uri.EscapeDataString(p.Key)}={Uri.EscapeDataString(p.Value)}"));
|
||
var requestUrl = $"https://{IdCardVerifyHost}/?{queryString}";
|
||
|
||
_logger.LogInformation("阿里云实名认证请求: {Url}", requestUrl.Replace(_options.AccessKeyId, "***"));
|
||
|
||
var response = await _httpClient.GetAsync(requestUrl);
|
||
var content = await response.Content.ReadAsStringAsync();
|
||
|
||
_logger.LogInformation("阿里云实名认证响应: {Response}", content);
|
||
|
||
var result = JsonSerializer.Deserialize<AliyunIdVerifyResponse>(content, new JsonSerializerOptions
|
||
{
|
||
PropertyNameCaseInsensitive = true
|
||
});
|
||
|
||
if (result?.Code == "200" && result.ResultObject?.BizCode == "1")
|
||
{
|
||
_logger.LogInformation("阿里云身份证二要素验证通过: {IdCard}", MaskIdCard(idCard));
|
||
return RealNameResult.Success(result.RequestId);
|
||
}
|
||
|
||
var errorMsg = GetErrorMessage(result?.ResultObject?.BizCode, result?.Message);
|
||
_logger.LogWarning("阿里云身份证二要素验证失败: {IdCard}, BizCode: {BizCode}, Error: {Error}",
|
||
MaskIdCard(idCard), result?.ResultObject?.BizCode, errorMsg);
|
||
|
||
return RealNameResult.Fail(
|
||
result?.ResultObject?.BizCode ?? result?.Code ?? "UNKNOWN",
|
||
errorMsg,
|
||
result?.RequestId);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "阿里云身份证二要素验证异常: {IdCard}", MaskIdCard(idCard));
|
||
return RealNameResult.Fail("EXCEPTION", ex.Message);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 身份证三要素验证(阿里云纯API模式不支持,返回失败)
|
||
/// </summary>
|
||
public Task<RealNameResult> VerifyWithPhotoAsync(string name, string idCard, string photoBase64)
|
||
{
|
||
_logger.LogWarning("阿里云纯API模式不支持身份证三要素验证(人脸比对)");
|
||
return Task.FromResult(RealNameResult.Fail("NOT_SUPPORTED", "当前配置不支持人脸比对验证,请使用二要素验证"));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 身份证OCR识别(正面)- 阿里云纯API模式不支持
|
||
/// </summary>
|
||
public Task<IdCardOcrResult> OcrIdCardFrontAsync(string imageBase64)
|
||
{
|
||
_logger.LogWarning("阿里云纯API模式不支持身份证OCR识别");
|
||
return Task.FromResult(IdCardOcrResult.Fail("NOT_SUPPORTED", "当前配置不支持身份证OCR识别,请手动输入身份信息"));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 身份证OCR识别(反面)- 阿里云纯API模式不支持
|
||
/// </summary>
|
||
public Task<IdCardOcrResult> OcrIdCardBackAsync(string imageBase64)
|
||
{
|
||
_logger.LogWarning("阿里云纯API模式不支持身份证OCR识别");
|
||
return Task.FromResult(IdCardOcrResult.Fail("NOT_SUPPORTED", "当前配置不支持身份证OCR识别,请手动输入身份信息"));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成阿里云API签名
|
||
/// </summary>
|
||
private string GenerateSignature(SortedDictionary<string, string> parameters)
|
||
{
|
||
var canonicalizedQueryString = string.Join("&",
|
||
parameters.Select(p => $"{PercentEncode(p.Key)}={PercentEncode(p.Value)}"));
|
||
|
||
var stringToSign = $"GET&%2F&{PercentEncode(canonicalizedQueryString)}";
|
||
|
||
// 确保 AccessKeySecret 不为空
|
||
var secretKey = _options.AccessKeySecret ?? string.Empty;
|
||
using var hmac = new HMACSHA1(Encoding.UTF8.GetBytes(secretKey + "&"));
|
||
var hashBytes = hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign));
|
||
return Convert.ToBase64String(hashBytes);
|
||
}
|
||
|
||
/// <summary>
|
||
/// URL编码(阿里云特殊规则)
|
||
/// </summary>
|
||
private static string PercentEncode(string value)
|
||
{
|
||
if (string.IsNullOrEmpty(value))
|
||
return string.Empty;
|
||
|
||
var encoded = Uri.EscapeDataString(value);
|
||
// 阿里云特殊编码规则
|
||
encoded = encoded.Replace("+", "%20")
|
||
.Replace("*", "%2A")
|
||
.Replace("%7E", "~");
|
||
return encoded;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取错误信息
|
||
/// </summary>
|
||
private static string GetErrorMessage(string? bizCode, string? message)
|
||
{
|
||
return bizCode switch
|
||
{
|
||
"1" => "验证通过",
|
||
"2" => "姓名与身份证号不一致",
|
||
"3" => "身份证号不存在",
|
||
"4" => "身份证号格式错误",
|
||
"5" => "服务异常,请稍后重试",
|
||
_ => message ?? "验证失败,请检查身份信息是否正确"
|
||
};
|
||
}
|
||
|
||
private static string MaskIdCard(string idCard)
|
||
{
|
||
if (string.IsNullOrEmpty(idCard) || idCard.Length < 10)
|
||
return "***";
|
||
return $"{idCard[..4]}**********{idCard[^4..]}";
|
||
}
|
||
|
||
#region 响应模型
|
||
|
||
private class AliyunIdVerifyResponse
|
||
{
|
||
public string? Code { get; set; }
|
||
public string? Message { get; set; }
|
||
public string? RequestId { get; set; }
|
||
public AliyunIdVerifyResultObject? ResultObject { get; set; }
|
||
}
|
||
|
||
private class AliyunIdVerifyResultObject
|
||
{
|
||
public string? BizCode { get; set; }
|
||
}
|
||
|
||
#endregion
|
||
}
|