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

.NET 8 Identity API如何配置接收哈希密码而非明文密码?

解决方案:让.NET 8 Identity API支持接收哈希密码

要让你的/login和/register端点支持接收哈希密码,核心是绕过Identity默认的密码二次哈希逻辑,并自定义登录验证逻辑。以下是具体实现步骤:

1. 替换默认的PasswordHasher(可选,用于保留默认注册端点)

如果想继续使用Identity默认的注册端点,需要自定义PasswordHasher,让它检测到传入的是已哈希密码时直接返回,不再二次哈希:

public class CustomPasswordHasher<TUser> : PasswordHasher<TUser> where TUser : class
{
    private readonly PasswordHasherOptions _options;

    public CustomPasswordHasher(IOptions<PasswordHasherOptions> optionsAccessor = null)
    {
        _options = optionsAccessor?.Value ?? new PasswordHasherOptions();
    }

    public override string HashPassword(TUser user, string password)
    {
        // 验证是否为Identity标准格式的哈希密码(PBKDF2)
        if (IsIdentityHashedPassword(password))
        {
            return password;
        }
        return base.HashPassword(user, password);
    }

    private bool IsIdentityHashedPassword(string password)
    {
        if (string.IsNullOrEmpty(password)) return false;
        var parts = password.Split('.');
        // Identity默认PBKDF2哈希格式包含4个部分:版本、迭代次数、盐、哈希值
        return parts.Length == 4 && parts[0] == "PBKDF2";
    }
}

在Program.cs中注册这个自定义PasswordHasher:

builder.Services.AddIdentityApiEndpoints<User>()
    .AddEntityFrameworkStores<ApplicationDbContext>()
    .AddPasswordHasher<CustomPasswordHasher<User>>(); // 添加此行

2. 自定义登录端点(必须,支持哈希密码验证)

默认的登录端点会将传入的密码当作明文验证,因此需要自定义登录逻辑,判断传入的是哈希密码还是明文:

第一步:定义请求模型

public class RegisterRequest
{
    [Required]
    public string Email { get; set; }
    [Required]
    public string Password { get; set; }
    [Compare("Password")]
    public string ConfirmPassword { get; set; }
}

public class LoginRequest
{
    [Required]
    public string Email { get; set; }
    [Required]
    public string Password { get; set; }
}

第二步:实现自定义端点

先移除默认的app.MapGroup("/api/Identity").MapIdentityApi<User>();,然后添加自定义端点:

// 自定义注册端点
app.MapPost("/api/Identity/register", async (RegisterRequest request, UserManager<User> userManager) =>
{
    var user = new User { UserName = request.Email, Email = request.Email };
    // 配合CustomPasswordHasher,直接使用传入的哈希密码存储
    var result = await userManager.CreateAsync(user, request.Password);

    if (!result.Succeeded)
    {
        return Results.BadRequest(result.Errors);
    }

    var token = await userManager.GenerateEmailConfirmationTokenAsync(user);
    return Results.Ok(new { UserId = user.Id, ConfirmationToken = token });
})
.WithName("RegisterUser")
.WithOpenApi();

// 自定义登录端点
app.MapPost("/api/Identity/login", async (LoginRequest request, SignInManager<User> signInManager, IConfiguration config) =>
{
    var user = await signInManager.UserManager.FindByEmailAsync(request.Email);
    if (user == null) return Results.Unauthorized();

    bool isPasswordValid;
    if (IsIdentityHashedPassword(request.Password))
    {
        // 直接比较存储的哈希与传入的哈希
        isPasswordValid = user.PasswordHash == request.Password;
    }
    else
    {
        // 正常验证明文密码
        var signInResult = await signInManager.CheckPasswordSignInAsync(user, request.Password, lockoutOnFailure: false);
        isPasswordValid = signInResult.Succeeded;
    }

    if (!isPasswordValid) return Results.Unauthorized();

    // 生成JWT Token
    var claims = new List<Claim>
    {
        new(ClaimTypes.NameIdentifier, user.Id),
        new(ClaimTypes.Email, user.Email)
    };

    var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config["Jwt:Key"]));
    var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

    var token = new JwtSecurityToken(
        issuer: config["Jwt:Issuer"],
        audience: config["Jwt:Audience"],
        claims: claims,
        expires: DateTime.Now.AddMinutes(30),
        signingCredentials: creds);

    return Results.Ok(new { Token = new JwtSecurityTokenHandler().WriteToken(token) });
})
.WithName("LoginUser")
.WithOpenApi();

// 哈希检测工具方法
static bool IsIdentityHashedPassword(string password)
{
    if (string.IsNullOrEmpty(password)) return false;
    var parts = password.Split('.');
    return parts.Length == 4 && parts[0] == "PBKDF2";
}

第三步:配置JWT认证

在Program.cs中替换原有的builder.Services.AddAuthentication();为:

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = builder.Configuration["Jwt:Issuer"],
            ValidAudience = builder.Configuration["Jwt:Audience"],
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]))
        };
    });

同时在appsettings.json中添加JWT配置:

"Jwt": {
  "Key": "YourSecureSecretKey_AtLeast16CharsLong",
  "Issuer": "YourApiIssuer",
  "Audience": "YourApiAudience"
}

3. Swagger调试支持

自定义端点的请求模型会自动在Swagger中生成参数,你可以直接在Password字段输入哈希值进行调试。

注意:直接接收哈希密码存在安全风险,攻击者可直接使用泄露的哈希登录,此方案仅建议用于调试环境,生产环境请严格使用明文密码传输(配合HTTPS)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 14:07:08