You need to enable JavaScript to run this app. ASP.NET Core Identity 使用CheckPasswordSignInAsync捕获登录失败错误
优惠活动
大模型
产品
解决方案
定价
更多

ASP.NET Core Identity 使用CheckPasswordSignInAsync捕获登录失败错误

ASP.NET Core Identity 登录失败日志捕获实现方案

推荐方案:业务层主动校验错误(无需依赖日志系统)

Identity 内部默认将密码校验失败的细节输出到日志,但 CheckPasswordSignInAsync 返回的 SignInResult 仅包含通用状态,不会暴露具体错误原因。最可控的方式是在业务层主动校验,自行生成对应错误信息,无需捕获内部日志。

你提到的控制台告警(已翻译):

警告:Microsoft.AspNetCore.Identity.UserManager[0]
      用户密码无效。
警告:Microsoft.AspNetCore.Identity.SignInManager[2]
      用户输入的密码不正确。

可参考修改后的业务代码:

// 自定义异常需扩展错误码属性,用于内部日志排查
public class UserLoginException : Exception
{
    public string ErrorCode { get; }
    public UserLoginException(string message, string errorCode) : base(message)
    {
        ErrorCode = errorCode;
    }
}

public async Task<UserReplyDTO> LoginAsync(UserLoginDTO userLoginDTO)
{
    // 补全原代码缺失的用户存在性校验,避免null用户传入SignInManager抛出参数异常
    var user = await _userRepo.GetUserByEmail(userLoginDTO.Email);
    if (user == null)
    {
        // 内部记录用户不存在的错误,对外返回统一模糊提示
        throw new UserLoginException("用户名或密码错误", "UserNotFound");
    }

    // 主动校验密码,对应UserManager输出的「用户密码无效」告警
    bool isPwdValid = await _signInManager.UserManager.CheckPasswordAsync(user, userLoginDTO.Password);
    if (!isPwdValid)
    {
        // 内部记录密码错误的错误,对外返回统一模糊提示
        throw new UserLoginException("用户名或密码错误", "InvalidPassword");
    }

    // 再执行登录状态校验(锁定、权限等)
    var result = await _signInManager.CheckPasswordSignInAsync(user, userLoginDTO.Password, lockoutOnFailure: true);
    if (!result.Succeeded)
    {
        // 根据SignInResult状态生成对应错误
        if (result.IsLockedOut)
            throw new UserLoginException("账号已锁定,请稍后重试", "UserLockedOut");
        if (result.IsNotAllowed)
            throw new UserLoginException("账号未激活", "UserNotAllowed");
        if (result.RequiresTwoFactor)
            throw new UserLoginException("需要二次验证", "RequiresTwoFactor");
        
        throw new UserLoginException("用户名或密码错误", "SignInFailed");
    }

    // 登录成功逻辑保持不变
    UserReplyDTO userReplyDTO = new() 
    { 
        Email = user.Email, 
        Id = user.Id, 
        UserName = user.UserName, 
        Role = user.Role 
    };
    userReplyDTO.Token = _tokenService.GetToken(user);
    return userReplyDTO;
}

接口层调整示例(分层处理错误信息):

[AllowAnonymous]
[HttpPost("login")]
public async Task<IActionResult> Login(UserLoginDTO userLoginDto)
{
    try
    {
        var user = await _authService.LoginAsync(userLoginDto);
        return Ok(user);
    }
    catch (UserLoginException ex)
    {
        // 仅在内部日志记录具体错误码,对外返回模糊提示
        _logger.LogWarning(ex, "登录失败,错误码:{ErrorCode}", ex.ErrorCode);
        return BadRequest(ex.Message);
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "登录接口未知异常");
        return StatusCode(500, "服务器内部错误");
    }
}

可选方案:捕获Identity内部默认输出的日志

如果无需自行校验,仅需捕获Identity默认输出的两条告警,可实现自定义日志提供者,过滤Identity命名空间下的警告日志:

  1. 实现 ILogger 与 ILoggerProvider 接口,仅捕获指定分类的日志
  2. 在程序启动时注册日志提供者

示例代码:

// 自定义日志类,仅捕获Identity相关的警告日志
public class IdentityCaptureLogger : ILogger
{
    private readonly string _categoryName;
    public IdentityCaptureLogger(string categoryName) => _categoryName = categoryName;

    public IDisposable? BeginScope<TState>(TState state) where TState : notnull => null;

    public bool IsEnabled(LogLevel logLevel) => 
        logLevel == LogLevel.Warning && 
        (_categoryName.Contains("Microsoft.AspNetCore.Identity.UserManager") || 
         _categoryName.Contains("Microsoft.AspNetCore.Identity.SignInManager"));

    public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
    {
        if (!IsEnabled(logLevel)) return;
        // 此处获取到的文本与控制台输出的告警完全一致
        string logContent = formatter(state, exception);
        // 写入自定义日志库、缓存到请求上下文等自定义处理
        CustomLogService.Write($"[{_categoryName}] {logContent}");
    }
}

// 自定义日志提供者
public class IdentityCaptureLoggerProvider : ILoggerProvider
{
    public ILogger CreateLogger(string categoryName) => new IdentityCaptureLogger(categoryName);
    public void Dispose() { }
}

// 程序启动时注册(Program.cs)
builder.Logging.AddProvider(new IdentityCaptureLoggerProvider());

注意:该方式属于横切捕获,无需修改业务代码,但日志格式、触发逻辑完全由Identity内部实现决定,灵活性低于主动校验方案。


实用技巧

  • 对外返回统一模糊错误:不要在接口响应中区分「用户不存在」或「密码错误」,防止被恶意枚举已注册账号;具体错误码仅在内部日志中记录
  • 使用PasswordSignInAsync简化代码:SignInManager.PasswordSignInAsync 会自动完成用户查询、密码校验、锁定逻辑,无需手动调用GetUserByEmail与CheckPasswordAsync,减少重复代码
  • 配置合理的锁定策略:如果开启lockoutOnFailure: true,需在Identity配置中设置最大失败次数、锁定时长,避免暴力破解
  • 重写SignInManager扩展能力:如果需要在不修改业务代码的前提下自定义登录逻辑,可继承SignInManager重写对应方法,将自定义错误码封装到返回结果中

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.28 03:06:04