分享给需要帮助的人:记一次 IdentityAPI 中注册的源码解读:设置用户账户为未验证状态,以及除此之外更安全的做法: 延迟用户创建。包含了对优缺点的说明,以及适用场景。


在ASP.NET 8 Identity 中注册API的源码如下:

routeGroup.MapPost("/register", async Task<Results<Ok, ValidationProblem>>
([FromBody] RegisterRequest registration, HttpContext context, [FromServices] IServiceProvider sp) =>
{
var userManager = sp.GetRequiredService<UserManager<TUser>>(); if (!userManager.SupportsUserEmail)
{
throw new NotSupportedException($"{nameof(MapIdentityApi)} requires a user store with email support.");
} var userStore = sp.GetRequiredService<IUserStore<TUser>>();
var emailStore = (IUserEmailStore<TUser>)userStore;
var email = registration.Email; if (string.IsNullOrEmpty(email) || !_emailAddressAttribute.IsValid(email))
{
return CreateValidationProblem(IdentityResult.Failed(userManager.ErrorDescriber.InvalidEmail(email)));
} var user = new TUser { EmailConfirmed = false }; // 标记为未验证
await userStore.SetUserNameAsync(user, email, CancellationToken.None);
await emailStore.SetEmailAsync(user, email, CancellationToken.None);
var result = await userManager.CreateAsync(user, registration.Password); if (!result.Succeeded)
{
return CreateValidationProblem(result);
} await SendConfirmationEmailAsync(user, userManager, context, email);
return TypedResults.Ok();
}); routeGroup.MapGet("/confirm-email", async Task<IResult>
([FromQuery] string userId, [FromQuery] string token, [FromServices] UserManager<TUser> userManager) =>
{
var user = await userManager.FindByIdAsync(userId);
if (user == null)
{
return TypedResults.BadRequest("Invalid user.");
} var result = await userManager.ConfirmEmailAsync(user, token);
if (!result.Succeeded)
{
return TypedResults.BadRequest("Email confirmation failed.");
} user.EmailConfirmed = true; // 更新为已验证
await userManager.UpdateAsync(user); return TypedResults.Ok("Email confirmed successfully.");
});

会发现它在注册的时候使用邮箱作为用户名,配置了邮箱和密码。但是它在发送邮箱验证码之前,就已经通过CreateAsync创建好了账号。这种方式叫做设置用户账户为未验证状态,将 EmailConfirmed 设置为 false,邮箱验证确认后设置为true。

这种方式的缺点很明显:

  1. 数据库冗余:未验证的用户仍然会被创建并保存在数据库中,可能会增加垃圾数据。
  2. 风险较高:未验证用户在短时间内可能会尝试恶意行为,需要额外的监控和限制措施。

优点如下:

  1. 实现简单:直接在用户创建时标记用户为未验证,逻辑简单易于实现。
  2. 用户体验:用户可以立即注册并部分使用系统功能,验证邮箱可以稍后进行。
  3. 安全可控:通过限制未验证用户的操作,可以在确保安全性的同时提供基本的用户体验。

更安全的方式是延迟用户创建,代码如下:

routeGroup.MapPost("/register", async Task<IResult>
([FromBody] RegisterRequest registration, HttpContext context, [FromServices] IServiceProvider sp) =>
{
var userManager = sp.GetRequiredService<UserManager<TUser>>(); if (!userManager.SupportsUserEmail)
{
throw new NotSupportedException($"{nameof(MapIdentityApi)} requires a user store with email support.");
} var userStore = sp.GetRequiredService<IUserStore<TUser>>();
var emailStore = (IUserEmailStore<TUser>)userStore;
var email = registration.Email; if (string.IsNullOrEmpty(email) || !_emailAddressAttribute.IsValid(email))
{
return CreateValidationProblem(IdentityResult.Failed(userManager.ErrorDescriber.InvalidEmail(email)));
} // 生成验证令牌并发送确认邮件
var verificationToken = GenerateVerificationToken();
await SendVerificationEmailAsync(email, verificationToken, context); // 临时保存注册信息
SaveTemporaryRegistrationInfo(registration, verificationToken); return TypedResults.Ok("Please confirm your email.");
}); routeGroup.MapGet("/confirm-email", async Task<IResult>
([FromQuery] string token, [FromServices] IServiceProvider sp) =>
{
var registration = GetTemporaryRegistrationInfoByToken(token); if (registration == null)
{
return TypedResults.BadRequest("Invalid or expired token.");
} var userManager = sp.GetRequiredService<UserManager<TUser>>();
var user = new TUser();
await userStore.SetUserNameAsync(user, registration.Email, CancellationToken.None);
await emailStore.SetEmailAsync(user, registration.Email, CancellationToken.None);
var result = await userManager.CreateAsync(user, registration.Password); if (!result.Succeeded)
{
return CreateValidationProblem(result);
} return TypedResults.Ok("Email confirmed and user created.");
});

会发现它与第一个例子是相反的,它是用户注册后把数据保存在了临时的内存中,再向邮箱发送验证码。通过配置邮箱的时候,用验证码得到用户数据,并以此创建新的账号。

此做法的缺点也很明显:

  1. 实现复杂:需要额外的逻辑来保存临时注册信息并处理验证令牌。
  2. 用户体验:用户在注册后需要先验证邮箱才能完成注册流程,可能会导致部分用户流失。

优点如下:

  1. 避免垃圾用户:只有当用户验证了邮箱后,才会正式创建用户账户,减少垃圾用户数量。
  2. 安全性高:在用户点击确认链接前,账户信息不会进入数据库,降低被滥用的风险。
  3. 资源节省:避免创建大量未验证的用户,节省数据库存储和处理资源。

它们的适用场景如下:

  1. 延迟用户创建:适用于希望最大限度减少垃圾用户并确保用户邮箱有效性的场景,如高安全性要求的系统。
  2. 设置用户账户为未验证状态:适用于希望提供更流畅的用户体验,允许用户在验证邮箱前进行部分操作的场景,如社交平台或内容网站。

.NET8 Identity Register的更多相关文章

  1. iOS-开发者相关的几种证书

    目录 引言 写在前面 一App IDbundle identifier 二设备Device 三开发证书Certificates 证书的概念 数字证书的概念 iOS开发证书 iOS开发证书的根证书 申请 ...

  2. [转载]iOS Provisioning Profile(Certificate)与Code Signing详解

    原文:http://blog.csdn.net/phunxm/article/details/42685597 引言 关于开发证书配置(Certificates & Identifiers & ...

  3. iOS Provisioning Profile(Certificate)与Code Signing详解

    引言 关于开发证书配置(Certificates & Identifiers & Provisioning Profiles),相信做 iOS 开发的同学没少被折腾.对于一个 iOS ...

  4. 【上传AppStore】iOS项目上传到AppStore步骤流程(第三章) - 基本信息总汇

    一.App ID(bundle identifier) App ID即Product ID,用于标识一个或者一组App. App ID应该和Xcode中的Bundle Identifier是一致(Ex ...

  5. 【转】iOS Provisioning Profile(Certificate)与Code Signing详解 -- 待看

    原文网址:http://blog.sina.com.cn/s/blog_82c8198f0102vy4j.html 引言 关于开发证书配置(Certificates & Identifiers ...

  6. 【转】 iOS Provisioning Profile(Certificate)与Code Signing详解

    原文:http://blog.csdn.net/phunxm/article/details/42685597 引言 关于开发证书配置(Certificates & Identifiers & ...

  7. iOS 证书那些事

    关于开发证书配置(Certificates & Identifiers & Provisioning Profiles),相信做iOS开发的同学没少被折腾.对于一个iOS开发小白.半吊 ...

  8. 网络协议 终章 - GTP 协议:复杂的移动网络

        前面都是讲电脑上网的情景,今天我们就来认识下使用最多的移动网络上网场景. 移动网络的发展历程     你一定知道手机上网有 2G.3G.4G 的说法,究竟这都是什么意思呢?有一个通俗的说法就是 ...

  9. Hyperledger Fabric CA User’s Guide——开始(三)

    Fabric CA User’s Guide——开始 先决条件 安装Go 1.9+ 设置正确的GOPATH环境变量 安装了libtool和libtdhl-dev包 下面是在Ubuntu上安装libto ...

  10. iOS 证书详解

    引言 关于开发证书配置(Certificates & Identifiers & Provisioning Profiles),相信做iOS开发的同学没少被折腾.对于一个iOS开发小白 ...

随机推荐

  1. Maven 读取pom.xml

    方法一 1.编写配置文件,要读取的内容用@@包裹. spring: application: # @变量名@ 读取pom.xml中的值 version: @project.version@ 2.pom ...

  2. 动态库 DLL 封装五:dll中弹出一个dialog窗口

    操作步骤: 1.在dll项目中,点击 资源,新建一个 dialog 2.cpp文件 CDialog dlg(IDD_STA); // 显示窗口 dlg.Create(IDD_STA, 0); dlg. ...

  3. k8s之存储卷local PV

    一.简介 local能够作为PV使用的本地存储卷. local卷插件用于将本地存储设备(如磁盘.分区或目录) 配置为卷. hostPath卷在Pod被重建后可能被调试至其它节点而无法再次使用此前的数据 ...

  4. K8s集群nginx-ingress监控告警最佳实践

    本文分享自华为云社区<K8s集群nginx-ingress监控告警最佳实践>,作者:可以交个朋友. 一 背景 nginx-ingress作为K8s集群中的关键组成部分.主要负责k8s集群中 ...

  5. JavaScript中的事件模型如何理解?

    一.事件与事件流 javascript中的事件,可以理解就是在HTML文档或者浏览器中发生的一种交互操作,使得网页具备互动性, 常见的有加载事件.鼠标事件.自定义事件等 由于DOM是一个树结构,如果在 ...

  6. 对中间件概念的理解,如何封装 node 中间件

    一.是什么 中间件(Middleware)是介于应用系统和系统软件之间的一类软件,它使用系统软件所提供的基础服务(功能),衔接网络上应用系统的各个部分或不同的应用,能够达到资源共享.功能共享的目的 在 ...

  7. webpack中常见的Plugin?解决了什么问题?

    一.是什么 Plugin(Plug-in)是一种计算机应用程序,它和主应用程序互相交互,以提供特定的功能 是一种遵循一定规范的应用程序接口编写出来的程序,只能运行在程序规定的系统下,因为其需要调用原纯 ...

  8. Python爬取网页遇到:selenium.common.exceptions.WebDriverException解决方法

    在PyCharm中写好下列程序: 一运行遇到下列报错: selenium.common.exceptions.WebDriverException: Message: 'chromedriver' e ...

  9. Apache Dubbo 3.0.0 正式发布 - 全面拥抱云原生

    简介: 一个新的里程碑! 一.背景 自从 Apache Dubbo 在 2011 年开源以来,在一众大规模互联网.IT公司的实践中积累了大量经验后,Dubbo 凭借对 Java 用户友好.功能丰富.治 ...

  10. [Go] golang-migrate/migrate 快速使用指南

    1. CLI 用途的安装 [文档] [确保] CLI 工具使用 go 命令安装时,不应该在 go.mod 所在的目录中执行命令,也就是先进入到其它非项目目录内. $ go get -tags 'pos ...