当前手机验证基本是标配,但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