今天,准备在ABP vNext上拓展一下短信登录,流程很简单:用户请求发送验证码接口,后台生成验证码,然后发送到用户手机,用户收到验证码,使用拓展授权方式SMSGrantType获取令牌,完成授权。
首先,关于验证码生成,为了避免生成存储到本地再校验一般采用TOTP加密算法,它是基于时间的一次性密码生成算法,由 RFC 6238 定义。和基于事件的一次性密码生成算法不同 HOTP,TOTP 是基于时间的,在有效时间过后,针对同一用户所生成的验证码将会发生改变。
AspNetCore已经在其TOTP- (2FA) 双因素身份验证 里借助Rfc6238AuthenticationService实现了加密,我去翻了一遍源码,大概调用流程如下:
然后,我们可以借助这个加密算法来进行短信验证码生成,通过拓展IDS4的grant_type来做一个SMSGrantType的授权方式。
第一步,生成验证码
1 public class SmsService : ApplicationService, ISmsService 2 { 3 private readonly UserManager<Volo.Abp.Identity.IdentityUser> UserManager; 4 public SmsService( 5 IdentityUserManager identityUserManager, 6 UserManager<Volo.Abp.Identity.IdentityUser> userManager) 7 { 8 UserManager = userManager; 9 } 10 } 11 12 // 生成验证码 13 protected async Task<string> GetVerificationCodeAsync( 14 Volo.Abp.Identity.IdentityUser user, string selectedProvider = "Phone") 15 { 16 var code = await UserManager.GenerateTwoFactorTokenAsync(user, selectedProvider); 17 18 Check.NotNull(code, nameof(code)); 19 20 return code; 21 } 22 }
第二步,HttpApi.Host 层拓展自定义授权模式
1 public class ExtensionGrantTypes 2 { 3 public const string SMSGrantType = "SMSGrantType"; 4 } 5 6 public class SMSGrantValidator : IExtensionGrantValidator 7 { 8 public string GrantType => ExtensionGrantTypes.SMSGrantType; 9 private readonly UserManager<Volo.Abp.Identity.IdentityUser> UserManager; 10 private readonly IdentityUserManager IdentityUserManager; 11 12 public SMSGrantValidator( 13 UserManager<Volo.Abp.Identity.IdentityUser> userManager, 14 IdentityUserManager identityUserManager) 15 { 16 UserManager = userManager; 17 IdentityUserManager = identityUserManager; 18 } 19 20 public Task ValidateAsync(ExtensionGrantValidationContext context) 21 { 22 var smsCode = context.Request.Raw.Get("smsCode"); 23 var phoneNumber = context.Request.Raw.Get("phoneNumber"); 24 25 if (string.IsNullOrEmpty(smsCode) || string.IsNullOrEmpty(phoneNumber)) 26 { 27 context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant); 28 } 29 // 在此实现自己的验证逻辑… 30 return Task.FromResult(0); 31 } 32 }
第三步,XXXHttpApiHostModule注册校验类
1 // 在httpapi.host的module文件内添加自定义授权模式 2 public override void PreConfigureServices(ServiceConfigurationContext context) 3 { 4 context.Services.PreConfigure<IIdentityServerBuilder>( 5 builder => 6 { 7 // 添加自定义授权模式 8 builder.AddExtensionGrantValidator<SMSGrantValidator>(); 9 } 10 ); 11 }
第四步,Domain添加Client数据种子(同时DbMigrator的appsettings添加配置)
1 var testClientId = configurationSection["Test_App:ClientId"]; 2 if (!testClientId.IsNullOrWhiteSpace()) 3 { 4 await CreateClientAsync( 5 name: testClientId, 6 scopes: commonScopes, 7 grantTypes: new[] { "password", "client_credentials", ExtensionGrantTypes.SMSGrantType }, 8 secret: (configurationSection["Test_App:ClientSecret"] ?? "123456").Sha256() 9 ); 10 }
第五步,愉快的使用(测试号码是假的不要来骚扰我......[狗头].jpg)