.NET8 Identity Register

net8,identity,register · 浏览次数 : 0

小编点评

**代码解读:** ```csharp // 注册用户接口 [RouteGroup("register")] public async Task Register(RegisterRequest registration, HttpContext context, IServiceProvider sp) { // 验证用户邮箱是否支持电子邮件存储 if (!userManager.SupportsUserEmail) { throw new NotSupportedException($"需要支持电子邮件存储的用户名存储。"); } // 获取用户存储服务 var userStore = sp.GetRequiredService>(); // 验证电子邮件格式和有效性 if (string.IsNullOrEmpty(registration.Email) || !_emailAddressAttribute.IsValid(email)) { return CreateValidationProblem(IdentityResult.Failed($"邮箱格式或有效性不正确。")); } // 创建新用户对象 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 CreatedAtRoute("ConfirmEmail", new { id = user.Id }, "Email confirmed successfully."); } // 验证邮箱确认接口 [Route("confirm-email")] public IActionResult ConfirmEmail(string token, IServiceProvider sp) { // 获取用户存储服务 var userManager = sp.GetRequiredService>(); // 验证验证令牌是否有效 var registration = GetTemporaryRegistrationInfoByToken(token); if (registration == null) { return BadRequest("无效或已失效的验证令牌。"); } // 更新用户的邮箱状态为已验证 await userManager.ConfirmEmailAsync(registration.User, token); // 返回成功响应 return Ok("Email confirmed successfully."); } ``` **优点:** * 简化用户注册流程。 * 确保用户邮箱有效性。 * 降低数据库的负载。 **缺点:** * 可能会产生大量的未验证用户,导致数据库冗余。 * 用户体验可能受到影响,因为用户需要先验证邮箱才能完成注册。

正文

分享给需要帮助的人:记一次 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相似的内容:

.NET8 Identity Register

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

造轮子之asp.net core identity

在前面我们完成了应用最基础的功能支持以及数据库配置,接下来就是我们的用户角色登录等功能了,在asp.net core中原生Identity可以让我们快速完成这个功能的开发,在.NET8中,asp.net core identity支持了WebApi的注册登录。这让我们在WebApi中可以更爽快的使用

.Net8 AddKeyedScoped键值key注册服务异常

异常描述:This service descriptor is keyed. Your service provider may not support keyed services. 场景:.Net8 WebAPI应用程序中使用AutoFac替代了默认的DI容器 当使用键值注册服务后: build

NET8中增加的简单适用的DI扩展库Microsoft.Extensions.DependencyInjection.AutoActivation

这个库提供了在启动期间实例化已注册的单例,而不是在首次使用它时实例化。 单例通常在首次使用时创建,这可能会导致响应传入请求的延迟高于平时。在注册时创建实例有助于防止第一次Request请求的SLA 以往我们要在注册的时候启动单例可能会这样写: //注册: services.AddSingleton<

.NET8中的Microsoft.Extensions.Http.Resilience库

接上一篇,https://www.cnblogs.com/vipwan/p/18129361 借助Aspire中新增的Microsoft.Extensions.ServiceDiscovery库,我们可以很容易的做到服务发现,那么服务节点的熔断限流以及重试等弹性机制.NET是怎么处理的呢? 比如下图

【.NET8】访问私有成员新姿势UnsafeAccessor(上)

前言 前几天在.NET性能优化群里面,有群友聊到了.NET8新增的一个特性,这个类叫UnsafeAccessor,有很多群友都不知道这个特性是干嘛的,所以我就想写一篇文章来带大家了解一下这个特性。 其实在很早之前我就有关注到这个特殊的特性,但是当时.NET8还没有正式发布,所以我也没有写文章,现在.

【.NET8】访问私有成员新姿势UnsafeAccessor(下)

前言 书接上回,我们讨论了在.NET8中新增的UnsafeAccessor,并且通过UnsafeAccessor访问了私有成员,这极大的方便了我们代码的编写,当然也聊到了它当前存在的一些局限性,那么它的性能到底如何?我们今天就来实际测试一下。 测试代码 话不多说,直接上代码,本次测试代码如下: us

【c#版本Openfeign】Net8 自带OpenFeign实现远程接口调用

引言 相信巨硬,我们便一直硬。Net版本到现在已经出了7了,8也已经在预览版了,相信在一个半月就会正式发布,其中也有很多拭目以待的新功能了,不仅仅有Apm和Tap的结合,TaskToAscynResult,以及UnsafeAccessor用来获取私有变量,性能比反射,EMIT更高,还有针对Async

【.NET 8】ASP.NET Core计划 - 支持更完善的AOT发布

.NET7.0刚发布不久,.NET社区开始了.NET8.0的开发,重心重新回到了新功能的迭代。 我们知道在.NET7.0中一个令人激动的特新就是支持了NativeAOT,我们可以通过NativeAOT生成本机程序,由于无需JIT编译,所以无需安装.NET Runtime,也进一步的提升了.程序的启动

开源项目分享:ChatGPT 控制台聊天应用

开源项目分享:ChatGPT 控制台聊天应用 分享一个我最近完成的一个小应用,一个ChatGPT 的控制台聊天应用,大家都在搞AI,我也来玩一玩,顺便分享到社区,有兴趣的小伙伴可以去我的github主页下载体验。 项目简介 ChatGPT 控制台聊天应用 基于.NET8实现,一键拥有你的私人 Cha