当前手机验证基本是标配,但Abp自身并没有实现这个功能,于是有了通过自定义模块实现的想法。
经过研究,发现要实现这个,只要重写和替换包含ReplaceEmailToUsernameOfInputIfNeeds
方法的类就可以了。但要实现这个,首先要在IdentityUserManager
类中添加FindByPhoneAsync
方法用来通过手机号码查询用户。刚开始想通过扩展方法的方式来实现,但发现唯一能用来获取用户集合的公共属性Users
并不能用,而又没有其他办法获取存储,于是放弃该方法。现在只能通过自定义IdentityUserManager
来实现了。于是在创建了Generic.Abp.PhoneLogin.Domain
模块定义了PhoneLoginUserManager
对象。
在Abp
的源代码中搜索ReplaceEmailToUsernameOfInputIfNeeds
,会发现有以下5个类包含该方法:
Volo.Abp.Account.Web
模块的AccountController
和LoginModel
Volo.Abp.Account.Web.IdentityServer
模块的IdentityServerSupportedLoginModel
Volo.Abp.IdentityServer.Domain
模块的AbpResourceOwnerPasswordValidator
Volo.Abp.OpenIddict.AspNetCore
模块的TokenController
Volo.Abp.Account.Web
模块的AccountController
和LoginModel
由于Volo.Abp.Account.Web.IdentityServer
模块的IdentityServerSupportedLoginModel
调用的是Volo.Abp.Account.Web
模块的``LoginModel的
ReplaceEmailToUsernameOfInputIfNeeds`方法,因而可以忽悠这个。
对应各个要重写的模块,建立对应的模块就行了。
对于AccountController
,通过替换控制器的方式就可实现替换了,具体代码如下:
[IgnoreAntiforgeryToken][RemoteService(Name = AccountRemoteServiceConsts.RemoteServiceName)][Controller][ControllerName("Login")][Area("account")][Route("api/account")][Dependency(ReplaceServices = true)][ExposeServices(typeof(AccountController), IncludeSelf = true)]public class PhoneLoginAccountController : AccountController{public PhoneLoginAccountController(SignInManager<IdentityUser> signInManager,PhoneLoginUserManager userManager,ISettingProvider settingProvider,IdentitySecurityLogManager identitySecurityLogManager,IOptions<IdentityOptions> identityOptions) :base(signInManager, userManager, settingProvider, identitySecurityLogManager, identityOptions){LocalizationResource = typeof(AccountResource);PhoneLoginUserManager = userManager;}protected PhoneLoginUserManager PhoneLoginUserManager { get; }protected override async Task ReplaceEmailToUsernameOfInputIfNeeds(UserLoginInfo login){var userByUsername = await UserManager.FindByNameAsync(login.UserNameOrEmailAddress);if (userByUsername != null){return;}var userByPhone = await PhoneLoginUserManager.FindByPhoneAsync(login.UserNameOrEmailAddress);if (userByPhone != null){login.UserNameOrEmailAddress = userByPhone.UserName;return;}if (!ValidationHelper.IsValidEmailAddress(login.UserNameOrEmailAddress)){return;}var userByEmail = await UserManager.FindByEmailAsync(login.UserNameOrEmailAddress);if (userByEmail != null){login.UserNameOrEmailAddress = userByEmail.UserName;return;}return;}}
在ReplaceEmailToUsernameOfInputIfNeeds
方法内,主要是通过FindByPhoneAsync
方法查找用户,如果找到用户,就用找到的用户名替换登录用户名就行了。
在这里要注意的是使用PhoneLoginUserManager
替换原来的IdentityUserManager
。
对于LoginModel
,可以使用只替换LoginModel
的方式,这里图方便直接使用了覆盖Login.cshtml
的方式。代码就不贴了,大家可以去Github
查看源代码。
Volo.Abp.IdentityServer.Domain
模块的AbpResourceOwnerPasswordValidator
IdentityServer4
是通过IResourceOwnerPasswordValidator
接口来实现密码验证的,因而要替换IResourceOwnerPasswordValidator
需要点技巧,笔者也是找了一圈才找到的。
具体的ReplaceEmailToUsernameOfInputIfNeeds
方法就不贴了,基本上和AccountController
没什么不同。
最难部分是在模块定义中替换原有的AbpResourceOwnerPasswordValidator
:
public class GenericAbpPhoneLoginIdentityServerDomainModule : AbpModule{public override void PreConfigureServices(ServiceConfigurationContext context){PreConfigure<IIdentityServerBuilder>(builder =>{builder.AddResourceOwnerValidator<PhoneLoginResourceOwnerPasswordValidator>();});context.Services.Replace(ServiceDescriptor.Transient<AbpResourceOwnerPasswordValidator, PhoneLoginResourceOwnerPasswordValidator>());}}
代码需要先把PhoneLoginResourceOwnerPasswordValidator
添加到验证器,然后再替换。
Volo.Abp.OpenIddict.AspNetCore
模块的TokenController
这个和替换AccountController
没什么不同,就不具体说了。
使用方法
OpenIddict
- 在应用的
Domain.Shared
模块引用Generic.Abp.PhoneLogin.Domain.Shared
- 在应用的
Domain
模块引用Generic.Abp.PhoneLogin.Domain
- 在应用的
Web
模块引用Generic.Abp.PhoneLogin.Account.Web
和Generic.Abp.PhoneLogin.OpenIddict.AspNetCore
IdenttityServer
- 在应用的
Domain.Shared
模块引用Generic.Abp.PhoneLogin.Domain.Shared
- 在应用的
Domain
模块引用Generic.Abp.PhoneLogin.Domain
和Generic.Abp.PhoneLogin.IdentityServer.Domain
- 在应用的
Web
模块引用Generic.Abp.PhoneLogin.Account.Web
具体示例可查看分支测试identtiyServer4手机登录
源代码:https://github.com/tianxiaode/GenericAbp