擴展.Net Core Identity Server 授權方式,實現 手機號+ 驗證碼 登錄
- 2022 年 5 月 7 日
- 筆記
- abp, dotNETCore, Identity Server
背景
中國來講,註冊/登錄流程都是儘可能的簡單,註冊流程複雜,容易流失客戶。手機號 + 簡訊驗證碼的方式非常普遍;但是框架默認並沒有類似的功能,需要我們自己進行擴展。
思路
- 驗證登錄手機號為註冊用戶,且驗證碼正確;驗證通過後,去 Identity Server 獲取Token,然後返回客戶端。
- 擴展 Identity 的授權方式,類似於 Authorization code;關於gtant type 可以參考 Grant Types — IdentityServer4 1.0.0 documentation (identityserver4test.readthedocs.io)
不過擴由於Identity Server 要收費,以及Abp 6.0 要集成 OpenIdDict;擴展 Grant Type 的方式,可以適用於當前,後續根據需要進行調整。
定義 GrantTypes
1 public class IdentityGrantTypes 2 { 3 public const string PhoneCode = "phone_code"; 4 } 5
View Code
實現 IExtensionGrantValidator
主要實現對手機號以及簡訊驗證碼的校驗
1 public class PhoneCodeGrantValidator : IExtensionGrantValidator, ITransientDependency 2 { 3 private readonly IOptions<IdentityOptions> _identityOptions; 4 private readonly IAccountRepository _accountRepository; 5 private readonly IdentityUserManager _identityUserManager; 6 private readonly AccountTokenManager _accountTokenManager; 7 8 public string GrantType => IdentityGrantTypes.PhoneCode; 9 10 public PhoneCodeGrantValidator( 11 IOptions<IdentityOptions> identityOptions, 12 IAccountRepository accountRepository, 13 IdentityUserManager identityUserManager, 14 AccountTokenManager accountTokenManager) 15 { 16 _identityOptions = identityOptions; 17 _accountRepository = accountRepository; 18 _identityUserManager = identityUserManager; 19 _accountTokenManager = accountTokenManager; 20 } 21 22 public async Task ValidateAsync(ExtensionGrantValidationContext context) 23 { 24 await _identityOptions.SetAsync(); 25 26 var phoneNumber = context.Request.Raw.Get("phoneNumber"); 27 var code = context.Request.Raw.Get("code"); 28 29 var validateParamsResult = ValidateRequestParams(phoneNumber, code); 30 if (!validateParamsResult.IsNullOrWhiteSpace()) 31 { 32 SetContextError(validateParamsResult, context); 33 return; 34 } 35 36 var identityUser = await _accountRepository.FindByConfirmedPhoneAsync(phoneNumber); 37 if (identityUser == null) 38 { 39 SetContextError("無效的手機號", context); 40 return; 41 } 42 43 if (await _identityUserManager.IsLockedOutAsync(identityUser)) 44 { 45 SetContextError("賬戶已鎖定", context); 46 return; 47 } 48 49 var validateCodeResult = await ValidateCodeLoginAsync(phoneNumber, code); 50 if (!validateCodeResult.IsNullOrWhiteSpace()) 51 { 52 await _identityUserManager.AccessFailedAsync(identityUser); 53 SetContextError(validateCodeResult, context); 54 return; 55 } 56 57 var claims = new List<Claim> 58 { 59 new("phoneNumber", phoneNumber) 60 }; 61 62 if (identityUser.TenantId.HasValue) 63 { 64 claims.Add(new Claim(AbpClaimTypes.TenantId, identityUser.TenantId?.ToString())); 65 } 66 67 claims.AddRange(identityUser.Claims.Select( 68 item => new Claim(item.ClaimType, item.ClaimValue))); 69 70 context.Result = new GrantValidationResult(identityUser.Id.ToString(), GrantType, claims); 71 } 72 73 public async Task<string> ValidateCodeLoginAsync(string phoneNumber, string code) 74 { 75 var isValidCode = await _accountTokenManager.VerifySignInCodeAsync(phoneNumber, code); 76 77 return !isValidCode ? "無效的手機號或驗證碼" : string.Empty; 78 } 79 80 private static string ValidateRequestParams( 81 string phoneNumber, string code) 82 { 83 if (string.IsNullOrWhiteSpace(phoneNumber)) 84 { 85 return "手機號不能為空"; 86 } 87 88 if (string.IsNullOrWhiteSpace(code)) 89 { 90 return "驗證碼不能為空"; 91 } 92 93 return string.Empty; 94 } 95 96 private static void SetContextError( 97 string errorMessage, ExtensionGrantValidationContext context) 98 { 99 context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant) 100 { 101 ErrorDescription = errorMessage 102 }; 103 } 104 }
View Code
註冊擴展服務
1 public override void PreConfigureServices(ServiceConfigurationContext context) 2 { 3 PreConfigure<IIdentityServerBuilder>(builder => 4 { 5 builder.AddExtensionGrantValidator<PhoneCodeGrantValidator>(); 6 }); 7 }
簡單驗證
至此,擴展方式的核心工作已經準備完成,可以通過 postman 進行簡單的實驗。
非擴展授權方式
此方式也比較簡單,校驗手機號以及驗證碼的主體邏輯一致,只需要驗證用戶之後,通過 httpClient 去 IdentityServer 獲取token,然後返回客戶端即可。
其他:為了更好的安全,在登錄失敗後需要顯式的標記登錄失敗,配合 Identity 的一些策略,可以對一段時間內登錄失敗次數過多的賬戶,進行鎖定,防止用戶資訊泄露。
結尾
近期已經從公司離職了。近期也思考了很多,大城市與二線城市在做事風格上確實差別比較大;也有很多令人唏噓的事情,改天總結一下。