From df3355b766ad5247ca83760e57282b4ede7c2902 Mon Sep 17 00:00:00 2001 From: colin Date: Fri, 25 Oct 2024 17:12:27 +0800 Subject: [PATCH] refactor(open-api): get api key from the request header --- .../OpenApiAuthorizationService.cs | 68 ++++++++++++------- .../LINGYUN.Abp.OpenApi.IdentityServer.csproj | 1 + .../IdentityServerAppKeyStore.cs | 2 +- .../LINGYUN.Abp.OpenApi.OpenIddict.csproj | 1 + .../OpenIddict/OpenIddictAppKeyStore.cs | 2 +- .../LINGYUN.Abp.OpenApi.csproj | 2 + .../LINGYUN/Abp/OpenApi/AbpOpenApiConsts.cs | 41 +++++++++-- .../LINGYUN/Abp/OpenApi/AbpOpenApiModule.cs | 2 + .../LINGYUN/Abp/OpenApi/AbpOpenApiOptions.cs | 20 +++++- .../ConfigurationStore/DefaultAppKeyStore.cs | 4 +- .../LINGYUN/Abp/OpenApi/DefaultNonceStore.cs | 45 ++++++++++++ .../LINGYUN/Abp/OpenApi/IAppKeyStore.cs | 2 +- .../LINGYUN/Abp/OpenApi/INonceStore.cs | 8 +++ .../OpenApi/Localization/Resources/en.json | 2 + .../Localization/Resources/zh-Hans.json | 2 + .../Abp/OpenApi/NonceStateCacheItem.cs | 26 +++++++ .../ClientProxyServiceCollectionExtensions.cs | 2 +- .../OpenApi.Sdk/OpenApi/ClientProxy.cs | 33 +++++---- 18 files changed, 216 insertions(+), 47 deletions(-) create mode 100644 aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/DefaultNonceStore.cs create mode 100644 aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/INonceStore.cs create mode 100644 aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/NonceStateCacheItem.cs diff --git a/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi.Authorization/LINGYUN/Abp/OpenApi/Authorization/OpenApiAuthorizationService.cs b/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi.Authorization/LINGYUN/Abp/OpenApi/Authorization/OpenApiAuthorizationService.cs index f5bdda243..c37fe1dec 100644 --- a/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi.Authorization/LINGYUN/Abp/OpenApi/Authorization/OpenApiAuthorizationService.cs +++ b/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi.Authorization/LINGYUN/Abp/OpenApi/Authorization/OpenApiAuthorizationService.cs @@ -13,7 +13,6 @@ using Volo.Abp; using Volo.Abp.AspNetCore.ExceptionHandling; using Volo.Abp.AspNetCore.WebClientInfo; -using Volo.Abp.Clients; using Volo.Abp.DependencyInjection; using Volo.Abp.Http; using Volo.Abp.Json; @@ -24,23 +23,23 @@ public class OpenApiAuthorizationService : IOpenApiAuthorizationService, ITransi { private readonly IAppKeyStore _appKeyStore; private readonly AbpOpenApiOptions _openApiOptions; - private readonly ICurrentClient _currentClient; private readonly IWebClientInfoProvider _clientInfoProvider; + private readonly INonceStore _nonceStore; private readonly IClientChecker _clientChecker; private readonly IIpAddressChecker _ipAddressChecker; private readonly AbpExceptionHandlingOptions _exceptionHandlingOptions; public OpenApiAuthorizationService( + INonceStore nonceStore, IAppKeyStore appKeyStore, - ICurrentClient currentClient, IClientChecker clientChecker, IIpAddressChecker ipAddressChecker, IWebClientInfoProvider clientInfoProvider, IOptionsMonitor options, IOptions exceptionHandlingOptions) { + _nonceStore = nonceStore; _appKeyStore = appKeyStore; - _currentClient = currentClient; _clientChecker = clientChecker; _ipAddressChecker = ipAddressChecker; _clientInfoProvider = clientInfoProvider; @@ -55,7 +54,7 @@ public async virtual Task AuthorizeAsync(HttpContext httpContext) return true; } - if (!await ValidateClient(httpContext)) + if (!await ValidateClientIpAddress(httpContext)) { return false; } @@ -73,18 +72,8 @@ public async virtual Task AuthorizeAsync(HttpContext httpContext) return true; } - protected async virtual Task ValidateClient(HttpContext httpContext) + protected async virtual Task ValidateClientIpAddress(HttpContext httpContext) { - if (_currentClient.IsAuthenticated && !await _clientChecker.IsGrantAsync(_currentClient.Id, httpContext.RequestAborted)) - { - var exception = new BusinessException( - AbpOpenApiConsts.InvalidAccessWithClientId, - $"Client Id {_currentClient.Id} Not Allowed", - $"Client Id {_currentClient.Id} Not Allowed"); - await Unauthorized(httpContext, exception); - return false; - } - if (!string.IsNullOrWhiteSpace(_clientInfoProvider.ClientIpAddress) && !await _ipAddressChecker.IsGrantAsync(_clientInfoProvider.ClientIpAddress, httpContext.RequestAborted)) { @@ -114,9 +103,11 @@ protected async virtual Task ValidateQueryString(HttpContext httpContext) protected async virtual Task ValidatAppDescriptor(HttpContext httpContext) { - httpContext.Request.Query.TryGetValue(AbpOpenApiConsts.AppKeyFieldName, out var appKey); - httpContext.Request.Query.TryGetValue(AbpOpenApiConsts.SignatureFieldName, out var sign); - httpContext.Request.Query.TryGetValue(AbpOpenApiConsts.TimeStampFieldName, out var timeStampString); + httpContext.Request.Headers.TryGetValue(AbpOpenApiConsts.AppKeyFieldName, out var appKey); + httpContext.Request.Headers.TryGetValue(AbpOpenApiConsts.SignatureFieldName, out var sign); + httpContext.Request.Headers.TryGetValue(AbpOpenApiConsts.NonceFieldName, out var nonce); + httpContext.Request.Headers.TryGetValue(AbpOpenApiConsts.TimeStampFieldName, out var timeStampString); + if (StringValues.IsNullOrEmpty(appKey)) { @@ -128,6 +119,17 @@ protected async virtual Task ValidatAppDescriptor(HttpContext httpContext) return false; } + if (StringValues.IsNullOrEmpty(nonce)) + { + var exception = new BusinessException( + AbpOpenApiConsts.InvalidAccessWithNonceNotFound, + $"{AbpOpenApiConsts.NonceFieldName} Not Found", + $"{AbpOpenApiConsts.NonceFieldName} Not Found"); + + await Unauthorized(httpContext, exception); + return false; + } + if (StringValues.IsNullOrEmpty(sign)) { var exception = new BusinessException( @@ -161,6 +163,26 @@ protected async virtual Task ValidatAppDescriptor(HttpContext httpContext) return false; } + if (!await _nonceStore.TrySetAsync(nonce.ToString(), httpContext.RequestAborted)) + { + var exception = new BusinessException( + AbpOpenApiConsts.InvalidAccessWithNonceRepeated, + $"Request {nonce} has repeated", + $"Request {nonce} has repeated"); + await Unauthorized(httpContext, exception); + return false; + } + + if (!await _clientChecker.IsGrantAsync(appKey.ToString(), httpContext.RequestAborted)) + { + var exception = new BusinessException( + AbpOpenApiConsts.InvalidAccessWithClientId, + $"Client Id {appKey} Not Allowed", + $"Client Id {appKey} Not Allowed"); + await Unauthorized(httpContext, exception); + return false; + } + var appDescriptor = await _appKeyStore.FindAsync(appKey.ToString(), httpContext.RequestAborted); if (appDescriptor == null) { @@ -178,13 +200,13 @@ protected async virtual Task ValidatAppDescriptor(HttpContext httpContext) var queryStringCollection = httpContext.Request.Query; foreach (var queryString in queryStringCollection) { - if (queryString.Key.Equals(AbpOpenApiConsts.SignatureFieldName)) - { - continue; - } queryDictionary.Add(queryString.Key, queryString.Value.ToString()); } + queryDictionary.TryAdd("appKey", appDescriptor.AppKey); queryDictionary.TryAdd("appSecret", appDescriptor.AppSecret); + queryDictionary.TryAdd("nonce", nonce.ToString()); + queryDictionary.TryAdd("t", timeStampString.ToString()); + var requiredSign = CalculationSignature(httpContext.Request.Path.Value, queryDictionary); if (!string.Equals(requiredSign, sign.ToString())) { diff --git a/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi.IdentityServer/LINGYUN.Abp.OpenApi.IdentityServer.csproj b/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi.IdentityServer/LINGYUN.Abp.OpenApi.IdentityServer.csproj index cbdcebb93..604327d6d 100644 --- a/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi.IdentityServer/LINGYUN.Abp.OpenApi.IdentityServer.csproj +++ b/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi.IdentityServer/LINGYUN.Abp.OpenApi.IdentityServer.csproj @@ -10,6 +10,7 @@ false false false + enable diff --git a/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi.IdentityServer/LINGYUN/Abp/OpenApi/IdentityServer/IdentityServerAppKeyStore.cs b/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi.IdentityServer/LINGYUN/Abp/OpenApi/IdentityServer/IdentityServerAppKeyStore.cs index 5387957af..37ac2d59d 100644 --- a/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi.IdentityServer/LINGYUN/Abp/OpenApi/IdentityServer/IdentityServerAppKeyStore.cs +++ b/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi.IdentityServer/LINGYUN/Abp/OpenApi/IdentityServer/IdentityServerAppKeyStore.cs @@ -27,7 +27,7 @@ public IdentityServerAppKeyStore( Logger = NullLogger.Instance; } - public async virtual Task FindAsync(string appKey, CancellationToken cancellationToken = default) + public async virtual Task FindAsync(string appKey, CancellationToken cancellationToken = default) { var client = await _clientRepository.FindByClientIdAsync(appKey, cancellationToken: cancellationToken); if (client != null) diff --git a/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi.OpenIddict/LINGYUN.Abp.OpenApi.OpenIddict.csproj b/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi.OpenIddict/LINGYUN.Abp.OpenApi.OpenIddict.csproj index b35b635f3..0d3c41258 100644 --- a/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi.OpenIddict/LINGYUN.Abp.OpenApi.OpenIddict.csproj +++ b/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi.OpenIddict/LINGYUN.Abp.OpenApi.OpenIddict.csproj @@ -10,6 +10,7 @@ false false false + enable diff --git a/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi.OpenIddict/LINGYUN/Abp/OpenApi/OpenIddict/OpenIddictAppKeyStore.cs b/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi.OpenIddict/LINGYUN/Abp/OpenApi/OpenIddict/OpenIddictAppKeyStore.cs index 952dade27..c42cafe62 100644 --- a/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi.OpenIddict/LINGYUN/Abp/OpenApi/OpenIddict/OpenIddictAppKeyStore.cs +++ b/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi.OpenIddict/LINGYUN/Abp/OpenApi/OpenIddict/OpenIddictAppKeyStore.cs @@ -22,7 +22,7 @@ public OpenIddictAppKeyStore( _guidGenerator = guidGenerator; } - public async virtual Task FindAsync(string appKey, CancellationToken cancellationToken = default) + public async virtual Task FindAsync(string appKey, CancellationToken cancellationToken = default) { var application = await _appStore.FindByClientIdAsync(appKey, cancellationToken); if (application != null) diff --git a/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi/LINGYUN.Abp.OpenApi.csproj b/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi/LINGYUN.Abp.OpenApi.csproj index 9d1c596ab..846156b9a 100644 --- a/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi/LINGYUN.Abp.OpenApi.csproj +++ b/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi/LINGYUN.Abp.OpenApi.csproj @@ -10,6 +10,7 @@ false false false + enable @@ -22,6 +23,7 @@ + diff --git a/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/AbpOpenApiConsts.cs b/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/AbpOpenApiConsts.cs index ed9a08fa5..983065910 100644 --- a/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/AbpOpenApiConsts.cs +++ b/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/AbpOpenApiConsts.cs @@ -4,21 +4,54 @@ public static class AbpOpenApiConsts { public const string SecurityChecking = "_AbpOpenApiSecurityChecking"; - public const string AppKeyFieldName = "appKey"; - public const string SignatureFieldName = "sign"; - public const string TimeStampFieldName = "t"; + public const string AppKeyFieldName = "X-API-APPKEY"; + public const string SignatureFieldName = "X-API-SIGN"; + public const string NonceFieldName = "X-API-NONCE"; + public const string TimeStampFieldName = "X-API-TIMESTAMP"; public const string KeyPrefix = "AbpOpenApi"; - + /// + /// 无效的应用标识 {AppKey}. + /// public const string InvalidAccessWithAppKey = KeyPrefix + ":9100"; + /// + /// 未携带应用标识(appKey). + /// public const string InvalidAccessWithAppKeyNotFound = KeyPrefix + ":9101"; + /// + /// 无效的签名 sign. + /// public const string InvalidAccessWithSign = KeyPrefix + ":9110"; + /// + /// 未携带签名(sign). + /// public const string InvalidAccessWithSignNotFound = KeyPrefix + ":9111"; + /// + /// 请求超时或会话已过期. + /// public const string InvalidAccessWithTimestamp = KeyPrefix + ":9210"; + /// + /// 未携带时间戳标识. + /// public const string InvalidAccessWithTimestampNotFound = KeyPrefix + ":9211"; + /// + /// 重复发起的请求. + /// + public const string InvalidAccessWithNonceRepeated = KeyPrefix + ":9220"; + /// + /// 未携带随机数. + /// + public const string InvalidAccessWithNonceNotFound = KeyPrefix + ":9221"; + + /// + /// 客户端不在允许的范围内. + /// public const string InvalidAccessWithClientId = KeyPrefix + ":9300"; + /// + /// 客户端IP不在允许的范围内. + /// public const string InvalidAccessWithIpAddress = KeyPrefix + ":9400"; } diff --git a/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/AbpOpenApiModule.cs b/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/AbpOpenApiModule.cs index 08c7b65ab..b1ef85a71 100644 --- a/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/AbpOpenApiModule.cs +++ b/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/AbpOpenApiModule.cs @@ -1,6 +1,7 @@ using LINGYUN.Abp.OpenApi.ConfigurationStore; using LINGYUN.Abp.OpenApi.Localization; using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Caching; using Volo.Abp.Localization; using Volo.Abp.Localization.ExceptionHandling; using Volo.Abp.Modularity; @@ -10,6 +11,7 @@ namespace LINGYUN.Abp.OpenApi; [DependsOn( + typeof(AbpCachingModule), typeof(AbpSecurityModule), typeof(AbpLocalizationModule))] public class AbpOpenApiModule : AbpModule diff --git a/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/AbpOpenApiOptions.cs b/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/AbpOpenApiOptions.cs index 960694271..a2673f33a 100644 --- a/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/AbpOpenApiOptions.cs +++ b/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/AbpOpenApiOptions.cs @@ -1,10 +1,28 @@ -namespace LINGYUN.Abp.OpenApi; +using System; + +namespace LINGYUN.Abp.OpenApi; public class AbpOpenApiOptions { + /// + /// 启用Api签名检查 + /// + /// + /// 默认: true + /// public bool IsEnabled { get; set; } + /// + /// 请求随机数过期时间 + /// + /// + /// 默认: 10分钟 + /// + public TimeSpan RequestNonceExpireIn { get; set; } + public AbpOpenApiOptions() { IsEnabled = true; + + RequestNonceExpireIn = TimeSpan.FromMinutes(10); } } diff --git a/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/ConfigurationStore/DefaultAppKeyStore.cs b/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/ConfigurationStore/DefaultAppKeyStore.cs index 301c4005c..f554b9a29 100644 --- a/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/ConfigurationStore/DefaultAppKeyStore.cs +++ b/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/ConfigurationStore/DefaultAppKeyStore.cs @@ -17,12 +17,12 @@ public DefaultAppKeyStore(IOptionsMonitor options) _options = options.CurrentValue; } - public Task FindAsync(string appKey, CancellationToken cancellationToken = default) + public Task FindAsync(string appKey, CancellationToken cancellationToken = default) { return Task.FromResult(Find(appKey)); } - public AppDescriptor Find(string appKey) + public AppDescriptor? Find(string appKey) { return _options.AppDescriptors?.FirstOrDefault(t => t.AppKey == appKey); } diff --git a/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/DefaultNonceStore.cs b/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/DefaultNonceStore.cs new file mode 100644 index 000000000..b506dfe1a --- /dev/null +++ b/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/DefaultNonceStore.cs @@ -0,0 +1,45 @@ +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Options; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp.Caching; +using Volo.Abp.DependencyInjection; + +namespace LINGYUN.Abp.OpenApi; +public class DefaultNonceStore : INonceStore, ITransientDependency +{ + private const string CacheKeyFormat = "open-api,n:{0}"; + + private readonly IDistributedCache _nonceCache; + private readonly AbpOpenApiOptions _options; + + public DefaultNonceStore( + IDistributedCache nonceCache, + IOptions options) + { + _nonceCache = nonceCache; + _options = options.Value; + } + + public async virtual Task TrySetAsync(string nonce, CancellationToken cancellationToken = default) + { + var cacheKey = string.Format(CacheKeyFormat, nonce); + + var cacheItem = await _nonceCache.GetAsync(cacheKey, token: cancellationToken); + if (cacheItem == null) + { + await _nonceCache.SetAsync( + cacheKey, + new NonceStateCacheItem(nonce), + options: new DistributedCacheEntryOptions + { + AbsoluteExpirationRelativeToNow = _options.RequestNonceExpireIn, + }, + token: cancellationToken); + + return true; + } + + return false; + } +} diff --git a/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/IAppKeyStore.cs b/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/IAppKeyStore.cs index 1db9649ac..51e286a25 100644 --- a/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/IAppKeyStore.cs +++ b/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/IAppKeyStore.cs @@ -5,7 +5,7 @@ namespace LINGYUN.Abp.OpenApi; public interface IAppKeyStore { - Task FindAsync(string appKey, CancellationToken cancellationToken = default); + Task FindAsync(string appKey, CancellationToken cancellationToken = default); Task StoreAsync(AppDescriptor descriptor, CancellationToken cancellationToken = default); } diff --git a/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/INonceStore.cs b/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/INonceStore.cs new file mode 100644 index 000000000..33918b1e1 --- /dev/null +++ b/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/INonceStore.cs @@ -0,0 +1,8 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.OpenApi; +public interface INonceStore +{ + Task TrySetAsync(string nonce, CancellationToken cancellationToken = default); +} diff --git a/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/Localization/Resources/en.json b/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/Localization/Resources/en.json index 761c17dfe..328d6154b 100644 --- a/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/Localization/Resources/en.json +++ b/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/Localization/Resources/en.json @@ -7,6 +7,8 @@ "AbpOpenApi:9111": "sign not found.", "AbpOpenApi:9210": "Request timed out or the session expired.", "AbpOpenApi:9211": "timestamp not found.", + "AbpOpenApi:9220": "Repeatedly initiated requests.", + "AbpOpenApi:9221": "nonce not found.", "AbpOpenApi:9300": "The client is not within the allowed range.", "AbpOpenApi:9400": "The client IP is not within the allowed range." } diff --git a/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/Localization/Resources/zh-Hans.json b/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/Localization/Resources/zh-Hans.json index 83b7aaef8..bf77bafd0 100644 --- a/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/Localization/Resources/zh-Hans.json +++ b/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/Localization/Resources/zh-Hans.json @@ -7,6 +7,8 @@ "AbpOpenApi:9111": "未携带签名(sign).", "AbpOpenApi:9210": "请求超时或会话已过期.", "AbpOpenApi:9211": "未携带时间戳标识.", + "AbpOpenApi:9220": "重复发起的请求.", + "AbpOpenApi:9221": "未携带随机数.", "AbpOpenApi:9300": "客户端不在允许的范围内.", "AbpOpenApi:9400": "客户端IP不在允许的范围内." } diff --git a/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/NonceStateCacheItem.cs b/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/NonceStateCacheItem.cs new file mode 100644 index 000000000..e0ed6382a --- /dev/null +++ b/aspnet-core/framework/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/NonceStateCacheItem.cs @@ -0,0 +1,26 @@ +using System; + +namespace LINGYUN.Abp.OpenApi; + +[Serializable] +public class NonceStateCacheItem +{ + private const string CacheKeyFormat = "open-api,nonce:{0}"; + + public string Nonce { get; set; } + + public NonceStateCacheItem() + { + + } + + public NonceStateCacheItem(string nonce) + { + Nonce = nonce; + } + + public static string CalculateCacheKey(string nonce) + { + return string.Format(CacheKeyFormat, nonce); + } +} diff --git a/aspnet-core/framework/open-api/OpenApi.Sdk/Microsoft/Extensions/DependencyInjection/ClientProxyServiceCollectionExtensions.cs b/aspnet-core/framework/open-api/OpenApi.Sdk/Microsoft/Extensions/DependencyInjection/ClientProxyServiceCollectionExtensions.cs index 2fcf36b4a..8ed106fe3 100644 --- a/aspnet-core/framework/open-api/OpenApi.Sdk/Microsoft/Extensions/DependencyInjection/ClientProxyServiceCollectionExtensions.cs +++ b/aspnet-core/framework/open-api/OpenApi.Sdk/Microsoft/Extensions/DependencyInjection/ClientProxyServiceCollectionExtensions.cs @@ -7,7 +7,7 @@ public static class ClientProxyServiceCollectionExtensions { public static IServiceCollection AddClientProxy(this IServiceCollection services, string serverUrl) { - services.AddHttpClient("opensdk", options => + services.AddHttpClient("openapi-sdk", options => { options.BaseAddress = new Uri(serverUrl); }); diff --git a/aspnet-core/framework/open-api/OpenApi.Sdk/OpenApi/ClientProxy.cs b/aspnet-core/framework/open-api/OpenApi.Sdk/OpenApi/ClientProxy.cs index 30c6becec..ea371a5cd 100644 --- a/aspnet-core/framework/open-api/OpenApi.Sdk/OpenApi/ClientProxy.cs +++ b/aspnet-core/framework/open-api/OpenApi.Sdk/OpenApi/ClientProxy.cs @@ -57,6 +57,8 @@ public async virtual Task> RequestAsync( { // UTC时间戳 var timeStamp = GetUtcTimeStampString(); + // 随机数 + var nonce = Guid.NewGuid().ToString(); // 取出api地址 var baseUrl = url.Split('?')[0]; // 组装请求参数 @@ -65,34 +67,39 @@ public async virtual Task> RequestAsync( url.Contains('?') ? "&" : "?", "appKey=", appKey, + "appSecret=", + appSecret, + "nonce=", + nonce, "&t=", timeStamp); - var quertString = ReverseQueryString(requestUrl); - // 密钥参与计算 - quertString.Add("appSecret", appSecret); + var queryString = ReverseQueryString(requestUrl); // 对请求参数签名 - var sign = CalculationSignature(baseUrl, quertString); - // 移除密钥 - quertString.Remove("appSecret"); - // 签名随请求传递 - quertString.Add("sign", sign); - // 重新拼接请求参数 - requestUrl = string.Concat(baseUrl, "?", BuildQuery(quertString)); + var sign = CalculationSignature(baseUrl, queryString); + // 构建请求体 - var requestMessage = new HttpRequestMessage(httpMethod, requestUrl); + var requestMessage = new HttpRequestMessage(httpMethod, url); if (request != null) { // Request Payload requestMessage.Content = new StringContent(JsonConvert.SerializeObject(request)); } - - // 返回中文错误提示 + // appKey添加到headers + requestMessage.Headers.TryAddWithoutValidation("X-API-APPKEY", appKey); + // 随机数添加到headers + requestMessage.Headers.TryAddWithoutValidation("X-API-NONCE", nonce); + // 时间戳添加到headers + requestMessage.Headers.TryAddWithoutValidation("X-API-TIMESTAMP", timeStamp); + // 签名添加到headers + requestMessage.Headers.TryAddWithoutValidation("X-API-SIGN", sign); + // 返回本地化错误提示 requestMessage.Headers.AcceptLanguage.Add(new StringWithQualityHeaderValue(CultureInfo.CurrentUICulture.Name)); // 返回错误消息可序列化 requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); // 序列化响应 var response = await client.SendAsync(requestMessage); + var stringContent = await response.Content.ReadAsStringAsync(); return JsonConvert.DeserializeObject>(stringContent);