diff --git a/PearAdmin.AbpTemplate.sln b/PearAdmin.AbpTemplate.sln index d12370d625e22be0c7051270b612c5288ac5758d..6c2bea48e1048bd1b8362aad913f92724321c41b 100644 --- a/PearAdmin.AbpTemplate.sln +++ b/PearAdmin.AbpTemplate.sln @@ -39,6 +39,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PearAdmin.AbpTemplate.Stora EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PearAdmin.AbpTemplate.NLog", "module\PearAdmin.AbpTemplate.NLog\PearAdmin.AbpTemplate.NLog.csproj", "{9AFB01E8-783F-472E-90D6-F2A8AD6120FF}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PearAdmin.AbpTemplate.MiniProgram", "module\PearAdmin.AbpTemplate.MiniProgram\PearAdmin.AbpTemplate.MiniProgram.csproj", "{A285A2DC-3435-48A4-B005-D47710AA351D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PearAdmin.AbpTemplate.ExternalAuth", "module\PearAdmin.AbpTemplate.ExternalAuth\PearAdmin.AbpTemplate.ExternalAuth.csproj", "{B55DAA71-4958-485D-8888-E185C2104931}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -101,6 +105,14 @@ Global {9AFB01E8-783F-472E-90D6-F2A8AD6120FF}.Debug|Any CPU.Build.0 = Debug|Any CPU {9AFB01E8-783F-472E-90D6-F2A8AD6120FF}.Release|Any CPU.ActiveCfg = Release|Any CPU {9AFB01E8-783F-472E-90D6-F2A8AD6120FF}.Release|Any CPU.Build.0 = Release|Any CPU + {A285A2DC-3435-48A4-B005-D47710AA351D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A285A2DC-3435-48A4-B005-D47710AA351D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A285A2DC-3435-48A4-B005-D47710AA351D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A285A2DC-3435-48A4-B005-D47710AA351D}.Release|Any CPU.Build.0 = Release|Any CPU + {B55DAA71-4958-485D-8888-E185C2104931}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B55DAA71-4958-485D-8888-E185C2104931}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B55DAA71-4958-485D-8888-E185C2104931}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B55DAA71-4958-485D-8888-E185C2104931}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -120,6 +132,8 @@ Global {F17AF2E0-E290-43A6-8695-B653FA6446EE} = {AFAA0841-BD93-466F-B8F4-FB4EEC86F1FC} {91E47E05-561D-42C2-AFB8-2F567185E177} = {A9CEE774-07BE-415D-935C-D42562C045F9} {9AFB01E8-783F-472E-90D6-F2A8AD6120FF} = {A9CEE774-07BE-415D-935C-D42562C045F9} + {A285A2DC-3435-48A4-B005-D47710AA351D} = {A9CEE774-07BE-415D-935C-D42562C045F9} + {B55DAA71-4958-485D-8888-E185C2104931} = {A9CEE774-07BE-415D-935C-D42562C045F9} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C6B33810-FA59-48A3-9DC4-F1F0312C57F6} diff --git a/module/PearAdmin.AbpTemplate.ExternalAuth/AbpTemplateExternalAuthModule.cs b/module/PearAdmin.AbpTemplate.ExternalAuth/AbpTemplateExternalAuthModule.cs new file mode 100644 index 0000000000000000000000000000000000000000..7a569e7d042f45d39da0829536513fa70beb74cc --- /dev/null +++ b/module/PearAdmin.AbpTemplate.ExternalAuth/AbpTemplateExternalAuthModule.cs @@ -0,0 +1,14 @@ +using Abp; +using Abp.Modules; + +namespace PearAdmin.AbpTemplate.ExternalAuth +{ + [DependsOn(typeof(AbpKernelModule))] + public class AbpTemplateExternalAuthModule : AbpModule + { + public override void Initialize() + { + IocManager.RegisterAssemblyByConvention(typeof(AbpTemplateExternalAuthModule).Assembly); + } + } +} diff --git a/module/PearAdmin.AbpTemplate.ExternalAuth/ExternalAuthConfiguration.cs b/module/PearAdmin.AbpTemplate.ExternalAuth/ExternalAuthConfiguration.cs new file mode 100644 index 0000000000000000000000000000000000000000..768743d8c2a7b859d5f7c0265e5e8298ca33f796 --- /dev/null +++ b/module/PearAdmin.AbpTemplate.ExternalAuth/ExternalAuthConfiguration.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using Abp.Dependency; + +namespace PearAdmin.AbpTemplate.ExternalAuth +{ + public class ExternalAuthConfiguration : IExternalAuthConfiguration, ISingletonDependency + { + public List Providers { get; } + + public ExternalAuthConfiguration() + { + Providers = new List(); + } + } +} diff --git a/module/PearAdmin.AbpTemplate.ExternalAuth/ExternalAuthManager.cs b/module/PearAdmin.AbpTemplate.ExternalAuth/ExternalAuthManager.cs new file mode 100644 index 0000000000000000000000000000000000000000..72c52f1653c23d773d046e00c7305d7c193ac1b5 --- /dev/null +++ b/module/PearAdmin.AbpTemplate.ExternalAuth/ExternalAuthManager.cs @@ -0,0 +1,41 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Abp.Dependency; + +namespace PearAdmin.AbpTemplate.ExternalAuth +{ + public class ExternalAuthManager : IExternalAuthManager, ITransientDependency + { + private readonly IIocResolver _iocResolver; + private readonly IExternalAuthConfiguration _externalAuthConfiguration; + + public ExternalAuthManager(IIocResolver iocResolver, IExternalAuthConfiguration externalAuthConfiguration) + { + _iocResolver = iocResolver; + _externalAuthConfiguration = externalAuthConfiguration; + } + + public Task IsValidUser(string provider, string providerKey, string providerAccessCode) + { + using (IDisposableDependencyObjectWrapper providerApi = CreateProviderApi(provider)) + return providerApi.Object.IsValidUser(providerKey, providerAccessCode); + } + + public Task GetUserInfo(string provider, string accessCode) + { + using (IDisposableDependencyObjectWrapper providerApi = CreateProviderApi(provider)) + return providerApi.Object.GetUserInfo(accessCode); + } + + public IDisposableDependencyObjectWrapper CreateProviderApi(string provider) + { + var providerInfo = _externalAuthConfiguration.Providers.Single(infoProvider => infoProvider.Name == provider).GetExternalLoginInfo(); + if (providerInfo == null) + throw new Exception("Unknown external auth provider: " + provider); + IDisposableDependencyObjectWrapper dependencyObjectWrapper = IocResolverExtensions.ResolveAsDisposable(_iocResolver, providerInfo.ProviderApiType); + dependencyObjectWrapper.Object.Initialize(providerInfo); + return dependencyObjectWrapper; + } + } +} diff --git a/module/PearAdmin.AbpTemplate.ExternalAuth/ExternalAuthProviderApiBase.cs b/module/PearAdmin.AbpTemplate.ExternalAuth/ExternalAuthProviderApiBase.cs new file mode 100644 index 0000000000000000000000000000000000000000..4cc820accf7dd5b02398c6750d1eb914c162a04d --- /dev/null +++ b/module/PearAdmin.AbpTemplate.ExternalAuth/ExternalAuthProviderApiBase.cs @@ -0,0 +1,22 @@ +using System.Threading.Tasks; +using Abp.Dependency; + +namespace PearAdmin.AbpTemplate.ExternalAuth +{ + public abstract class ExternalAuthProviderApiBase : IExternalAuthProviderApi, ITransientDependency + { + public ExternalLoginProviderInfo ProviderInfo { get; set; } + + public virtual void Initialize(ExternalLoginProviderInfo providerInfo) + { + ProviderInfo = providerInfo; + } + + public async Task IsValidUser(string userId, string accessCode) + { + return (await GetUserInfo(accessCode)).ProviderKey == userId; + } + + public abstract Task GetUserInfo(string accessCode); + } +} diff --git a/module/PearAdmin.AbpTemplate.ExternalAuth/ExternalAuthUserInfo.cs b/module/PearAdmin.AbpTemplate.ExternalAuth/ExternalAuthUserInfo.cs new file mode 100644 index 0000000000000000000000000000000000000000..6582e62f422f6056c582544d2b3c274c0636116e --- /dev/null +++ b/module/PearAdmin.AbpTemplate.ExternalAuth/ExternalAuthUserInfo.cs @@ -0,0 +1,15 @@ +namespace PearAdmin.AbpTemplate.ExternalAuth +{ + public class ExternalAuthUserInfo + { + public string ProviderKey { get; set; } + + public string Name { get; set; } + + public string EmailAddress { get; set; } + + public string Surname { get; set; } + + public string Provider { get; set; } + } +} diff --git a/module/PearAdmin.AbpTemplate.ExternalAuth/ExternalLoginProviderInfo.cs b/module/PearAdmin.AbpTemplate.ExternalAuth/ExternalLoginProviderInfo.cs new file mode 100644 index 0000000000000000000000000000000000000000..7ea97297e75be33a5b1a6b207a26024095920560 --- /dev/null +++ b/module/PearAdmin.AbpTemplate.ExternalAuth/ExternalLoginProviderInfo.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; + +namespace PearAdmin.AbpTemplate.ExternalAuth +{ + public class ExternalLoginProviderInfo + { + public string Name { get; set; } + + public string ClientId { get; set; } + + public string ClientSecret { get; set; } + + public Type ProviderApiType { get; set; } + + public Dictionary AdditionalParams { get; set; } + + public ExternalLoginProviderInfo(string name, string clientId, string clientSecret, Type providerApiType, Dictionary additionalParams = null) + { + Name = name; + ClientId = clientId; + ClientSecret = clientSecret; + ProviderApiType = providerApiType; + AdditionalParams = additionalParams ?? new Dictionary(); + } + } +} diff --git a/module/PearAdmin.AbpTemplate.ExternalAuth/IExternalAuthConfiguration.cs b/module/PearAdmin.AbpTemplate.ExternalAuth/IExternalAuthConfiguration.cs new file mode 100644 index 0000000000000000000000000000000000000000..863571824f1f72bb686bb9a8fc7a4aba0f26f646 --- /dev/null +++ b/module/PearAdmin.AbpTemplate.ExternalAuth/IExternalAuthConfiguration.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace PearAdmin.AbpTemplate.ExternalAuth +{ + public interface IExternalAuthConfiguration + { + List Providers { get; } + } +} diff --git a/module/PearAdmin.AbpTemplate.ExternalAuth/IExternalAuthManager.cs b/module/PearAdmin.AbpTemplate.ExternalAuth/IExternalAuthManager.cs new file mode 100644 index 0000000000000000000000000000000000000000..1b9015ecdb4d1f8a66df4a38e6e33f41e4685835 --- /dev/null +++ b/module/PearAdmin.AbpTemplate.ExternalAuth/IExternalAuthManager.cs @@ -0,0 +1,11 @@ +using System.Threading.Tasks; + +namespace PearAdmin.AbpTemplate.ExternalAuth +{ + public interface IExternalAuthManager + { + Task IsValidUser(string provider, string providerKey, string providerAccessCode); + + Task GetUserInfo(string provider, string accessCode); + } +} diff --git a/module/PearAdmin.AbpTemplate.ExternalAuth/IExternalAuthProviderApi.cs b/module/PearAdmin.AbpTemplate.ExternalAuth/IExternalAuthProviderApi.cs new file mode 100644 index 0000000000000000000000000000000000000000..0d646fb9ec5b7a1651487617738edf51f8bab1c3 --- /dev/null +++ b/module/PearAdmin.AbpTemplate.ExternalAuth/IExternalAuthProviderApi.cs @@ -0,0 +1,15 @@ +using System.Threading.Tasks; + +namespace PearAdmin.AbpTemplate.ExternalAuth +{ + public interface IExternalAuthProviderApi + { + ExternalLoginProviderInfo ProviderInfo { get; } + + Task IsValidUser(string userId, string accessCode); + + Task GetUserInfo(string accessCode); + + void Initialize(ExternalLoginProviderInfo providerInfo); + } +} \ No newline at end of file diff --git a/module/PearAdmin.AbpTemplate.ExternalAuth/IExternalLoginInfoProvider.cs b/module/PearAdmin.AbpTemplate.ExternalAuth/IExternalLoginInfoProvider.cs new file mode 100644 index 0000000000000000000000000000000000000000..ae6744a99389b5f7bc27290737a613397a094eff --- /dev/null +++ b/module/PearAdmin.AbpTemplate.ExternalAuth/IExternalLoginInfoProvider.cs @@ -0,0 +1,9 @@ +namespace PearAdmin.AbpTemplate.ExternalAuth +{ + public interface IExternalLoginInfoProvider + { + string Name { get; } + + ExternalLoginProviderInfo GetExternalLoginInfo(); + } +} diff --git a/module/PearAdmin.AbpTemplate.ExternalAuth/PearAdmin.AbpTemplate.ExternalAuth.csproj b/module/PearAdmin.AbpTemplate.ExternalAuth/PearAdmin.AbpTemplate.ExternalAuth.csproj new file mode 100644 index 0000000000000000000000000000000000000000..a8e3113908e770cae2bee546600904e02abc27ba --- /dev/null +++ b/module/PearAdmin.AbpTemplate.ExternalAuth/PearAdmin.AbpTemplate.ExternalAuth.csproj @@ -0,0 +1,12 @@ + + + + 1.0.0.0 + net5.0 + + + + + + + diff --git a/module/PearAdmin.AbpTemplate.MiniProgram/AbpTemplateMiniProgramModule.cs b/module/PearAdmin.AbpTemplate.MiniProgram/AbpTemplateMiniProgramModule.cs new file mode 100644 index 0000000000000000000000000000000000000000..f43b8b62c915391eb168f99d34423444dffd06a3 --- /dev/null +++ b/module/PearAdmin.AbpTemplate.MiniProgram/AbpTemplateMiniProgramModule.cs @@ -0,0 +1,14 @@ +using Abp.Modules; +using PearAdmin.AbpTemplate.ExternalAuth; + +namespace PearAdmin.AbpTemplate.MiniProgram +{ + [DependsOn(typeof(AbpTemplateExternalAuthModule))] + public class AbpTemplateMiniProgramModule : AbpModule + { + public override void Initialize() + { + IocManager.RegisterAssemblyByConvention(typeof(AbpTemplateMiniProgramModule).Assembly); + } + } +} diff --git a/module/PearAdmin.AbpTemplate.MiniProgram/Enums/RequestErrorCodeEnum.cs b/module/PearAdmin.AbpTemplate.MiniProgram/Enums/RequestErrorCodeEnum.cs new file mode 100644 index 0000000000000000000000000000000000000000..809c0ba3317fb9845634b6774e5f4d747be49ff2 --- /dev/null +++ b/module/PearAdmin.AbpTemplate.MiniProgram/Enums/RequestErrorCodeEnum.cs @@ -0,0 +1,11 @@ +namespace PearAdmin.AbpTemplate.MiniProgram.Enums +{ + public enum RequestErrorCodeEnum + { + Busy = -1, + Succeed = 0, + CodeError = 40029, + FrequencyLimit = 45011, + HighRiskUser = 40226 + } +} diff --git a/module/PearAdmin.AbpTemplate.MiniProgram/MiniProgramAuthProviderApi.cs b/module/PearAdmin.AbpTemplate.MiniProgram/MiniProgramAuthProviderApi.cs new file mode 100644 index 0000000000000000000000000000000000000000..0199295fb46892b653d526db4bb6fb05004c3430 --- /dev/null +++ b/module/PearAdmin.AbpTemplate.MiniProgram/MiniProgramAuthProviderApi.cs @@ -0,0 +1,89 @@ +using System.Threading.Tasks; +using Abp.Runtime.Caching; +using Abp.UI; +using Castle.Core.Logging; +using PearAdmin.AbpTemplate.ExternalAuth; +using PearAdmin.AbpTemplate.MiniProgram.Enums; +using SKIT.FlurlHttpClient.Wechat.Api; +using SKIT.FlurlHttpClient.Wechat.Api.Models; +using Snowflake.Core; + +namespace PearAdmin.AbpTemplate.MiniProgram +{ + public class MiniProgramAuthProviderApi : ExternalAuthProviderApiBase + { + public const string ProviderName = "WeChatMiniProgram"; + private WechatApiClient _weChatApiClient; + private readonly ILogger _logger; + private readonly ICacheManager _cacheManager; + + public MiniProgramAuthProviderApi(ILogger logger, + ICacheManager cacheManager) + { + _logger = logger; + _cacheManager = cacheManager; + } + + public override void Initialize(ExternalLoginProviderInfo providerInfo) + { + base.Initialize(providerInfo); + _weChatApiClient = new WechatApiClient(new WechatApiClientOptions() + { + AppId = ProviderInfo.ClientId, + AppSecret = ProviderInfo.ClientSecret, + }); + } + + public async override Task GetUserInfo(string accessCode) + { + var miniProgramSessionKey = await GetWeChatUserOpenId(accessCode); + var randomId = GenerateRandomId(); + var externalAuthUserInfo = new ExternalAuthUserInfo + { + EmailAddress = randomId + $"@{ProviderName}.com",//邮箱唯一性 + Surname = randomId.ToString(), + Name = randomId.ToString(), + ProviderKey = miniProgramSessionKey.OpenId, + Provider = ProviderName, + }; + + return externalAuthUserInfo; + } + + private long GenerateRandomId() + { + var idWorker = new IdWorker(1, 1); + var id = idWorker.NextId(); + return id; + } + + private async Task GetWeChatUserOpenId(string code) + { + var response = await _weChatApiClient.ExecuteSnsJsCode2SessionAsync(new SnsJsCode2SessionRequest() + { + JsCode = code + }); + + if (response.ErrorCode == (int)RequestErrorCodeEnum.Succeed) + { + return new MiniProgramSessionKey() + { + OpenId = response.OpenId, + SessionKey = response.SessionKey, + UnionId = response.UnionId + }; + } + if (response.ErrorCode == (int)RequestErrorCodeEnum.CodeError) + { + throw new UserFriendlyException("There was an error calling the mini program api code"); + } + + if (response.ErrorCode == (int)RequestErrorCodeEnum.Busy) + { + throw new UserFriendlyException("The wechat mini program api is busy"); + } + + throw new UserFriendlyException("The request for authentication failed"); + } + } +} diff --git a/module/PearAdmin.AbpTemplate.MiniProgram/MiniProgramExternalLoginInfoProvider.cs b/module/PearAdmin.AbpTemplate.MiniProgram/MiniProgramExternalLoginInfoProvider.cs new file mode 100644 index 0000000000000000000000000000000000000000..9aefd18688809f422123000c6bf35794cdfb0533 --- /dev/null +++ b/module/PearAdmin.AbpTemplate.MiniProgram/MiniProgramExternalLoginInfoProvider.cs @@ -0,0 +1,27 @@ +using PearAdmin.AbpTemplate.ExternalAuth; + +namespace PearAdmin.AbpTemplate.MiniProgram +{ + public class MiniProgramExternalLoginInfoProvider : IExternalLoginInfoProvider + { + public string Name { get; } = "WeChatMiniProgram"; + + protected string ConsumerKey { get; set; } + + protected string ConsumerSecret { get; set; } + + protected ExternalLoginProviderInfo ExternalLoginProviderInfo { get; set; } + + public MiniProgramExternalLoginInfoProvider(string appId, string appSecret) + { + ConsumerKey = appId; + ConsumerSecret = appSecret; + ExternalLoginProviderInfo = new ExternalLoginProviderInfo(Name, ConsumerKey, ConsumerSecret, typeof(MiniProgramAuthProviderApi), null); + } + + public virtual ExternalLoginProviderInfo GetExternalLoginInfo() + { + return ExternalLoginProviderInfo; + } + } +} diff --git a/module/PearAdmin.AbpTemplate.MiniProgram/MiniProgramSessionKey.cs b/module/PearAdmin.AbpTemplate.MiniProgram/MiniProgramSessionKey.cs new file mode 100644 index 0000000000000000000000000000000000000000..da3641f07e0f0e8ea5c7b16e292b8a9c8b60de99 --- /dev/null +++ b/module/PearAdmin.AbpTemplate.MiniProgram/MiniProgramSessionKey.cs @@ -0,0 +1,9 @@ +namespace PearAdmin.AbpTemplate.MiniProgram +{ + public class MiniProgramSessionKey + { + public string OpenId { get; set; } + public string SessionKey { get; set; } + public string UnionId { get; set; } + } +} diff --git a/module/PearAdmin.AbpTemplate.MiniProgram/PearAdmin.AbpTemplate.MiniProgram.csproj b/module/PearAdmin.AbpTemplate.MiniProgram/PearAdmin.AbpTemplate.MiniProgram.csproj new file mode 100644 index 0000000000000000000000000000000000000000..d9dbcad2eb01aa6333881ec5b0a34150f3b30dd5 --- /dev/null +++ b/module/PearAdmin.AbpTemplate.MiniProgram/PearAdmin.AbpTemplate.MiniProgram.csproj @@ -0,0 +1,19 @@ + + + + 1.0.0.0 + net5.0 + + + + + + + + + + + + + + diff --git a/src/PearAdmin.AbpTemplate.Admin/AbpTemplateAdminModule.cs b/src/PearAdmin.AbpTemplate.Admin/AbpTemplateAdminModule.cs index d56b49e522792e4ceb04593d8efc5f5ec3f51160..428976ea6ad8ad67f7b1c297e79317f20f8de6cc 100644 --- a/src/PearAdmin.AbpTemplate.Admin/AbpTemplateAdminModule.cs +++ b/src/PearAdmin.AbpTemplate.Admin/AbpTemplateAdminModule.cs @@ -13,7 +13,9 @@ using Microsoft.Extensions.Configuration; using PearAdmin.AbpTemplate.Admin.Configuration; using PearAdmin.AbpTemplate.Admin.Views; using PearAdmin.AbpTemplate.EntityFrameworkCore; +using PearAdmin.AbpTemplate.ExternalAuth; using PearAdmin.AbpTemplate.Gateway; +using PearAdmin.AbpTemplate.MiniProgram; namespace PearAdmin.AbpTemplate.Admin { @@ -23,7 +25,8 @@ namespace PearAdmin.AbpTemplate.Admin typeof(AbpTemplateGatewayModule), typeof(AbpAspNetCoreModule), typeof(AbpAspNetCoreSignalRModule), - typeof(AbpHangfireAspNetCoreModule) + typeof(AbpHangfireAspNetCoreModule), + typeof(AbpTemplateMiniProgramModule) )] public class AbpTemplateAdminModule : AbpModule { @@ -61,6 +64,23 @@ namespace PearAdmin.AbpTemplate.Admin SetAppFolders(); IocManager.Resolve() .AddApplicationPartsIfNotAddedBefore(typeof(AbpTemplateAdminModule).Assembly); + + ConfigureExternalAuthProviders(); + } + + private void ConfigureExternalAuthProviders() + { + var externalAuthConfiguration = IocManager.Resolve(); + + if (bool.Parse(_appConfiguration["Authentication:WeChatMiniProgram:IsEnabled"])) + { + externalAuthConfiguration.Providers.Add( + new MiniProgramExternalLoginInfoProvider( + _appConfiguration["Authentication:WeChatMiniProgram:AppId"], + _appConfiguration["Authentication:WeChatMiniProgram:AppSecret"] + ) + ); + } } public override void Shutdown() diff --git a/src/PearAdmin.AbpTemplate.Admin/Controllers/AccountController.cs b/src/PearAdmin.AbpTemplate.Admin/Controllers/AccountController.cs index 7610156872916b5d006236bb3034d2578404d2c4..55c2483fbfda6a41fee4210ff8bd77a1cd438f0d 100644 --- a/src/PearAdmin.AbpTemplate.Admin/Controllers/AccountController.cs +++ b/src/PearAdmin.AbpTemplate.Admin/Controllers/AccountController.cs @@ -1,29 +1,20 @@ using System; using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Security.Claims; using System.Threading.Tasks; -using Abp; -using Abp.AspNetCore.Mvc.Authorization; using Abp.Authorization; using Abp.Authorization.Users; -using Abp.Configuration; using Abp.Configuration.Startup; using Abp.Domain.Uow; using Abp.Extensions; using Abp.MultiTenancy; using Abp.Notifications; -using Abp.Threading; -using Abp.Timing; -using Abp.UI; using Abp.Web.Models; -using Abp.Zero.Configuration; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using PearAdmin.AbpTemplate.Admin.Models.Account; using PearAdmin.AbpTemplate.Authorization; using PearAdmin.AbpTemplate.Authorization.Users; +using PearAdmin.AbpTemplate.ExternalAuth; using PearAdmin.AbpTemplate.Identity; using PearAdmin.AbpTemplate.MultiTenancy; using PearAdmin.AbpTemplate.Sessions; @@ -43,6 +34,7 @@ namespace PearAdmin.AbpTemplate.Admin.Controllers private readonly ISessionAppService _sessionAppService; private readonly ITenantCache _tenantCache; private readonly INotificationPublisher _notificationPublisher; + private readonly IExternalAuthManager _externalAuthManager; public AccountController( UserManager userManager, @@ -55,7 +47,8 @@ namespace PearAdmin.AbpTemplate.Admin.Controllers UserRegistrationManager userRegistrationManager, ISessionAppService sessionAppService, ITenantCache tenantCache, - INotificationPublisher notificationPublisher) + INotificationPublisher notificationPublisher, + IExternalAuthManager externalAuthManager) { _userManager = userManager; _multiTenancyConfig = multiTenancyConfig; @@ -68,6 +61,7 @@ namespace PearAdmin.AbpTemplate.Admin.Controllers _sessionAppService = sessionAppService; _tenantCache = tenantCache; _notificationPublisher = notificationPublisher; + _externalAuthManager = externalAuthManager; } #region Login / Logout @@ -79,7 +73,7 @@ namespace PearAdmin.AbpTemplate.Admin.Controllers /// /// /// - public ActionResult HostLogin(string userNameOrEmailAddress = "", string returnUrl = "", string successMessage = "") + public ActionResult HostLogin(string returnUrl) { if (string.IsNullOrWhiteSpace(returnUrl)) { @@ -104,7 +98,7 @@ namespace PearAdmin.AbpTemplate.Admin.Controllers /// /// /// - public ActionResult Login(string userNameOrEmailAddress = "", string returnUrl = "", string successMessage = "") + public ActionResult Login(string returnUrl) { if (string.IsNullOrWhiteSpace(returnUrl)) { @@ -124,7 +118,7 @@ namespace PearAdmin.AbpTemplate.Admin.Controllers [HttpPost] [UnitOfWork] - public virtual async Task Login([FromBody]LoginViewModel loginModel) + public virtual async Task Login([FromBody] LoginViewModel loginModel) { loginModel.ReturnUrl = NormalizeReturnUrl(loginModel.ReturnUrl); if (!string.IsNullOrWhiteSpace(loginModel.ReturnUrlHash)) @@ -159,24 +153,6 @@ namespace PearAdmin.AbpTemplate.Admin.Controllers } } - #endregion - - #region Register - - public ActionResult Register() - { - return RegisterView(new RegisterViewModel()); - } - - private ActionResult RegisterView(RegisterViewModel model) - { - ViewBag.IsMultiTenancyEnabled = _multiTenancyConfig.IsEnabled; - - ViewBag.TencentId = AbpTemplateApplicationConsts.DefaultTenantId; - - return View("Register", model); - } - private bool IsSelfRegistrationEnabled() { if (!AbpSession.TenantId.HasValue) @@ -186,210 +162,83 @@ namespace PearAdmin.AbpTemplate.Admin.Controllers return true; } + #endregion + + #region External Login [HttpPost] [UnitOfWork] - public async Task Register(RegisterViewModel model) + public async Task ExternalLogin([FromBody] ExternalAuthenticateModel model) { - try + using (AbpSession.Use(AbpTemplateApplicationConsts.DefaultTenantId, null)) { - ExternalLoginInfo externalLoginInfo = null; - if (model.IsExternalLogin) - { - externalLoginInfo = await _signInManager.GetExternalLoginInfoAsync(); - if (externalLoginInfo == null) - { - throw new Exception("Can not external login!"); - } - - model.UserName = model.EmailAddress; - model.Password = Authorization.Users.User.CreateRandomPassword(); - } - else - { - if (model.UserName.IsNullOrEmpty() || model.Password.IsNullOrEmpty()) - { - throw new UserFriendlyException(L("FormIsNotValidMessage")); - } - } - - var user = await _userRegistrationManager.RegisterAsync( - model.Name, - model.Surname, - model.EmailAddress, - model.UserName, - model.Password, - true // Assumed email address is always confirmed. Change this if you want to implement email confirmation. - ); - - // Getting tenant-specific settings - var isEmailConfirmationRequiredForLogin = await SettingManager.GetSettingValueAsync(AbpZeroSettingNames.UserManagement.IsEmailConfirmationRequiredForLogin); - - if (model.IsExternalLogin) + var externalUser = await GetExternalUserInfo(model); + var loginResult = await _logInManager.LoginAsync(new UserLoginInfo(model.AuthProvider, externalUser.ProviderKey, model.AuthProvider), GetTenancyNameOrNull()); + switch (loginResult.Result) { - Debug.Assert(externalLoginInfo != null); - - if (string.Equals(externalLoginInfo.Principal.FindFirstValue(ClaimTypes.Email), model.EmailAddress, StringComparison.OrdinalIgnoreCase)) - { - user.IsEmailConfirmed = true; - } - - user.Logins = new List - { - new UserLogin + case AbpLoginResultType.Success: { - LoginProvider = externalLoginInfo.LoginProvider, - ProviderKey = externalLoginInfo.ProviderKey, - TenantId = user.TenantId + await _signInManager.SignInAsync(loginResult.Identity, true); + await UnitOfWorkManager.Current.SaveChangesAsync(); + return Json(new AjaxResponse { TargetUrl = model.ReturnUrl }); + } + case AbpLoginResultType.UnknownExternalLogin: + { + var user = await _userRegistrationManager.RegisterAsync( + externalUser.Name, + externalUser.Surname, + externalUser.EmailAddress, + externalUser.EmailAddress.ToMd5(), + Authorization.Users.User.CreateRandomPassword(), + true + ); + user.Logins = new List + { + new UserLogin + { + LoginProvider = externalUser.Provider, + ProviderKey = externalUser.ProviderKey, + TenantId = user.TenantId + } + }; + await CurrentUnitOfWork.SaveChangesAsync(); + + var tryLoginResult = await _logInManager.LoginAsync(new UserLoginInfo(model.AuthProvider, model.ProviderKey, model.AuthProvider), GetTenancyNameOrNull()); + + if (tryLoginResult.Result == AbpLoginResultType.Success) + { + await _signInManager.SignInAsync(loginResult.Identity, false); + return Json(new AjaxResponse { TargetUrl = model.ReturnUrl }); + } + + return Json(new AjaxResponse { TargetUrl = model.ReturnUrl }); + } + default: + { + throw _abpLoginResultTypeHelper.CreateExceptionForFailedLoginAttempt( + loginResult.Result, + model.ProviderKey, + GetTenancyNameOrNull() + ); } - }; - } - - await _unitOfWorkManager.Current.SaveChangesAsync(); - - Debug.Assert(user.TenantId != null); - - var tenant = await _tenantManager.GetByIdAsync(user.TenantId.Value); - - // Directly login if possible - if (user.IsActive && (user.IsEmailConfirmed || !isEmailConfirmationRequiredForLogin)) - { - AbpLoginResult loginResult; - if (externalLoginInfo != null) - { - loginResult = await _logInManager.LoginAsync(externalLoginInfo, tenant.TenancyName); - } - else - { - loginResult = await GetLoginResultAsync(user.UserName, model.Password, tenant.TenancyName); - } - - if (loginResult.Result == AbpLoginResultType.Success) - { - await _signInManager.SignInAsync(loginResult.Identity, false); - return Redirect(GetAppHomeUrl()); - } - - Logger.Warn("New registered user could not be login. This should not be normally. login result: " + loginResult.Result); } - - return View("RegisterResult", new RegisterResultViewModel - { - TenancyName = tenant.TenancyName, - NameAndSurname = user.Name + " " + user.Surname, - UserName = user.UserName, - EmailAddress = user.EmailAddress, - IsEmailConfirmed = user.IsEmailConfirmed, - IsActive = user.IsActive, - IsEmailConfirmationRequiredForLogin = isEmailConfirmationRequiredForLogin - }); - } - catch (UserFriendlyException ex) - { - ViewBag.ErrorMessage = ex.Message; - - return View("Register", model); - } - } - - #endregion - - #region External Login - - [HttpPost] - [ValidateAntiForgeryToken] - public ActionResult ExternalLogin(string provider, string returnUrl) - { - var redirectUrl = Url.Action( - "ExternalLoginCallback", - "Account", - new - { - ReturnUrl = returnUrl - }); - - return Challenge( - // TODO: ...? - // new Microsoft.AspNetCore.Http.Authentication.AuthenticationProperties - // { - // Items = { { "LoginProvider", provider } }, - // RedirectUri = redirectUrl - // }, - provider - ); - } - - [UnitOfWork] - public virtual async Task ExternalLoginCallback(string returnUrl, string remoteError = null) - { - returnUrl = NormalizeReturnUrl(returnUrl); - - if (remoteError != null) - { - Logger.Error("Remote Error in ExternalLoginCallback: " + remoteError); - throw new UserFriendlyException(L("CouldNotCompleteLoginOperation")); - } - - var externalLoginInfo = await _signInManager.GetExternalLoginInfoAsync(); - if (externalLoginInfo == null) - { - Logger.Warn("Could not get information from external login."); - return RedirectToAction(nameof(Login)); - } - - await _signInManager.SignOutAsync(); - - var tenancyName = GetTenancyNameOrNull(); - - var loginResult = await _logInManager.LoginAsync(externalLoginInfo, tenancyName); - - switch (loginResult.Result) - { - case AbpLoginResultType.Success: - await _signInManager.SignInAsync(loginResult.Identity, false); - return Redirect(returnUrl); - default: - throw _abpLoginResultTypeHelper.CreateExceptionForFailedLoginAttempt( - loginResult.Result, - externalLoginInfo.Principal.FindFirstValue(ClaimTypes.Email) ?? externalLoginInfo.ProviderKey, - tenancyName - ); } } - [UnitOfWork] - protected virtual async Task> FindPossibleTenantsOfUserAsync(UserLoginInfo login) + private async Task GetExternalUserInfo(ExternalAuthenticateModel model) { - List allUsers; - using (_unitOfWorkManager.Current.DisableFilter(AbpDataFilters.MayHaveTenant)) - { - allUsers = await _userManager.FindAllAsync(login); - } - - return allUsers - .Where(u => u.TenantId != null) - .Select(u => AsyncHelper.RunSync(() => _tenantManager.FindByIdAsync(u.TenantId.Value))) - .ToList(); + var userInfo = await _externalAuthManager.GetUserInfo(model.AuthProvider, model.ProviderAccessCode); + return userInfo; } - #endregion - #region Helpers - - public ActionResult RedirectToAppHome() - { - return RedirectToAction("Index", "Home"); - } + #region Common public string GetAppHomeUrl() { return Url.Action("Index", "Home"); } - #endregion - - #region Common - private string GetTenancyNameOrNull() { if (!AbpSession.TenantId.HasValue) @@ -421,36 +270,5 @@ namespace PearAdmin.AbpTemplate.Admin.Controllers } #endregion - - #region Etc - - /// - /// This is a demo code to demonstrate sending notification to default tenant admin and host admin uers. - /// Don't use this code in production !!! - /// - /// - /// - [AbpMvcAuthorize] - public async Task TestNotification(string message = "") - { - if (message.IsNullOrEmpty()) - { - message = "This is a test notification, created at " + Clock.Now; - } - - var defaultTenantAdmin = new UserIdentifier(1, 2); - var hostAdmin = new UserIdentifier(null, 1); - - await _notificationPublisher.PublishAsync( - "App.SimpleMessage", - new MessageNotificationData(message), - severity: NotificationSeverity.Info, - userIds: new[] { defaultTenantAdmin, hostAdmin } - ); - - return Content("Sent notification: " + message); - } - - #endregion } } diff --git a/src/PearAdmin.AbpTemplate.Admin/Models/Account/ExternalAuthenticateModel.cs b/src/PearAdmin.AbpTemplate.Admin/Models/Account/ExternalAuthenticateModel.cs new file mode 100644 index 0000000000000000000000000000000000000000..b8d284f38865749d5db441e66315d6ef2b6a92f7 --- /dev/null +++ b/src/PearAdmin.AbpTemplate.Admin/Models/Account/ExternalAuthenticateModel.cs @@ -0,0 +1,10 @@ +namespace PearAdmin.AbpTemplate.Admin.Models.Account +{ + public class ExternalAuthenticateModel + { + public string AuthProvider { get; set; } + public string ProviderKey { get; set; } + public string ProviderAccessCode { get; set; } + public string ReturnUrl { get; set; } + } +} diff --git a/src/PearAdmin.AbpTemplate.Admin/Models/Account/RegisterResultViewModel.cs b/src/PearAdmin.AbpTemplate.Admin/Models/Account/RegisterResultViewModel.cs deleted file mode 100644 index 6f25eceb4b102f616538003d41b654b093838077..0000000000000000000000000000000000000000 --- a/src/PearAdmin.AbpTemplate.Admin/Models/Account/RegisterResultViewModel.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace PearAdmin.AbpTemplate.Admin.Models.Account -{ - public class RegisterResultViewModel - { - public string TenancyName { get; set; } - - public string UserName { get; set; } - - public string EmailAddress { get; set; } - - public string NameAndSurname { get; set; } - - public bool IsActive { get; set; } - - public bool IsEmailConfirmationRequiredForLogin { get; set; } - - public bool IsEmailConfirmed { get; set; } - } -} diff --git a/src/PearAdmin.AbpTemplate.Admin/PearAdmin.AbpTemplate.Admin.csproj b/src/PearAdmin.AbpTemplate.Admin/PearAdmin.AbpTemplate.Admin.csproj index fca73baa10569c644e0987644f22cb5c4600b506..3f3ae0a484038e8badb825ee7f1e395a2b6f00e6 100644 --- a/src/PearAdmin.AbpTemplate.Admin/PearAdmin.AbpTemplate.Admin.csproj +++ b/src/PearAdmin.AbpTemplate.Admin/PearAdmin.AbpTemplate.Admin.csproj @@ -15,6 +15,7 @@ true + diff --git a/src/PearAdmin.AbpTemplate.Admin/Properties/launchSettings.json b/src/PearAdmin.AbpTemplate.Admin/Properties/launchSettings.json index 6ce592e41aefddea6e35e735ed318819ed433d39..e0fcdf7f5bf6e98b97abf15c23b587fe7d17a078 100644 --- a/src/PearAdmin.AbpTemplate.Admin/Properties/launchSettings.json +++ b/src/PearAdmin.AbpTemplate.Admin/Properties/launchSettings.json @@ -1,4 +1,4 @@ -{ +{ "iisSettings": { "windowsAuthentication": false, "anonymousAuthentication": true, diff --git a/src/PearAdmin.AbpTemplate.Admin/Startup.cs b/src/PearAdmin.AbpTemplate.Admin/Startup.cs index 3aad1c41a04dff6d505a1c2bc2f5bddc33e6e6c4..f1c584d49b80c85ef006bb0bbbdc189b55e958ad 100644 --- a/src/PearAdmin.AbpTemplate.Admin/Startup.cs +++ b/src/PearAdmin.AbpTemplate.Admin/Startup.cs @@ -8,7 +8,6 @@ using Abp.Json; using Hangfire; using Hangfire.MemoryStorage; using LogDashboard; -using LogDashboard.Authorization.Filters; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Identity; @@ -16,7 +15,6 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; using Newtonsoft.Json.Serialization; using PearAdmin.AbpTemplate.Admin.Configuration; using PearAdmin.AbpTemplate.Admin.Extensions; diff --git a/src/PearAdmin.AbpTemplate.Admin/appsettings.json b/src/PearAdmin.AbpTemplate.Admin/appsettings.json index e38770b0b10e47e4cc7ec1751b2ff888cb77f1e3..5c4a85fff9c2d884b6887deb6fe4a2d2dc331a27 100644 --- a/src/PearAdmin.AbpTemplate.Admin/appsettings.json +++ b/src/PearAdmin.AbpTemplate.Admin/appsettings.json @@ -62,5 +62,12 @@ "final": true } ] + }, + "Authentication": { + "WeChatMiniProgram": { + "IsEnabled": "false", + "AppId": "", + "AppSecret": "" + } } }