xiangyixiangqin/server/src/XiangYi.Infrastructure/RealName/AliyunRealNameProvider.cs
2026-01-29 02:44:15 +08:00

211 lines
8.1 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}