当前手机验证基本是标配,但Abp自身并没有实现这个功能,于是有了通过自定义模块实现的想法。
经过研究,发现要实现这个,只要重写和替换包含ReplaceEmailToUsernameOfInputIfNeeds方法的类就可以了。但要实现这个,首先要在IdentityUserManager类中添加FindByPhoneAsync方法用来通过手机号码查询用户。刚开始想通过扩展方法的方式来实现,但发现唯一能用来获取用户集合的公共属性Users并不能用,而又没有其他办法获取存储,于是放弃该方法。现在只能通过自定义IdentityUserManager来实现了。于是在创建了Generic.Abp.PhoneLogin.Domain模块定义了PhoneLoginUserManager对象。
在Abp的源代码中搜索ReplaceEmailToUsernameOfInputIfNeeds,会发现有以下5个类包含该方法:
Volo.Abp.Account.Web模块的AccountController和LoginModelVolo.Abp.Account.Web.IdentityServer模块的IdentityServerSupportedLoginModelVolo.Abp.IdentityServer.Domain模块的AbpResourceOwnerPasswordValidatorVolo.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



















