You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在C# ASP.NET Core 8 Web API(Identity User)中用手机号替代邮箱注册登录

基于ASP.NET Core 8.0 Identity的手机号登录/注册实现方案

以下是完整的替代邮箱、用手机号完成注册登录的落地方案,涵盖核心代码与配置细节:

1. 自定义IdentityUser实体

默认IdentityUser以邮箱为核心标识,我们需要自定义实体,将手机号设为必填且唯一的核心字段:

using Microsoft.AspNetCore.Identity;
using System.ComponentModel.DataAnnotations;

public class AppUser : IdentityUser
{
    // 移除邮箱必填约束(若不需要邮箱字段可直接删除)
    public override string? Email { get; set; }
    public override bool EmailConfirmed { get; set; }

    // 强制手机号必填、限定格式
    [Required(ErrorMessage = "手机号不能为空")]
    [Phone(ErrorMessage = "手机号格式不正确")]
    [MaxLength(20)]
    public override string PhoneNumber { get; set; } = string.Empty;
}

2. 更新DbContext

让Identity使用自定义的AppUser实体:

using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;

public class AppDbContext : IdentityDbContext<AppUser>
{
    public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
}

3. 配置Identity服务(Program.cs)

调整Identity的默认规则,适配手机号作为核心标识:

builder.Services.AddIdentity<AppUser, IdentityRole>(options =>
{
    // 关闭邮箱唯一性要求
    options.User.RequireUniqueEmail = false;
    // 允许用户名包含手机号的合法字符(如数字、+、-)
    options.User.AllowedUserNameCharacters = "0123456789+-";
    
    // 可选:强制登录前需验证手机号
    options.SignIn.RequireConfirmedPhoneNumber = true;
    
    // 按需配置密码复杂度规则
    options.Password.RequireDigit = true;
    options.Password.RequireLowercase = true;
    options.Password.RequireUppercase = false;
    options.Password.RequireNonAlphanumeric = false;
    options.Password.RequiredLength = 6;
})
.AddEntityFrameworkStores<AppDbContext>()
.AddDefaultTokenProviders(); // 用于生成手机号验证令牌

4. 实现注册接口

接收手机号与密码,完成用户创建与可选的手机号验证:

[ApiController]
[Route("api/auth")]
public class AuthController : ControllerBase
{
    private readonly UserManager<AppUser> _userManager;

    public AuthController(UserManager<AppUser> userManager)
    {
        _userManager = userManager;
    }

    [HttpPost("register")]
    public async Task<IActionResult> Register([FromBody] RegisterRequest request)
    {
        if (!ModelState.IsValid)
            return BadRequest(ModelState);

        // 检查手机号是否已注册
        var existingUser = await _userManager.FindByPhoneNumberAsync(request.PhoneNumber);
        if (existingUser != null)
            return BadRequest("该手机号已被注册");

        // 创建用户,将手机号设为用户名(适配Identity默认登录逻辑)
        var user = new AppUser
        {
            UserName = request.PhoneNumber,
            PhoneNumber = request.PhoneNumber
        };

        var createResult = await _userManager.CreateAsync(user, request.Password);
        if (!createResult.Succeeded)
            return BadRequest(createResult.Errors.Select(e => e.Description));

        // 生成手机号验证令牌(需自行集成短信服务发送)
        var verifyToken = await _userManager.GenerateChangePhoneNumberTokenAsync(user, request.PhoneNumber);
        // TODO: 调用第三方短信API发送verifyToken给用户

        return Ok("注册成功,请查收短信验证码完成验证");
    }

    // 注册请求DTO
    public class RegisterRequest
    {
        [Required]
        [Phone]
        public string PhoneNumber { get; set; } = string.Empty;

        [Required]
        [DataType(DataType.Password)]
        public string Password { get; set; } = string.Empty;
    }
}

5. 实现手机号登录接口

直接通过手机号查找用户并验证密码:

[HttpPost("login")]
public async Task<IActionResult> Login([FromBody] LoginRequest request)
{
    if (!ModelState.IsValid)
        return BadRequest(ModelState);

    // 通过手机号查找用户
    var user = await _userManager.FindByPhoneNumberAsync(request.PhoneNumber);
    if (user == null)
        return Unauthorized("手机号或密码错误");

    // 验证密码(若开启了手机号验证,需先检查PhoneNumberConfirmed)
    if (!user.PhoneNumberConfirmed)
        return Unauthorized("请先验证手机号");

    var passwordResult = await _userManager.CheckPasswordAsync(user, request.Password);
    if (!passwordResult)
        return Unauthorized("手机号或密码错误");

    // TODO: 生成JWT令牌(需自行实现JWT配置与生成逻辑)
    var jwtToken = GenerateJwtToken(user);

    return Ok(new { Token = jwtToken });
}

// 登录请求DTO
public class LoginRequest
{
    [Required]
    [Phone]
    public string PhoneNumber { get; set; } = string.Empty;

    [Required]
    [DataType(DataType.Password)]
    public string Password { get; set; } = string.Empty;
}

6. 补充:手机号验证接口

用于用户提交短信验证码完成手机号验证:

[HttpPost("verify-phone")]
public async Task<IActionResult> VerifyPhone([FromBody] VerifyPhoneRequest request)
{
    if (!ModelState.IsValid)
        return BadRequest(ModelState);

    var user = await _userManager.FindByPhoneNumberAsync(request.PhoneNumber);
    if (user == null)
        return BadRequest("用户不存在");

    var verifyResult = await _userManager.VerifyChangePhoneNumberTokenAsync(user, request.VerifyToken, request.PhoneNumber);
    if (!verifyResult)
        return BadRequest("验证码无效或已过期");

    // 标记手机号已验证
    user.PhoneNumberConfirmed = true;
    await _userManager.UpdateAsync(user);

    return Ok("手机号验证成功");
}

public class VerifyPhoneRequest
{
    [Required]
    [Phone]
    public string PhoneNumber { get; set; } = string.Empty;

    [Required]
    public string VerifyToken { get; set; } = string.Empty;
}

关键注意事项

  • 数据库迁移:修改实体后需执行Add-Migration PhoneNumberAsPrimary和Update-Database更新数据库结构
  • 短信服务集成:需自行对接第三方短信平台(如阿里云、腾讯云)实现验证码发送
  • JWT配置:若使用JWT认证,需在Program.cs中添加AddJwtBearer配置,并实现GenerateJwtToken方法生成令牌

内容的提问来源于stack exchange,提问作者MAD_CRAZY

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 01:57:07