.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
相关产品推荐
相关产品推荐

