diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN/Abp/Account/AccountAppService.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN/Abp/Account/AccountAppService.cs index dd83d33c1..ff4fa9f5f 100644 --- a/aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN/Abp/Account/AccountAppService.cs +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN/Abp/Account/AccountAppService.cs @@ -1,319 +1,319 @@ -using LINGYUN.Abp.Identity; -using LINGYUN.Abp.Identity.Security; -using LINGYUN.Abp.Identity.Settings; -using LINGYUN.Abp.WeChat; -using LINGYUN.Abp.WeChat.MiniProgram; -using LINGYUN.Abp.WeChat.OpenId; -using Microsoft.AspNetCore.Identity; -using Microsoft.Extensions.Caching.Distributed; -using Microsoft.Extensions.Options; -using System; -using System.ComponentModel.DataAnnotations; -using System.Text; -using System.Threading.Tasks; -using Volo.Abp; -using Volo.Abp.Account.Localization; -using Volo.Abp.Application.Services; -using Volo.Abp.Caching; -using Volo.Abp.Identity; -using Volo.Abp.Settings; -using Volo.Abp.Validation; -using IIdentityUserRepository = LINGYUN.Abp.Identity.IIdentityUserRepository; - -namespace LINGYUN.Abp.Account -{ - public class AccountAppService : ApplicationService, IAccountAppService - { - protected ITotpService TotpService { get; } - protected IdentityUserStore UserStore { get; } - protected IdentityUserManager UserManager { get; } - protected IIdentityUserRepository UserRepository { get; } - protected IUserSecurityCodeSender SecurityCodeSender { get; } - protected IWeChatOpenIdFinder WeChatOpenIdFinder { get; } - protected IOptions IdentityOptions { get; } - protected AbpWeChatMiniProgramOptionsFactory MiniProgramOptionsFactory { get; } - protected IDistributedCache SecurityTokenCache { get; } - - public AccountAppService( - ITotpService totpService, - IdentityUserStore userStore, - IdentityUserManager userManager, - IWeChatOpenIdFinder weChatOpenIdFinder, - IIdentityUserRepository userRepository, - IUserSecurityCodeSender securityCodeSender, - IDistributedCache securityTokenCache, - AbpWeChatMiniProgramOptionsFactory miniProgramOptionsFactory, - IOptions identityOptions) - { - TotpService = totpService; - UserStore = userStore; - UserManager = userManager; - UserRepository = userRepository; - WeChatOpenIdFinder = weChatOpenIdFinder; - SecurityCodeSender = securityCodeSender; - SecurityTokenCache = securityTokenCache; - MiniProgramOptionsFactory = miniProgramOptionsFactory; - IdentityOptions = identityOptions; - - LocalizationResource = typeof(AccountResource); - } - - public virtual async Task RegisterAsync(WeChatRegisterDto input) - { - ThowIfInvalidEmailAddress(input.EmailAddress); - - await CheckSelfRegistrationAsync(); - await IdentityOptions.SetAsync(); - - var options = await MiniProgramOptionsFactory.CreateAsync(); - - var wehchatOpenId = await WeChatOpenIdFinder.FindAsync(input.Code, options.AppId, options.AppSecret); - - var user = await UserManager.FindByLoginAsync(AbpWeChatMiniProgramConsts.ProviderKey, wehchatOpenId.OpenId); - if (user != null) - { - // 应该要抛出微信号已注册异常,而不是直接返回注册用户数据,否则造成用户信息泄露 - throw new UserFriendlyException(L["DuplicateWeChat"]); - } - var userName = input.UserName; - if (userName.IsNullOrWhiteSpace()) - { - userName = "wxid-" + wehchatOpenId.OpenId.ToMd5().ToLower(); - } - - var userEmail = input.EmailAddress;//如果邮件地址不验证,随意写入一个 - if (userEmail.IsNullOrWhiteSpace()) - { - userEmail = $"{userName}@{CurrentTenant.Name ?? "default"}.io"; - } - - user = new IdentityUser(GuidGenerator.Create(), userName, userEmail, CurrentTenant.Id); - (await UserManager.CreateAsync(user, input.Password)).CheckErrors(); - - (await UserManager.AddDefaultRolesAsync(user)).CheckErrors(); - - var userLogin = new UserLoginInfo(AbpWeChatMiniProgramConsts.ProviderKey, wehchatOpenId.OpenId, AbpWeChatGlobalConsts.DisplayName); - (await UserManager.AddLoginAsync(user, userLogin)).CheckErrors(); - - await CurrentUnitOfWork.SaveChangesAsync(); - } - - public virtual async Task SendPhoneRegisterCodeAsync(SendPhoneRegisterCodeDto input) - { - await CheckSelfRegistrationAsync(); - await CheckNewUserPhoneNumberNotBeUsedAsync(input.PhoneNumber); - - var securityTokenCacheKey = SmsSecurityTokenCacheItem.CalculateCacheKey(input.PhoneNumber, "SmsVerifyCode"); - var securityTokenCacheItem = await SecurityTokenCache.GetAsync(securityTokenCacheKey); - var interval = await SettingProvider.GetAsync(IdentitySettingNames.User.SmsRepetInterval, 1); - - if (securityTokenCacheItem != null) - { - throw new UserFriendlyException(L["SendRepeatSmsVerifyCode", interval]); - } - - var template = await SettingProvider.GetOrNullAsync(IdentitySettingNames.User.SmsNewUserRegister); - - // 安全令牌 - var securityToken = GuidGenerator.Create().ToString("N"); - - var code = TotpService.GenerateCode(Encoding.Unicode.GetBytes(securityToken), securityTokenCacheKey); - securityTokenCacheItem = new SmsSecurityTokenCacheItem(code.ToString(), securityToken); - - await SecurityCodeSender.SendPhoneConfirmedCodeAsync( - input.PhoneNumber, securityTokenCacheItem.Token, template); - - await SecurityTokenCache - .SetAsync(securityTokenCacheKey, securityTokenCacheItem, - new DistributedCacheEntryOptions - { - AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(interval) - }); - } - - public virtual async Task RegisterAsync(PhoneRegisterDto input) - { - await CheckSelfRegistrationAsync(); - await IdentityOptions.SetAsync(); - await CheckNewUserPhoneNumberNotBeUsedAsync(input.PhoneNumber); - - var securityTokenCacheKey = SmsSecurityTokenCacheItem.CalculateCacheKey(input.PhoneNumber, "SmsVerifyCode"); - var securityTokenCacheItem = await SecurityTokenCache.GetAsync(securityTokenCacheKey); - if (securityTokenCacheItem == null) - { - // 验证码过期 - throw new UserFriendlyException(L["InvalidSmsVerifyCode"]); - } - - // 验证码是否有效 - if (input.Code.Equals(securityTokenCacheItem.Token) && int.TryParse(input.Code, out int token)) - { - var securityToken = Encoding.Unicode.GetBytes(securityTokenCacheItem.SecurityToken); - // 校验totp验证码 - if (TotpService.ValidateCode(securityToken, token, securityTokenCacheKey)) - { - var userEmail = input.EmailAddress ?? $"{input.PhoneNumber}@{CurrentTenant.Name ?? "default"}.io";//如果邮件地址不验证,随意写入一个 - var userName = input.UserName ?? input.PhoneNumber; - var user = new IdentityUser(GuidGenerator.Create(), userName, userEmail, CurrentTenant.Id) - { - Name = input.Name ?? input.PhoneNumber - }; - - await UserStore.SetPhoneNumberAsync(user, input.PhoneNumber); - await UserStore.SetPhoneNumberConfirmedAsync(user, true); - - (await UserManager.CreateAsync(user, input.Password)).CheckErrors(); - - (await UserManager.AddDefaultRolesAsync(user)).CheckErrors(); - - await SecurityTokenCache.RemoveAsync(securityTokenCacheKey); - - await CurrentUnitOfWork.SaveChangesAsync(); - - return; - } - } - // 验证码无效 - throw new UserFriendlyException(L["InvalidSmsVerifyCode"]); - } - - public virtual async Task SendPhoneResetPasswordCodeAsync(SendPhoneResetPasswordCodeDto input) - { - /* - * 注解: 微软的重置密码方法通过 UserManager.GeneratePasswordResetTokenAsync 接口生成密码重置Token - * 而这个Token设计的意义就是用户通过链接来重置密码,所以不适合短信验证 - * 某些企业是把链接生成一个短链发送短信的,不过这种方式不是很推荐,因为现在是真没几个人敢随便点短信链接的 - * - * 此处设计方式为: - * - * step1: 例行检查是否重复发送,这一点是很有必要的 - * step2: 通过已确认的手机号来查询用户,如果用户未确认手机号,那就不能发送,这一点也是很有必要的 - * step3(重点): 通过 UserManager.GenerateTwoFactorTokenAsync 接口来生成二次认证码,这就相当于伪验证码,只是用于确认用户传递的验证码是否通过 - * 比起自己生成随机数,这个验证码利用了TOTP算法,有时间限制的 - * step4(重点): 用户传递验证码后,通过 UserManager.VerifyTwoFactorTokenAsync 接口来校验验证码 - * 验证通过后,再利用 UserManager.GeneratePasswordResetTokenAsync 接口来生成真正的用于重置密码的Token - */ - - var securityTokenCacheKey = SmsSecurityTokenCacheItem.CalculateCacheKey(input.PhoneNumber, "SmsVerifyCode"); - var securityTokenCacheItem = await SecurityTokenCache.GetAsync(securityTokenCacheKey); - var interval = await SettingProvider.GetAsync(IdentitySettingNames.User.SmsRepetInterval, 1); - // 传递 isConfirmed 用户必须是已确认过手机号的 - var user = await GetUserByPhoneNumberAsync(input.PhoneNumber, isConfirmed: true); - // 能查询到缓存就是重复发送 - if (securityTokenCacheItem != null) - { - throw new UserFriendlyException(L["SendRepeatSmsVerifyCode", interval]); - } - - var template = await SettingProvider.GetOrNullAsync(IdentitySettingNames.User.SmsResetPassword); - // 生成二次认证码 - var code = await UserManager.GenerateTwoFactorTokenAsync(user, TokenOptions.DefaultPhoneProvider); - // 发送短信验证码 - await SecurityCodeSender.SendPhoneConfirmedCodeAsync(input.PhoneNumber, code, template); - // 缓存这个手机号的记录,防重复 - securityTokenCacheItem = new SmsSecurityTokenCacheItem(code, user.SecurityStamp); - await SecurityTokenCache - .SetAsync(securityTokenCacheKey, securityTokenCacheItem, - new DistributedCacheEntryOptions - { - AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(interval) - }); - } - - public virtual async Task ResetPasswordAsync(PhoneResetPasswordDto input) - { - var securityTokenCacheKey = SmsSecurityTokenCacheItem.CalculateCacheKey(input.PhoneNumber, "SmsVerifyCode"); - var securityTokenCacheItem = await SecurityTokenCache.GetAsync(securityTokenCacheKey); - if (securityTokenCacheItem == null) - { - throw new UserFriendlyException(L["InvalidSmsVerifyCode"]); - } - await IdentityOptions.SetAsync(); - // 传递 isConfirmed 用户必须是已确认过手机号的 - var user = await GetUserByPhoneNumberAsync(input.PhoneNumber, isConfirmed: true); - // 验证二次认证码 - if (!await UserManager.VerifyTwoFactorTokenAsync(user, TokenOptions.DefaultPhoneProvider, input.Code)) - { - // 验证码无效 - throw new UserFriendlyException(L["InvalidSmsVerifyCode"]); - } - // 生成真正的重置密码Token - var resetPwdToken = await UserManager.GeneratePasswordResetTokenAsync(user); - // 重置密码 - (await UserManager.ResetPasswordAsync(user, resetPwdToken, input.NewPassword)).CheckErrors(); - // 移除缓存项 - await SecurityTokenCache.RemoveAsync(securityTokenCacheKey); - - await CurrentUnitOfWork.SaveChangesAsync(); - } - - public virtual async Task SendPhoneSigninCodeAsync(SendPhoneSigninCodeDto input) - { - var securityTokenCacheKey = SmsSecurityTokenCacheItem.CalculateCacheKey(input.PhoneNumber, "SmsVerifyCode"); - var securityTokenCacheItem = await SecurityTokenCache.GetAsync(securityTokenCacheKey); - var interval = await SettingProvider.GetAsync(IdentitySettingNames.User.SmsRepetInterval, 1); - if (securityTokenCacheItem != null) - { - throw new UserFriendlyException(L["SendRepeatSmsVerifyCode", interval]); - } - // 传递 isConfirmed 验证过的用户才允许通过手机登录 - var user = await GetUserByPhoneNumberAsync(input.PhoneNumber, isConfirmed: true); - var code = await UserManager.GenerateTwoFactorTokenAsync(user, TokenOptions.DefaultPhoneProvider); - var template = await SettingProvider.GetOrNullAsync(IdentitySettingNames.User.SmsUserSignin); - - // 发送登录验证码短信 - await SecurityCodeSender.SendPhoneConfirmedCodeAsync(input.PhoneNumber, code, template); - // 缓存登录验证码状态,防止同一手机号重复发送 - securityTokenCacheItem = new SmsSecurityTokenCacheItem(code, user.SecurityStamp); - await SecurityTokenCache - .SetAsync(securityTokenCacheKey, securityTokenCacheItem, - new DistributedCacheEntryOptions - { - AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(interval) - }); - } - - protected virtual async Task GetUserByPhoneNumberAsync(string phoneNumber, bool isConfirmed = true) - { - var user = await UserRepository.FindByPhoneNumberAsync(phoneNumber, isConfirmed, true); - if (user == null) - { - throw new UserFriendlyException(L["PhoneNumberNotRegisterd"]); - } - return user; - } - - /// - /// 检查是否允许用户注册 - /// - /// - protected virtual async Task CheckSelfRegistrationAsync() - { - if (!await SettingProvider.IsTrueAsync(Volo.Abp.Account.Settings.AccountSettingNames.IsSelfRegistrationEnabled)) - { - throw new UserFriendlyException(L["SelfRegistrationDisabledMessage"]); - } - } - - protected virtual async Task CheckNewUserPhoneNumberNotBeUsedAsync(string phoneNumber) - { - if (await UserRepository.IsPhoneNumberUedAsync(phoneNumber)) - { - throw new UserFriendlyException(L["DuplicatePhoneNumber"]); - } - } - - private void ThowIfInvalidEmailAddress(string inputEmail) - { - if (!inputEmail.IsNullOrWhiteSpace() && - !ValidationHelper.IsValidEmailAddress(inputEmail)) - { - throw new AbpValidationException( - new ValidationResult[] - { - new ValidationResult(L["The {0} field is not a valid e-mail address.", L["DisplayName:EmailAddress"]], new string[]{ "EmailAddress" }) - }); - } - } - } -} +using LINGYUN.Abp.Identity; +using LINGYUN.Abp.Identity.Security; +using LINGYUN.Abp.Identity.Settings; +using LINGYUN.Abp.WeChat; +using LINGYUN.Abp.WeChat.MiniProgram; +using LINGYUN.Abp.WeChat.OpenId; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Options; +using System; +using System.ComponentModel.DataAnnotations; +using System.Text; +using System.Threading.Tasks; +using Volo.Abp; +using Volo.Abp.Account.Localization; +using Volo.Abp.Application.Services; +using Volo.Abp.Caching; +using Volo.Abp.Identity; +using Volo.Abp.Settings; +using Volo.Abp.Validation; +using IIdentityUserRepository = LINGYUN.Abp.Identity.IIdentityUserRepository; + +namespace LINGYUN.Abp.Account +{ + public class AccountAppService : ApplicationService, IAccountAppService + { + protected ITotpService TotpService { get; } + protected IdentityUserStore UserStore { get; } + protected IdentityUserManager UserManager { get; } + protected IIdentityUserRepository UserRepository { get; } + protected IUserSecurityCodeSender SecurityCodeSender { get; } + protected IWeChatOpenIdFinder WeChatOpenIdFinder { get; } + protected IOptions IdentityOptions { get; } + protected AbpWeChatMiniProgramOptionsFactory MiniProgramOptionsFactory { get; } + protected IDistributedCache SecurityTokenCache { get; } + + public AccountAppService( + ITotpService totpService, + IdentityUserStore userStore, + IdentityUserManager userManager, + IWeChatOpenIdFinder weChatOpenIdFinder, + IIdentityUserRepository userRepository, + IUserSecurityCodeSender securityCodeSender, + IDistributedCache securityTokenCache, + AbpWeChatMiniProgramOptionsFactory miniProgramOptionsFactory, + IOptions identityOptions) + { + TotpService = totpService; + UserStore = userStore; + UserManager = userManager; + UserRepository = userRepository; + WeChatOpenIdFinder = weChatOpenIdFinder; + SecurityCodeSender = securityCodeSender; + SecurityTokenCache = securityTokenCache; + MiniProgramOptionsFactory = miniProgramOptionsFactory; + IdentityOptions = identityOptions; + + LocalizationResource = typeof(AccountResource); + } + + public virtual async Task RegisterAsync(WeChatRegisterDto input) + { + ThowIfInvalidEmailAddress(input.EmailAddress); + + await CheckSelfRegistrationAsync(); + await IdentityOptions.SetAsync(); + + var options = await MiniProgramOptionsFactory.CreateAsync(); + + var wehchatOpenId = await WeChatOpenIdFinder.FindAsync(input.Code, options.AppId, options.AppSecret); + + var user = await UserManager.FindByLoginAsync(AbpWeChatMiniProgramConsts.ProviderName, wehchatOpenId.OpenId); + if (user != null) + { + // 应该要抛出微信号已注册异常,而不是直接返回注册用户数据,否则造成用户信息泄露 + throw new UserFriendlyException(L["DuplicateWeChat"]); + } + var userName = input.UserName; + if (userName.IsNullOrWhiteSpace()) + { + userName = "wxid-" + wehchatOpenId.OpenId.ToMd5().ToLower(); + } + + var userEmail = input.EmailAddress;//如果邮件地址不验证,随意写入一个 + if (userEmail.IsNullOrWhiteSpace()) + { + userEmail = $"{userName}@{CurrentTenant.Name ?? "default"}.io"; + } + + user = new IdentityUser(GuidGenerator.Create(), userName, userEmail, CurrentTenant.Id); + (await UserManager.CreateAsync(user, input.Password)).CheckErrors(); + + (await UserManager.AddDefaultRolesAsync(user)).CheckErrors(); + + var userLogin = new UserLoginInfo(AbpWeChatMiniProgramConsts.ProviderName, wehchatOpenId.OpenId, AbpWeChatGlobalConsts.DisplayName); + (await UserManager.AddLoginAsync(user, userLogin)).CheckErrors(); + + await CurrentUnitOfWork.SaveChangesAsync(); + } + + public virtual async Task SendPhoneRegisterCodeAsync(SendPhoneRegisterCodeDto input) + { + await CheckSelfRegistrationAsync(); + await CheckNewUserPhoneNumberNotBeUsedAsync(input.PhoneNumber); + + var securityTokenCacheKey = SmsSecurityTokenCacheItem.CalculateCacheKey(input.PhoneNumber, "SmsVerifyCode"); + var securityTokenCacheItem = await SecurityTokenCache.GetAsync(securityTokenCacheKey); + var interval = await SettingProvider.GetAsync(IdentitySettingNames.User.SmsRepetInterval, 1); + + if (securityTokenCacheItem != null) + { + throw new UserFriendlyException(L["SendRepeatSmsVerifyCode", interval]); + } + + var template = await SettingProvider.GetOrNullAsync(IdentitySettingNames.User.SmsNewUserRegister); + + // 安全令牌 + var securityToken = GuidGenerator.Create().ToString("N"); + + var code = TotpService.GenerateCode(Encoding.Unicode.GetBytes(securityToken), securityTokenCacheKey); + securityTokenCacheItem = new SmsSecurityTokenCacheItem(code.ToString(), securityToken); + + await SecurityCodeSender.SendPhoneConfirmedCodeAsync( + input.PhoneNumber, securityTokenCacheItem.Token, template); + + await SecurityTokenCache + .SetAsync(securityTokenCacheKey, securityTokenCacheItem, + new DistributedCacheEntryOptions + { + AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(interval) + }); + } + + public virtual async Task RegisterAsync(PhoneRegisterDto input) + { + await CheckSelfRegistrationAsync(); + await IdentityOptions.SetAsync(); + await CheckNewUserPhoneNumberNotBeUsedAsync(input.PhoneNumber); + + var securityTokenCacheKey = SmsSecurityTokenCacheItem.CalculateCacheKey(input.PhoneNumber, "SmsVerifyCode"); + var securityTokenCacheItem = await SecurityTokenCache.GetAsync(securityTokenCacheKey); + if (securityTokenCacheItem == null) + { + // 验证码过期 + throw new UserFriendlyException(L["InvalidSmsVerifyCode"]); + } + + // 验证码是否有效 + if (input.Code.Equals(securityTokenCacheItem.Token) && int.TryParse(input.Code, out int token)) + { + var securityToken = Encoding.Unicode.GetBytes(securityTokenCacheItem.SecurityToken); + // 校验totp验证码 + if (TotpService.ValidateCode(securityToken, token, securityTokenCacheKey)) + { + var userEmail = input.EmailAddress ?? $"{input.PhoneNumber}@{CurrentTenant.Name ?? "default"}.io";//如果邮件地址不验证,随意写入一个 + var userName = input.UserName ?? input.PhoneNumber; + var user = new IdentityUser(GuidGenerator.Create(), userName, userEmail, CurrentTenant.Id) + { + Name = input.Name ?? input.PhoneNumber + }; + + await UserStore.SetPhoneNumberAsync(user, input.PhoneNumber); + await UserStore.SetPhoneNumberConfirmedAsync(user, true); + + (await UserManager.CreateAsync(user, input.Password)).CheckErrors(); + + (await UserManager.AddDefaultRolesAsync(user)).CheckErrors(); + + await SecurityTokenCache.RemoveAsync(securityTokenCacheKey); + + await CurrentUnitOfWork.SaveChangesAsync(); + + return; + } + } + // 验证码无效 + throw new UserFriendlyException(L["InvalidSmsVerifyCode"]); + } + + public virtual async Task SendPhoneResetPasswordCodeAsync(SendPhoneResetPasswordCodeDto input) + { + /* + * 注解: 微软的重置密码方法通过 UserManager.GeneratePasswordResetTokenAsync 接口生成密码重置Token + * 而这个Token设计的意义就是用户通过链接来重置密码,所以不适合短信验证 + * 某些企业是把链接生成一个短链发送短信的,不过这种方式不是很推荐,因为现在是真没几个人敢随便点短信链接的 + * + * 此处设计方式为: + * + * step1: 例行检查是否重复发送,这一点是很有必要的 + * step2: 通过已确认的手机号来查询用户,如果用户未确认手机号,那就不能发送,这一点也是很有必要的 + * step3(重点): 通过 UserManager.GenerateTwoFactorTokenAsync 接口来生成二次认证码,这就相当于伪验证码,只是用于确认用户传递的验证码是否通过 + * 比起自己生成随机数,这个验证码利用了TOTP算法,有时间限制的 + * step4(重点): 用户传递验证码后,通过 UserManager.VerifyTwoFactorTokenAsync 接口来校验验证码 + * 验证通过后,再利用 UserManager.GeneratePasswordResetTokenAsync 接口来生成真正的用于重置密码的Token + */ + + var securityTokenCacheKey = SmsSecurityTokenCacheItem.CalculateCacheKey(input.PhoneNumber, "SmsVerifyCode"); + var securityTokenCacheItem = await SecurityTokenCache.GetAsync(securityTokenCacheKey); + var interval = await SettingProvider.GetAsync(IdentitySettingNames.User.SmsRepetInterval, 1); + // 传递 isConfirmed 用户必须是已确认过手机号的 + var user = await GetUserByPhoneNumberAsync(input.PhoneNumber, isConfirmed: true); + // 能查询到缓存就是重复发送 + if (securityTokenCacheItem != null) + { + throw new UserFriendlyException(L["SendRepeatSmsVerifyCode", interval]); + } + + var template = await SettingProvider.GetOrNullAsync(IdentitySettingNames.User.SmsResetPassword); + // 生成二次认证码 + var code = await UserManager.GenerateTwoFactorTokenAsync(user, TokenOptions.DefaultPhoneProvider); + // 发送短信验证码 + await SecurityCodeSender.SendPhoneConfirmedCodeAsync(input.PhoneNumber, code, template); + // 缓存这个手机号的记录,防重复 + securityTokenCacheItem = new SmsSecurityTokenCacheItem(code, user.SecurityStamp); + await SecurityTokenCache + .SetAsync(securityTokenCacheKey, securityTokenCacheItem, + new DistributedCacheEntryOptions + { + AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(interval) + }); + } + + public virtual async Task ResetPasswordAsync(PhoneResetPasswordDto input) + { + var securityTokenCacheKey = SmsSecurityTokenCacheItem.CalculateCacheKey(input.PhoneNumber, "SmsVerifyCode"); + var securityTokenCacheItem = await SecurityTokenCache.GetAsync(securityTokenCacheKey); + if (securityTokenCacheItem == null) + { + throw new UserFriendlyException(L["InvalidSmsVerifyCode"]); + } + await IdentityOptions.SetAsync(); + // 传递 isConfirmed 用户必须是已确认过手机号的 + var user = await GetUserByPhoneNumberAsync(input.PhoneNumber, isConfirmed: true); + // 验证二次认证码 + if (!await UserManager.VerifyTwoFactorTokenAsync(user, TokenOptions.DefaultPhoneProvider, input.Code)) + { + // 验证码无效 + throw new UserFriendlyException(L["InvalidSmsVerifyCode"]); + } + // 生成真正的重置密码Token + var resetPwdToken = await UserManager.GeneratePasswordResetTokenAsync(user); + // 重置密码 + (await UserManager.ResetPasswordAsync(user, resetPwdToken, input.NewPassword)).CheckErrors(); + // 移除缓存项 + await SecurityTokenCache.RemoveAsync(securityTokenCacheKey); + + await CurrentUnitOfWork.SaveChangesAsync(); + } + + public virtual async Task SendPhoneSigninCodeAsync(SendPhoneSigninCodeDto input) + { + var securityTokenCacheKey = SmsSecurityTokenCacheItem.CalculateCacheKey(input.PhoneNumber, "SmsVerifyCode"); + var securityTokenCacheItem = await SecurityTokenCache.GetAsync(securityTokenCacheKey); + var interval = await SettingProvider.GetAsync(IdentitySettingNames.User.SmsRepetInterval, 1); + if (securityTokenCacheItem != null) + { + throw new UserFriendlyException(L["SendRepeatSmsVerifyCode", interval]); + } + // 传递 isConfirmed 验证过的用户才允许通过手机登录 + var user = await GetUserByPhoneNumberAsync(input.PhoneNumber, isConfirmed: true); + var code = await UserManager.GenerateTwoFactorTokenAsync(user, TokenOptions.DefaultPhoneProvider); + var template = await SettingProvider.GetOrNullAsync(IdentitySettingNames.User.SmsUserSignin); + + // 发送登录验证码短信 + await SecurityCodeSender.SendPhoneConfirmedCodeAsync(input.PhoneNumber, code, template); + // 缓存登录验证码状态,防止同一手机号重复发送 + securityTokenCacheItem = new SmsSecurityTokenCacheItem(code, user.SecurityStamp); + await SecurityTokenCache + .SetAsync(securityTokenCacheKey, securityTokenCacheItem, + new DistributedCacheEntryOptions + { + AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(interval) + }); + } + + protected virtual async Task GetUserByPhoneNumberAsync(string phoneNumber, bool isConfirmed = true) + { + var user = await UserRepository.FindByPhoneNumberAsync(phoneNumber, isConfirmed, true); + if (user == null) + { + throw new UserFriendlyException(L["PhoneNumberNotRegisterd"]); + } + return user; + } + + /// + /// 检查是否允许用户注册 + /// + /// + protected virtual async Task CheckSelfRegistrationAsync() + { + if (!await SettingProvider.IsTrueAsync(Volo.Abp.Account.Settings.AccountSettingNames.IsSelfRegistrationEnabled)) + { + throw new UserFriendlyException(L["SelfRegistrationDisabledMessage"]); + } + } + + protected virtual async Task CheckNewUserPhoneNumberNotBeUsedAsync(string phoneNumber) + { + if (await UserRepository.IsPhoneNumberUedAsync(phoneNumber)) + { + throw new UserFriendlyException(L["DuplicatePhoneNumber"]); + } + } + + private void ThowIfInvalidEmailAddress(string inputEmail) + { + if (!inputEmail.IsNullOrWhiteSpace() && + !ValidationHelper.IsValidEmailAddress(inputEmail)) + { + throw new AbpValidationException( + new ValidationResult[] + { + new ValidationResult(L["The {0} field is not a valid e-mail address.", L["DisplayName:EmailAddress"]], new string[]{ "EmailAddress" }) + }); + } + } + } +} diff --git a/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChat/LINGYUN/Abp/IdentityServer/WeChat/WeChatGrantValidator.cs b/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChat/LINGYUN/Abp/IdentityServer/WeChat/WeChatGrantValidator.cs index f24fcd0ab..234e25509 100644 --- a/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChat/LINGYUN/Abp/IdentityServer/WeChat/WeChatGrantValidator.cs +++ b/aspnet-core/modules/identityServer/LINGYUN.Abp.IdentityServer.WeChat/LINGYUN/Abp/IdentityServer/WeChat/WeChatGrantValidator.cs @@ -109,7 +109,7 @@ namespace LINGYUN.Abp.IdentityServer.WeChat new UserLoginInfo( LoginProvider, wechatOpenId.OpenId, - LoginProvider))).CheckErrors(); + AbpWeChatGlobalConsts.DisplayName))).CheckErrors(); } if (await UserManager.IsLockedOutAsync(currentUser))