diff --git a/aspnet-core/LINGYUN.MicroService.Common.sln b/aspnet-core/LINGYUN.MicroService.Common.sln
index f40b45866..533f69ae7 100644
--- a/aspnet-core/LINGYUN.MicroService.Common.sln
+++ b/aspnet-core/LINGYUN.MicroService.Common.sln
@@ -206,7 +206,17 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.AspNetCore.Mvc.
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Wrapper.Tests", "tests\LINGYUN.Abp.Wrapper.Tests\LINGYUN.Abp.Wrapper.Tests.csproj", "{31AED9ED-29BD-4F2F-8D3A-F00CBB9FC73C}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.HttpClient.Wrapper", "modules\common\LINGYUN.Abp.HttpClient.Wrapper\LINGYUN.Abp.HttpClient.Wrapper.csproj", "{F6CABEE7-DE34-458A-8787-95DFB1DA4762}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.HttpClient.Wrapper", "modules\common\LINGYUN.Abp.HttpClient.Wrapper\LINGYUN.Abp.HttpClient.Wrapper.csproj", "{F6CABEE7-DE34-458A-8787-95DFB1DA4762}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "open-api", "open-api", "{8C688427-DD35-4F0B-86DA-6F536D3852D5}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.OpenApi", "modules\open-api\LINGYUN.Abp.OpenApi\LINGYUN.Abp.OpenApi.csproj", "{2C8A6B4C-D6B2-44FE-9EF5-EC7480D2A6B2}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.OpenApi.Authorization", "modules\open-api\LINGYUN.Abp.OpenApi.Authorization\LINGYUN.Abp.OpenApi.Authorization.csproj", "{3CE350AF-5574-46EC-8120-8542350AED20}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenApi.Sdk", "modules\open-api\OpenApi.Sdk\OpenApi.Sdk.csproj", "{108192F3-3780-423F-9871-A1BBE323413E}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.OpenApi.Tests", "tests\LINGYUN.Abp.OpenApi.Tests\LINGYUN.Abp.OpenApi.Tests.csproj", "{6C75799E-4B46-434D-BE1B-4AD71DF49686}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -542,6 +552,22 @@ Global
{F6CABEE7-DE34-458A-8787-95DFB1DA4762}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F6CABEE7-DE34-458A-8787-95DFB1DA4762}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F6CABEE7-DE34-458A-8787-95DFB1DA4762}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2C8A6B4C-D6B2-44FE-9EF5-EC7480D2A6B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2C8A6B4C-D6B2-44FE-9EF5-EC7480D2A6B2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2C8A6B4C-D6B2-44FE-9EF5-EC7480D2A6B2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2C8A6B4C-D6B2-44FE-9EF5-EC7480D2A6B2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3CE350AF-5574-46EC-8120-8542350AED20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3CE350AF-5574-46EC-8120-8542350AED20}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3CE350AF-5574-46EC-8120-8542350AED20}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3CE350AF-5574-46EC-8120-8542350AED20}.Release|Any CPU.Build.0 = Release|Any CPU
+ {108192F3-3780-423F-9871-A1BBE323413E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {108192F3-3780-423F-9871-A1BBE323413E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {108192F3-3780-423F-9871-A1BBE323413E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {108192F3-3780-423F-9871-A1BBE323413E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6C75799E-4B46-434D-BE1B-4AD71DF49686}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6C75799E-4B46-434D-BE1B-4AD71DF49686}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6C75799E-4B46-434D-BE1B-4AD71DF49686}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6C75799E-4B46-434D-BE1B-4AD71DF49686}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -646,6 +672,11 @@ Global
{AE5E6DE8-FC02-4633-BA49-C4B8ABADB502} = {B86C21A4-73B7-471E-B73A-B4B905EC9435}
{31AED9ED-29BD-4F2F-8D3A-F00CBB9FC73C} = {B86C21A4-73B7-471E-B73A-B4B905EC9435}
{F6CABEE7-DE34-458A-8787-95DFB1DA4762} = {086BE5BE-8594-4DA7-8819-935FEF76DABD}
+ {8C688427-DD35-4F0B-86DA-6F536D3852D5} = {02EA4E78-5891-43BC-944F-3E52FEE032E4}
+ {2C8A6B4C-D6B2-44FE-9EF5-EC7480D2A6B2} = {8C688427-DD35-4F0B-86DA-6F536D3852D5}
+ {3CE350AF-5574-46EC-8120-8542350AED20} = {8C688427-DD35-4F0B-86DA-6F536D3852D5}
+ {108192F3-3780-423F-9871-A1BBE323413E} = {8C688427-DD35-4F0B-86DA-6F536D3852D5}
+ {6C75799E-4B46-434D-BE1B-4AD71DF49686} = {B86C21A4-73B7-471E-B73A-B4B905EC9435}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {06C707C6-02C0-411A-AD3B-2D0E13787CB8}
diff --git a/aspnet-core/LINGYUN.MicroService.Platform.sln b/aspnet-core/LINGYUN.MicroService.Platform.sln
index 31ed1fd60..ee2efa879 100644
--- a/aspnet-core/LINGYUN.MicroService.Platform.sln
+++ b/aspnet-core/LINGYUN.MicroService.Platform.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 16
-VisualStudioVersion = 16.0.30011.22
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "modules", "modules", "{15BDA03E-DE8E-46E4-96A8-CA3F2872E812}"
EndProject
@@ -92,6 +92,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "navigation", "navigation",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.UI.Navigation", "modules\navigation\LINGYUN.Abp.UI.Navigation\LINGYUN.Abp.UI.Navigation.csproj", "{BE9131C0-7DD4-4032-A5F7-4B9D76EC16CF}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Wrapper", "modules\common\LINGYUN.Abp.Wrapper\LINGYUN.Abp.Wrapper.csproj", "{36EFF070-3400-448E-A912-82930D6E1828}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.AspNetCore.Tests", "tests\LINGYUN.Abp.AspNetCore.Tests\LINGYUN.Abp.AspNetCore.Tests.csproj", "{3B363320-B679-4D29-B428-28311C576E2E}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -226,6 +230,14 @@ Global
{BE9131C0-7DD4-4032-A5F7-4B9D76EC16CF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BE9131C0-7DD4-4032-A5F7-4B9D76EC16CF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BE9131C0-7DD4-4032-A5F7-4B9D76EC16CF}.Release|Any CPU.Build.0 = Release|Any CPU
+ {36EFF070-3400-448E-A912-82930D6E1828}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {36EFF070-3400-448E-A912-82930D6E1828}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {36EFF070-3400-448E-A912-82930D6E1828}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {36EFF070-3400-448E-A912-82930D6E1828}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3B363320-B679-4D29-B428-28311C576E2E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3B363320-B679-4D29-B428-28311C576E2E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3B363320-B679-4D29-B428-28311C576E2E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3B363320-B679-4D29-B428-28311C576E2E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -271,6 +283,8 @@ Global
{2A69A38F-E98A-490D-BEAE-1BBBD87121DD} = {4096EC6A-EEAD-4E5B-B087-393D7A4E5874}
{D61721B8-B0F7-4C3D-AD1B-44FBE81DFA79} = {15BDA03E-DE8E-46E4-96A8-CA3F2872E812}
{BE9131C0-7DD4-4032-A5F7-4B9D76EC16CF} = {D61721B8-B0F7-4C3D-AD1B-44FBE81DFA79}
+ {36EFF070-3400-448E-A912-82930D6E1828} = {265D5E44-682B-49BC-984A-BDD8CA45E60E}
+ {3B363320-B679-4D29-B428-28311C576E2E} = {CCEFF583-4EEE-433F-8568-9E64166B41FE}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {03D3B66F-8926-4C00-B7AB-A21761EC859E}
diff --git a/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi.Authorization/LINGYUN.Abp.OpenApi.Authorization.csproj b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi.Authorization/LINGYUN.Abp.OpenApi.Authorization.csproj
new file mode 100644
index 000000000..c58e6bb0d
--- /dev/null
+++ b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi.Authorization/LINGYUN.Abp.OpenApi.Authorization.csproj
@@ -0,0 +1,19 @@
+
+
+
+
+
+ net5.0
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi.Authorization/LINGYUN/Abp/OpenApi/Authorization/AbpOpenApiAuthorizationModule.cs b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi.Authorization/LINGYUN/Abp/OpenApi/Authorization/AbpOpenApiAuthorizationModule.cs
new file mode 100644
index 000000000..43afaa467
--- /dev/null
+++ b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi.Authorization/LINGYUN/Abp/OpenApi/Authorization/AbpOpenApiAuthorizationModule.cs
@@ -0,0 +1,16 @@
+using LINGYUN.Abp.Wrapper;
+using Volo.Abp.AspNetCore;
+using Volo.Abp.Modularity;
+using Volo.Abp.Timing;
+
+namespace LINGYUN.Abp.OpenApi.Authorization
+{
+ [DependsOn(
+ typeof(AbpWrapperModule),
+ typeof(AbpTimingModule),
+ typeof(AbpOpenApiModule),
+ typeof(AbpAspNetCoreModule))]
+ public class AbpOpenApiAuthorizationModule : AbpModule
+ {
+ }
+}
diff --git a/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi.Authorization/LINGYUN/Abp/OpenApi/Authorization/IOpenApiAuthorizationService.cs b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi.Authorization/LINGYUN/Abp/OpenApi/Authorization/IOpenApiAuthorizationService.cs
new file mode 100644
index 000000000..0003d71e4
--- /dev/null
+++ b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi.Authorization/LINGYUN/Abp/OpenApi/Authorization/IOpenApiAuthorizationService.cs
@@ -0,0 +1,10 @@
+using Microsoft.AspNetCore.Http;
+using System.Threading.Tasks;
+
+namespace LINGYUN.Abp.OpenApi.Authorization
+{
+ public interface IOpenApiAuthorizationService
+ {
+ Task AuthorizeAsync(HttpContext httpContext);
+ }
+}
diff --git a/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi.Authorization/LINGYUN/Abp/OpenApi/Authorization/OpenApiAuthorizationMiddleware.cs b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi.Authorization/LINGYUN/Abp/OpenApi/Authorization/OpenApiAuthorizationMiddleware.cs
new file mode 100644
index 000000000..5d614d3af
--- /dev/null
+++ b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi.Authorization/LINGYUN/Abp/OpenApi/Authorization/OpenApiAuthorizationMiddleware.cs
@@ -0,0 +1,24 @@
+using Microsoft.AspNetCore.Http;
+using System.Threading.Tasks;
+using Volo.Abp.DependencyInjection;
+
+namespace LINGYUN.Abp.OpenApi.Authorization
+{
+ public class OpenApiAuthorizationMiddleware : IMiddleware, ITransientDependency
+ {
+ private readonly IOpenApiAuthorizationService _authorizationService;
+ public OpenApiAuthorizationMiddleware(
+ IOpenApiAuthorizationService authorizationService)
+ {
+ _authorizationService = authorizationService;
+ }
+
+ public async Task InvokeAsync(HttpContext context, RequestDelegate next)
+ {
+ if (await _authorizationService.AuthorizeAsync(context))
+ {
+ await next(context);
+ }
+ }
+ }
+}
diff --git a/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi.Authorization/LINGYUN/Abp/OpenApi/Authorization/OpenApiAuthorizationService.cs b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi.Authorization/LINGYUN/Abp/OpenApi/Authorization/OpenApiAuthorizationService.cs
new file mode 100644
index 000000000..7ecfd1930
--- /dev/null
+++ b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi.Authorization/LINGYUN/Abp/OpenApi/Authorization/OpenApiAuthorizationService.cs
@@ -0,0 +1,234 @@
+using LINGYUN.Abp.Wrapper;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
+using Microsoft.Extensions.Primitives;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Text;
+using System.Threading.Tasks;
+using System.Web;
+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;
+
+namespace LINGYUN.Abp.OpenApi.Authorization
+{
+ public class OpenApiAuthorizationService : IOpenApiAuthorizationService, ITransientDependency
+ {
+ private readonly IAppKeyStore _appKeyStore;
+ private readonly AbpOpenApiOptions _openApiOptions;
+ private readonly ICurrentClient _currentClient;
+ private readonly IWebClientInfoProvider _clientInfoProvider;
+
+ public OpenApiAuthorizationService(
+ IAppKeyStore appKeyStore,
+ ICurrentClient currentClient,
+ IWebClientInfoProvider clientInfoProvider,
+ IOptionsMonitor options)
+ {
+ _appKeyStore = appKeyStore;
+ _currentClient = currentClient;
+ _clientInfoProvider = clientInfoProvider;
+ _openApiOptions = options.CurrentValue;
+ }
+
+ public virtual async Task AuthorizeAsync(HttpContext httpContext)
+ {
+ if (!_openApiOptions.IsEnabled)
+ {
+ return true;
+ }
+
+ if (_currentClient.IsAuthenticated &&
+ _openApiOptions.HasWhiteClient(_currentClient.Id))
+ {
+ return true;
+ }
+
+ if (!string.IsNullOrWhiteSpace(_clientInfoProvider.ClientIpAddress) &&
+ _openApiOptions.HasWhiteIpAddress(_clientInfoProvider.ClientIpAddress))
+ {
+ return true;
+ }
+
+ BusinessException exception;
+ if (!httpContext.Request.QueryString.HasValue)
+ {
+ exception = new BusinessException(
+ AbpOpenApiConsts.InvalidAccessWithAppKeyNotFound,
+ $"{AbpOpenApiConsts.AppKeyFieldName} Not Found",
+ $"{AbpOpenApiConsts.AppKeyFieldName} Not Found");
+ await Unauthorized(httpContext, exception);
+ return false;
+ }
+
+ 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);
+
+ if (StringValues.IsNullOrEmpty(appKey))
+ {
+ exception = new BusinessException(
+ AbpOpenApiConsts.InvalidAccessWithAppKeyNotFound,
+ $"{AbpOpenApiConsts.AppKeyFieldName} Not Found",
+ $"{AbpOpenApiConsts.AppKeyFieldName} Not Found");
+ await Unauthorized(httpContext, exception);
+ return false;
+ }
+
+ if (StringValues.IsNullOrEmpty(sign))
+ {
+ exception = new BusinessException(
+ AbpOpenApiConsts.InvalidAccessWithSignNotFound,
+ $"{AbpOpenApiConsts.SignatureFieldName} Not Found",
+ $"{AbpOpenApiConsts.SignatureFieldName} Not Found");
+
+ await Unauthorized(httpContext, exception);
+ return false;
+ }
+
+ if (StringValues.IsNullOrEmpty(timeStampString))
+ {
+ exception = new BusinessException(
+ AbpOpenApiConsts.InvalidAccessWithTimestampNotFound,
+ $"{AbpOpenApiConsts.TimeStampFieldName} Not Found",
+ $"{AbpOpenApiConsts.TimeStampFieldName} Not Found");
+
+ await Unauthorized(httpContext, exception);
+ return false;
+ }
+
+ if (!long.TryParse(timeStampString.ToString(), out long timeStamp))
+ {
+ exception = new BusinessException(
+ AbpOpenApiConsts.InvalidAccessWithTimestamp,
+ "invalid timestamp",
+ "invalid timestamp");
+
+ await Unauthorized(httpContext, exception);
+ return false;
+ }
+
+ var appDescriptor = await _appKeyStore.FindAsync(appKey.ToString());
+ if (appDescriptor == null)
+ {
+ exception = new BusinessException(
+ AbpOpenApiConsts.InvalidAccessWithAppKey,
+ "invalid appKey",
+ "invalid appKey")
+ .WithData("AppKey", appKey.ToString());
+
+ await Unauthorized(httpContext, exception);
+ return false;
+ }
+
+ var queryDictionary = new Dictionary();
+ var queryStringCollection = httpContext.Request.Query.OrderBy(q => q.Key);
+ foreach (var queryString in queryStringCollection)
+ {
+ if (queryString.Key.Equals(AbpOpenApiConsts.SignatureFieldName))
+ {
+ continue;
+ }
+ queryDictionary.Add(queryString.Key, queryString.Value.ToString());
+ }
+
+ var requiredSign = CalculationSignature(httpContext.Request.Path.Value, appDescriptor.AppSecret, queryDictionary);
+ if (!string.Equals(requiredSign, sign.ToString()))
+ {
+ exception = new BusinessException(
+ AbpOpenApiConsts.InvalidAccessWithSign,
+ "invalid signature",
+ "invalid signature");
+
+ await Unauthorized(httpContext, exception);
+ return false;
+ }
+
+ if (appDescriptor.SignLifetime.HasValue && appDescriptor.SignLifetime > 0)
+ {
+ var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
+
+ if ((now - timeStamp) / 1000 > appDescriptor.SignLifetime.Value)
+ {
+ exception = new BusinessException(
+ AbpOpenApiConsts.InvalidAccessWithTimestamp,
+ "session timed out or expired",
+ "session timed out or expired");
+
+ await Unauthorized(httpContext, exception);
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ protected virtual async Task Unauthorized(HttpContext context, BusinessException exception)
+ {
+ var errorInfoConverter = context.RequestServices.GetRequiredService();
+ var errorInfo = errorInfoConverter.Convert(exception, false);
+
+ var exceptionWrapHandlerFactory = context.RequestServices.GetRequiredService();
+ var exceptionWrapContext = new ExceptionWrapContext(
+ exception,
+ errorInfo,
+ context.RequestServices);
+ exceptionWrapHandlerFactory.CreateFor(exceptionWrapContext).Wrap(exceptionWrapContext);
+
+ if (context.Request.CanAccept(MimeTypes.Application.Json) ||
+ context.Request.IsAjax())
+ {
+ var wrapResult = new WrapResult(
+ exceptionWrapContext.ErrorInfo.Code,
+ exceptionWrapContext.ErrorInfo.Message,
+ exceptionWrapContext.ErrorInfo.Details);
+
+ var jsonSerializer = context.RequestServices.GetRequiredService();
+
+ context.Response.Headers.Add(AbpHttpWrapConsts.AbpWrapResult, "true");
+ context.Response.StatusCode = (int)HttpStatusCode.OK;
+
+ await context.Response.WriteAsync(jsonSerializer.Serialize(wrapResult));
+ return;
+ }
+
+ context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
+ await context.Response.WriteAsync(errorInfo.Message);
+ }
+
+ private static string CalculationSignature(string url, string appKey, IDictionary queryDictionary)
+ {
+ var queryString = BuildQuery(queryDictionary);
+ var encodeUrl = UrlEncode(string.Concat(url, "?", queryString, appKey));
+
+ return encodeUrl.ToMd5();
+ }
+
+ private static string BuildQuery(IDictionary queryStringDictionary)
+ {
+ StringBuilder sb = new StringBuilder();
+ foreach (var queryString in queryStringDictionary)
+ {
+ sb.Append(queryString.Key)
+ .Append('=')
+ .Append(queryString.Value)
+ .Append('&');
+ }
+ sb.Remove(sb.Length - 1, 1);
+ return sb.ToString();
+ }
+
+ private static string UrlEncode(string str)
+ {
+ return HttpUtility.UrlEncode(str);
+ }
+ }
+}
diff --git a/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi.Authorization/Microsoft/AspNetCore/Builder/OpenApiAuthorizationApplicationBuilderExtensions.cs b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi.Authorization/Microsoft/AspNetCore/Builder/OpenApiAuthorizationApplicationBuilderExtensions.cs
new file mode 100644
index 000000000..686482c02
--- /dev/null
+++ b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi.Authorization/Microsoft/AspNetCore/Builder/OpenApiAuthorizationApplicationBuilderExtensions.cs
@@ -0,0 +1,12 @@
+using LINGYUN.Abp.OpenApi.Authorization;
+
+namespace Microsoft.AspNetCore.Builder
+{
+ public static class OpenApiAuthorizationApplicationBuilderExtensions
+ {
+ public static IApplicationBuilder UseOpenApiAuthorization(this IApplicationBuilder app)
+ {
+ return app.UseMiddleware();
+ }
+ }
+}
diff --git a/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN.Abp.OpenApi.csproj b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN.Abp.OpenApi.csproj
new file mode 100644
index 000000000..2f65cac0e
--- /dev/null
+++ b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN.Abp.OpenApi.csproj
@@ -0,0 +1,25 @@
+
+
+
+
+
+ netstandard2.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/AbpOpenApiConsts.cs b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/AbpOpenApiConsts.cs
new file mode 100644
index 000000000..e636f4c88
--- /dev/null
+++ b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/AbpOpenApiConsts.cs
@@ -0,0 +1,22 @@
+namespace LINGYUN.Abp.OpenApi
+{
+ 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 KeyPrefix = "AbpOpenApi";
+
+ public const string InvalidAccessWithAppKey = KeyPrefix + ":9100";
+ public const string InvalidAccessWithAppKeyNotFound = KeyPrefix + ":9101";
+
+ public const string InvalidAccessWithSign = KeyPrefix + ":9110";
+ public const string InvalidAccessWithSignNotFound = KeyPrefix + ":9111";
+
+ public const string InvalidAccessWithTimestamp = KeyPrefix + ":9210";
+ public const string InvalidAccessWithTimestampNotFound = KeyPrefix + ":9211";
+ }
+}
diff --git a/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/AbpOpenApiModule.cs b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/AbpOpenApiModule.cs
new file mode 100644
index 000000000..e3eae6d14
--- /dev/null
+++ b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/AbpOpenApiModule.cs
@@ -0,0 +1,40 @@
+using LINGYUN.Abp.OpenApi.Localization;
+using Microsoft.Extensions.DependencyInjection;
+using Volo.Abp.Localization;
+using Volo.Abp.Localization.ExceptionHandling;
+using Volo.Abp.Modularity;
+using Volo.Abp.Security;
+using Volo.Abp.VirtualFileSystem;
+
+namespace LINGYUN.Abp.OpenApi
+{
+ [DependsOn(
+ typeof(AbpSecurityModule),
+ typeof(AbpLocalizationModule))]
+ public class AbpOpenApiModule : AbpModule
+ {
+ public override void ConfigureServices(ServiceConfigurationContext context)
+ {
+ var configuration = context.Services.GetConfiguration();
+
+ Configure(configuration.GetSection("OpenApi"));
+
+ Configure(options =>
+ {
+ options.FileSets.AddEmbedded();
+ });
+
+ Configure(options =>
+ {
+ options.Resources
+ .Add()
+ .AddVirtualJson("/LINGYUN/Abp/OpenApi/Localization/Resources");
+ });
+
+ Configure(options =>
+ {
+ options.MapCodeNamespace(AbpOpenApiConsts.KeyPrefix, typeof(OpenApiResource));
+ });
+ }
+ }
+}
diff --git a/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/AbpOpenApiOptions.cs b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/AbpOpenApiOptions.cs
new file mode 100644
index 000000000..bb6c4808b
--- /dev/null
+++ b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/AbpOpenApiOptions.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace LINGYUN.Abp.OpenApi
+{
+ public class AbpOpenApiOptions
+ {
+ public bool IsEnabled { get; set; }
+ public string[] WhiteIpAddress { get; set; }
+ public string[] WhiteClient { get; set; }
+ public AbpOpenApiOptions()
+ {
+ IsEnabled = true;
+ WhiteIpAddress = new string[0];
+ WhiteClient = new string[0];
+ }
+
+ public bool HasWhiteIpAddress(string ipAddress)
+ {
+ return WhiteIpAddress?.Contains(ipAddress) == true;
+ }
+
+ public bool HasWhiteClient(string clientId)
+ {
+ if (clientId.IsNullOrWhiteSpace())
+ {
+ return false;
+ }
+ return WhiteClient?.Contains(clientId) == true;
+ }
+ }
+}
diff --git a/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/AppDescriptor.cs b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/AppDescriptor.cs
new file mode 100644
index 000000000..903048140
--- /dev/null
+++ b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/AppDescriptor.cs
@@ -0,0 +1,42 @@
+namespace LINGYUN.Abp.OpenApi
+{
+ public class AppDescriptor
+ {
+ ///
+ /// 应用名称
+ ///
+ public string AppName { get; set; }
+ ///
+ /// 应用标识
+ ///
+ public string AppKey { get; set; }
+ ///
+ /// 应用密钥
+ ///
+ public string AppSecret { get; set; }
+ ///
+ /// 应用token
+ ///
+ public string AppToken { get; set; }
+ ///
+ /// 签名有效时间
+ /// 单位: s
+ ///
+ public int? SignLifetime { get; set; }
+
+ public AppDescriptor() { }
+ public AppDescriptor(
+ string appName,
+ string appKey,
+ string appSecret,
+ string appToken = null,
+ int? signLifeTime = null)
+ {
+ AppName = appName;
+ AppKey = appKey;
+ AppSecret = appSecret;
+ AppToken = appToken;
+ SignLifetime = signLifeTime;
+ }
+ }
+}
diff --git a/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/ConfigurationStore/AbpDefaultAppKeyStoreOptions.cs b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/ConfigurationStore/AbpDefaultAppKeyStoreOptions.cs
new file mode 100644
index 000000000..ac89b6d69
--- /dev/null
+++ b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/ConfigurationStore/AbpDefaultAppKeyStoreOptions.cs
@@ -0,0 +1,12 @@
+namespace LINGYUN.Abp.OpenApi.ConfigurationStore
+{
+ public class AbpDefaultAppKeyStoreOptions
+ {
+ public AppDescriptor[] Apps { get; set; }
+
+ public AbpDefaultAppKeyStoreOptions()
+ {
+ Apps = new AppDescriptor[0];
+ }
+ }
+}
diff --git a/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/ConfigurationStore/DefaultAppKeyStore.cs b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/ConfigurationStore/DefaultAppKeyStore.cs
new file mode 100644
index 000000000..c3d1d9561
--- /dev/null
+++ b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/ConfigurationStore/DefaultAppKeyStore.cs
@@ -0,0 +1,28 @@
+using Microsoft.Extensions.Options;
+using System.Linq;
+using System.Threading.Tasks;
+using Volo.Abp.DependencyInjection;
+
+namespace LINGYUN.Abp.OpenApi.ConfigurationStore
+{
+ [Dependency(TryRegister = true)]
+ public class DefaultAppKeyStore : IAppKeyStore, ITransientDependency
+ {
+ private readonly AbpDefaultAppKeyStoreOptions _options;
+
+ public DefaultAppKeyStore(IOptionsMonitor options)
+ {
+ _options = options.CurrentValue;
+ }
+
+ public Task FindAsync(string appKey)
+ {
+ return Task.FromResult(Find(appKey));
+ }
+
+ public AppDescriptor Find(string appKey)
+ {
+ return _options.Apps?.FirstOrDefault(t => t.AppKey == appKey);
+ }
+ }
+}
diff --git a/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/IAppKeyStore.cs b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/IAppKeyStore.cs
new file mode 100644
index 000000000..047d81f76
--- /dev/null
+++ b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/IAppKeyStore.cs
@@ -0,0 +1,9 @@
+using System.Threading.Tasks;
+
+namespace LINGYUN.Abp.OpenApi
+{
+ public interface IAppKeyStore
+ {
+ Task FindAsync(string appKey);
+ }
+}
diff --git a/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/Localization/OpenApiResource.cs b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/Localization/OpenApiResource.cs
new file mode 100644
index 000000000..3215ca85d
--- /dev/null
+++ b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/Localization/OpenApiResource.cs
@@ -0,0 +1,9 @@
+using Volo.Abp.Localization;
+
+namespace LINGYUN.Abp.OpenApi.Localization
+{
+ [LocalizationResourceName("OpenApi")]
+ public class OpenApiResource
+ {
+ }
+}
diff --git a/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/Localization/Resources/en.json b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/Localization/Resources/en.json
new file mode 100644
index 000000000..cffd482c2
--- /dev/null
+++ b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/Localization/Resources/en.json
@@ -0,0 +1,11 @@
+{
+ "culture": "en",
+ "texts": {
+ "AbpOpenApi:9100": "Invalid appKey {AppKey}.",
+ "AbpOpenApi:9101": "appKey not found.",
+ "AbpOpenApi:9110": "Invalid sign.",
+ "AbpOpenApi:9111": "sign not found.",
+ "AbpOpenApi:9210": "Request timed out or the session expired.",
+ "AbpOpenApi:9211": "timestamp not found."
+ }
+}
\ No newline at end of file
diff --git a/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/Localization/Resources/zh-Hans.json b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/Localization/Resources/zh-Hans.json
new file mode 100644
index 000000000..0853fa4df
--- /dev/null
+++ b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/Localization/Resources/zh-Hans.json
@@ -0,0 +1,11 @@
+{
+ "culture": "zh-Hans",
+ "texts": {
+ "AbpOpenApi:9100": "无效的应用标识 {AppKey}.",
+ "AbpOpenApi:9101": "未携带应用标识(appKey).",
+ "AbpOpenApi:9110": "无效的签名 sign.",
+ "AbpOpenApi:9111": "未携带签名(sign).",
+ "AbpOpenApi:9210": "请求超时或会话已过期.",
+ "AbpOpenApi:9211": "未携带时间戳标识."
+ }
+}
\ No newline at end of file
diff --git a/aspnet-core/modules/open-api/OpenApi.Sdk/Microsoft/Extensions/DependencyInjection/ClientProxyServiceCollectionExtensions.cs b/aspnet-core/modules/open-api/OpenApi.Sdk/Microsoft/Extensions/DependencyInjection/ClientProxyServiceCollectionExtensions.cs
new file mode 100644
index 000000000..409c762db
--- /dev/null
+++ b/aspnet-core/modules/open-api/OpenApi.Sdk/Microsoft/Extensions/DependencyInjection/ClientProxyServiceCollectionExtensions.cs
@@ -0,0 +1,19 @@
+using OpenApi;
+using System;
+
+namespace Microsoft.Extensions.DependencyInjection
+{
+ public static class ClientProxyServiceCollectionExtensions
+ {
+ public static IServiceCollection AddClientProxy(this IServiceCollection services, string serverUrl)
+ {
+ services.AddHttpClient("opensdk", options =>
+ {
+ options.BaseAddress = new Uri(serverUrl);
+ });
+ services.AddSingleton();
+
+ return services;
+ }
+ }
+}
diff --git a/aspnet-core/modules/open-api/OpenApi.Sdk/OpenApi.Sdk.csproj b/aspnet-core/modules/open-api/OpenApi.Sdk/OpenApi.Sdk.csproj
new file mode 100644
index 000000000..5b2285436
--- /dev/null
+++ b/aspnet-core/modules/open-api/OpenApi.Sdk/OpenApi.Sdk.csproj
@@ -0,0 +1,13 @@
+
+
+
+ netstandard2.0
+
+
+
+
+
+
+
+
+
diff --git a/aspnet-core/modules/open-api/OpenApi.Sdk/OpenApi/ApiResponse.cs b/aspnet-core/modules/open-api/OpenApi.Sdk/OpenApi/ApiResponse.cs
new file mode 100644
index 000000000..4f9111d68
--- /dev/null
+++ b/aspnet-core/modules/open-api/OpenApi.Sdk/OpenApi/ApiResponse.cs
@@ -0,0 +1,25 @@
+using System;
+
+namespace OpenApi
+{
+ [Serializable]
+ public class ApiResponse : ApiResponse