扩展.Net Core Identity Server 授权方式,实现 手机号+ 验证码 登录


背景

国内来讲,注册/登录流程都是尽可能的简单,注册流程复杂,容易流失客户。

手机号 + 短信验证码的方式非常普遍;但是框架默认并没有类似的功能,需要我们自己进行扩展。

思路

  1. 验证登录手机号为注册用户,且验证码正确;验证通过后,去 Identity Server 获取Token,然后返回客户端。
  2. 扩展 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 }

实现 IExtensionGrantValidator

主要实现对手机号以及短信验证码的校验

 1 public async Task ValidateAsync(ExtensionGrantValidationContext context)
 2     {
 3         await _identityOptions.SetAsync();
 4 
 5         var phoneNumber = context.Request.Raw.Get("phoneNumber");
 6         var code = context.Request.Raw.Get("code");
 7 
 8         var validateParamsResult = ValidateRequestParams(phoneNumber, code);
 9         if (!validateParamsResult.IsNullOrWhiteSpace())
10         {
11             SetContextError(validateParamsResult, context);
12             return;
13         }
14             
15         var identityUser = await _accountRepository.FindByConfirmedPhoneAsync(phoneNumber);
16         if (identityUser == null)
17         {
18             SetContextError("无效的手机号", context);
19             return;
20         }
21             
22         if (await _identityUserManager.IsLockedOutAsync(identityUser))
23         {
24             SetContextError("账户已锁定", context);
25             return;
26         }
27 
28         var validateCodeResult = await ValidateCodeLoginAsync(phoneNumber, code);
29         if (!validateCodeResult.IsNullOrWhiteSpace())
30         {
31             await _identityUserManager.AccessFailedAsync(identityUser);
32             SetContextError(validateCodeResult, context);
33             return;
34         }
35 
36         var claims = new List
37         {
38             new("phoneNumber", phoneNumber)
39         };
40 
41         if (identityUser.TenantId.HasValue)
42         {
43             claims.Add(new Claim(AbpClaimTypes.TenantId, identityUser.TenantId?.ToString()));
44         }
45 
46         claims.AddRange(identityUser.Claims.Select(
47             item => new Claim(item.ClaimType, item.ClaimValue)));
48 
49         context.Result = new GrantValidationResult(identityUser.Id.ToString(), GrantType, claims);
50     }

注册扩展服务

1   public override void PreConfigureServices(ServiceConfigurationContext context)
2     {
3         PreConfigure(builder =>
4         {
5             builder.AddExtensionGrantValidator();
6         });
7     }

简单验证

至此,扩展方式的核心工作已经准备完成,可以通过 postman 进行简单的实验。

非扩展授权方式

此方式也比较简单,校验手机号以及验证码的主体逻辑一致,只需要验证用户之后,

通过 httpClient 去 IdentityServer 获取token,然后返回客户端即可。

其他:为了更好的安全,在登录失败后需要显式的标记登录失败,配合 Identity 的一些策略,

可以对一段时间内登录失败次数过多的账户,进行锁定,防止用户信息泄露。