using Common.Shared.Application.DaHua;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Security;
using System.Net.Http.Json;
using System.Security.Cryptography;
using System.Text.Json;
namespace Common.Shared.DomainService
{
///
/// 获取大华icc平台的token服务
///
public class TokenProviderService : ITokenProviderService
{
private readonly IConfiguration _configuration;
private readonly ILogger _logger;
public TokenProviderService(IConfiguration configuration, ILogger logger)
{
_configuration = configuration;
_logger = logger;
}
///
/// 开发测试的时候,忽略证书
///
private static readonly HttpClient _http = new(new HttpClientHandler
{
ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
});
public async Task GetTokenAsync(string clientId)
{
if (TokenCache.TokenMap.TryGetValue(clientId, out var tokenEntry)
&& tokenEntry.ExpireAt > DateTimeOffset.UtcNow.AddMinutes(5))
{
return tokenEntry.AccessToken!;
}
var tokenLock = TokenLockProvider.GetLock(clientId);
await tokenLock.WaitAsync();
try
{
// 加锁后再次检查,防止重复刷新
if (TokenCache.TokenMap.TryGetValue(clientId, out tokenEntry)
&& tokenEntry.ExpireAt > DateTimeOffset.UtcNow.AddMinutes(5))
{
return tokenEntry.AccessToken!;
}
var refreshed = await TryRefreshOrLoginAsync(clientId, tokenEntry);
return refreshed.AccessToken;
}
finally
{
tokenLock.Release();
}
}
private async Task TryRefreshOrLoginAsync(string clientId, TokenEntry? current)
{
try
{
TokenEntry refreshed;
if (current?.AccessToken is { } refreshToken)
{
var dto = new RefreshTokenReqDto
{
ClientId = clientId,
ClientSecret = _configuration["DahuaAuth:ClientSecret"]!,
GrantType = "refresh_token",
//刷新要求去掉 Bearer 前缀
RefreshToken = refreshToken.Replace("Bearer ", string.Empty)
};
var result = await RefreshToken(dto);
if (result?.Data != null && result.Data.AccessToken != "" && result.Data.AccessToken != null)
{
refreshed = new TokenEntry
{
AccessToken = string.Concat(result.Data.TokenType, " ", result.Data.AccessToken),
ExpireAt = DateTimeOffset.UtcNow.AddSeconds(result.Data.ExpiresIn)
};
_logger.LogInformation("Refresh 成功: {ClientId}", clientId);
}
else
{
_logger.LogWarning("RefreshToken 失败,尝试重新登录");
refreshed = await GetDaHToken();
}
}
else
{
refreshed = await GetDaHToken();
}
if (refreshed != null && refreshed.AccessToken != "")
{
// 更新缓存
TokenCache.TokenMap[clientId] = refreshed;
}
return refreshed;
}
catch (Exception ex)
{
_logger.LogError(ex, "获取 token 异常:{ClientId}", clientId);
return new TokenEntry
{
AccessToken = string.Empty,
ExpireAt = DateTimeOffset.UtcNow.AddMinutes(1)
};
}
}
private async Task GetDaHToken()
{
//1. 获取公钥
DaHApiResult publicKeyResult = await GetPublicKey();
if (publicKeyResult.Success == false)
{
return new TokenEntry
{
AccessToken = string.Empty,
ExpireAt = DateTimeOffset.UtcNow.AddMinutes(1)
};
}
LoginRequestDto dto = new();
//2. 鉴权
dto.PublicKey = publicKeyResult.Data!.PublicKey;
dto.ClientId = _configuration["DahuaAuth:ClientId"]!;
dto.ClientSecret = _configuration["DahuaAuth:ClientSecret"]!;
dto.Password = _configuration["DahuaAuth:Password"]!;
dto.Username = _configuration["DahuaAuth:Username"]!;
DaHApiResult loginResult = await GetToken(dto);
TokenEntry refreshed = new()
{
AccessToken = loginResult.Data!.AccessToken,
ExpireAt = DateTimeOffset.UtcNow.AddSeconds(120)
};
return refreshed;
}
///
/// 刷新token,2个小时过期的
///
///
///
///
///
///
private async Task> RefreshToken(RefreshTokenReqDto dto)
{
DaHApiResult result = new DaHApiResult() { Success = true, Code = "0" };
if (string.IsNullOrWhiteSpace(dto.RefreshToken))
{
result.Success = false;
result.Code = "1005";
result.Msg = "刷新令牌不能为空";
_logger.LogWarning("刷新大华令牌失败,刷新令牌不能为空");
return result;
}
try
{
var url = $"https://{_configuration["DahuaAuth:Host"]}/evo-apigw/evo-oauth/1.0.0/oauth/extend/refresh/token";
using var resp = await _http.PostAsJsonAsync(url, dto);
resp.EnsureSuccessStatusCode();
result = await resp.Content.ReadFromJsonAsync>();
if (!result.Success || result.Code != "0")
{
result.Success = false;
result.Code = "1006";
result.Msg = "刷新大华令牌失败";
_logger.LogWarning("刷新大华令牌失败,返回结果:{Result}", result);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "刷新大华令牌出错");
result.Success = false;
result.Code = "1006";
result.Msg = "刷新大华令牌失败";
}
return result;
}
///
/// 获取公钥
///
///
///
private async Task> GetPublicKey()
{
DaHApiResult result = new() { Success = true, Code = "0", Data = new PublicKeyDto() { PublicKey = "" } };
try
{
var url = $"https://{_configuration["DahuaAuth:Host"]}/evo-apigw/evo-oauth/1.0.0/oauth/public-key";
using var resp = await _http.GetAsync(url);
resp.EnsureSuccessStatusCode();
var json = await resp.Content.ReadAsStringAsync();
var envelope = JsonSerializer.Deserialize>(json, new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
});
if (envelope?.Data?.PublicKey is null or { Length: 0 })
{
_logger.LogWarning("获取大华公钥失败,返回结果:{Result}", json);
result.Success = false;
result.Code = "1001";
result.Msg = "获取大华公钥失败";
return result;
}
result.Data.PublicKey = envelope.Data.PublicKey;
}
catch (Exception ex)
{
_logger.LogWarning(ex, "大华平台获取公钥出错");
result.Success = false;
result.Code = "1001";
result.Msg = "获取大华公钥失败";
}
return result;
}
///
/// 获取token
///
///
///
private async Task> GetToken(LoginRequestDto dto)
{
DaHApiResult result = new() { Success = true, Code = "0", Data = new LoginResDto { } };
if (dto is null)
{
result.Success = false;
result.Code = "1002";
result.Msg = "请求参数不能为空";
_logger.LogWarning("获取大华登录令牌失败,参数不能为空");
return result;
}
if (string.IsNullOrWhiteSpace(dto.Password))
{
result.Success = false;
result.Code = "1003";
result.Msg = "密码不能为空";
_logger.LogWarning("获取大华登录令牌失败,密码不能为空");
return result;
}
try
{
var url = $"https://{_configuration["DahuaAuth:Host"]}/evo-apigw/evo-oauth/1.0.0/oauth/extend/token";
//必须加密
dto.Password = EncryptByPublicKey(dto.Password, dto.PublicKey!);
using var resp = await _http.PostAsJsonAsync(url, dto);
resp.EnsureSuccessStatusCode();
var tokenInfo = await resp.Content.ReadFromJsonAsync>();
if (tokenInfo == null || !result.Success || result.Code != "0")
{
result.Success = false;
result.Code = "1004";
result.Msg = "获取大华登录令牌失败";
_logger.LogWarning("获取大华登录令牌失败,返回结果:{Result}", result);
}
result = tokenInfo!;
//固定的拼接方式
result.Data!.AccessToken = string.Concat(tokenInfo?.Data!.TokenType, " ", tokenInfo?.Data!.AccessToken);
TokenEntry refreshed = new TokenEntry
{
AccessToken = string.Concat(result!.Data.TokenType, " ", result.Data.AccessToken),
ExpireAt = DateTimeOffset.UtcNow.AddSeconds(result.Data.ExpiresIn)
};
}
catch (Exception ex)
{
_logger.LogError(ex, "获取大华登录令牌出错");
result.Success = false;
result.Code = "1004";
result.Msg = "获取大华登录令牌失败";
}
return result;
}
///
/// 判断token是否有效
///
///
///
public bool IsTokenValid(string token)
{
// 避免 NullReferenceException
if (string.IsNullOrWhiteSpace(token))
return true;
// 统一写法,后续改条件只改这里
return token.Length < 10;
}
#region RES加密
private static string EncryptByPublicKey(string context, string publicKey)
{
RSACryptoServiceProvider rsa = new();
rsa.ImportParameters(FromXmlStringExtensions(ConvertToXmlPublicJavaKey(publicKey)));
byte[] byteText = System.Text.Encoding.UTF8.GetBytes(context);
byte[] byteEntry = rsa.Encrypt(byteText, false);
return Convert.ToBase64String(byteEntry);
}
private static RSAParameters FromXmlStringExtensions(string xmlString)
{
RSAParameters parameters = new RSAParameters();
System.Xml.XmlDocument xmlDoc = new System.Xml.XmlDocument();
xmlDoc.LoadXml(xmlString);
if (xmlDoc.DocumentElement!.Name.Equals("RSAKeyValue"))
{
foreach (System.Xml.XmlNode node in xmlDoc.DocumentElement.ChildNodes)
{
switch (node.Name)
{
case "Modulus": parameters.Modulus = string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText); break;
case "Exponent": parameters.Exponent = string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText); break;
case "P": parameters.P = string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText); break;
case "Q": parameters.Q = string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText); break;
case "DP": parameters.DP = string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText); break;
case "DQ": parameters.DQ = string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText); break;
case "InverseQ": parameters.InverseQ = string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText); break;
case "D": parameters.D = string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText); break;
}
}
}
else
{
throw new Exception("Invalid XML RSA key.");
}
return parameters;
}
private static string ConvertToXmlPublicJavaKey(string publicJavaKey)
{
RsaKeyParameters publicKeyParam = (RsaKeyParameters)PublicKeyFactory.CreateKey(Convert.FromBase64String(publicJavaKey));
string xmlpublicKey = string.Format("{0}{1}",
Convert.ToBase64String(publicKeyParam.Modulus.ToByteArrayUnsigned()),
Convert.ToBase64String(publicKeyParam.Exponent.ToByteArrayUnsigned()));
Console.WriteLine(xmlpublicKey);
return xmlpublicKey;
}
#endregion RES加密
}
}