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加密 } }