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

能否将ASP.NET Core Identity与JWT结合?启动报错求解决

问题解决:IEmailSender未注册错误 + 替换ASP.NET Core Identity默认令牌为JWT

一、先解决IEmailSender未注册的启动错误

MapIdentityApi<User>底层依赖IEmailSender<TUser>服务处理邮箱验证、密码重置这类流程,你没注册该服务才触发了InvalidOperationException。有两种处理方式:

1. 快速临时解决:注册空实现

如果暂时不需要邮件功能,可注册一个空实现绕过报错:

// 在服务扩展或Program.cs中添加注册代码
services.AddScoped<IEmailSender<YourUserType>, NullEmailSender>();

// 定义空实现类
public class NullEmailSender : IEmailSender<YourUserType>
{
    public Task SendConfirmationLinkAsync(YourUserType user, string email, string confirmationLink)
    {
        return Task.CompletedTask;
    }

    public Task SendPasswordResetLinkAsync(YourUserType user, string email, string resetLink)
    {
        return Task.CompletedTask;
    }

    public Task SendPasswordResetCodeAsync(YourUserType user, string email, string resetCode)
    {
        return Task.CompletedTask;
    }
}

2. 正式方案:实现自定义邮件发送服务

如果后续需要邮箱验证等功能,可基于IEmailSender<YourUserType>实现自己的邮件逻辑(比如用SMTP或第三方邮件服务),再注册到DI容器:

// 自定义邮件发送实现
public class CustomEmailSender : IEmailSender<YourUserType>
{
    private readonly IConfiguration _config;

    public CustomEmailSender(IConfiguration config)
    {
        _config = config;
    }

    public async Task SendConfirmationLinkAsync(YourUserType user, string email, string confirmationLink)
    {
        // 示例:用SMTP发送确认邮件
        using var smtpClient = new SmtpClient(_config["Smtp:Host"])
        {
            Port = int.Parse(_config["Smtp:Port"]),
            Credentials = new NetworkCredential(_config["Smtp:Username"], _config["Smtp:Password"]),
            EnableSsl = true
        };

        var message = new MailMessage(
            from: _config["Smtp:From"],
            to: email,
            subject: "邮箱确认",
            body: $"点击链接完成确认:{confirmationLink}"
        );

        await smtpClient.SendMailAsync(message);
    }

    public Task SendPasswordResetLinkAsync(YourUserType user, string email, string resetLink)
    {
        // 实现密码重置链接发送逻辑
        return Task.CompletedTask;
    }

    public Task SendPasswordResetCodeAsync(YourUserType user, string email, string resetCode)
    {
        // 实现密码重置验证码发送逻辑
        return Task.CompletedTask;
    }
}

// 注册到服务容器
services.AddScoped<IEmailSender<YourUserType>, CustomEmailSender>();

二、替换默认Bearer令牌为JWT

解决报错后,按以下步骤配置JWT替换Identity的默认文本令牌:

1. 添加JWT配置到appsettings.json

"Jwt": {
  "Issuer": "YourApiIssuer",
  "Audience": "YourApiAudience",
  "Key": "YourSuperSecretKeyAtLeast16CharactersLong" // 生产环境建议从环境变量读取安全密钥
}

2. 在服务扩展中配置JWT认证

// 在AddIdentityCore之后添加JWT认证服务
services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
    options.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuer = true,
        ValidateAudience = true,
        ValidateLifetime = true,
        ValidateIssuerSigningKey = true,
        ValidIssuer = configuration["Jwt:Issuer"],
        ValidAudience = configuration["Jwt:Audience"],
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["Jwt:Key"]))
    };
});

// 确保Identity服务配置正确
services.AddIdentityCore<YourUserType>(options =>
{
    // 可自定义密码、用户验证规则
    options.Password.RequireDigit = true;
    options.Password.RequireLowercase = true;
})
.AddEntityFrameworkStores<YourDbContext>()
.AddApiEndpoints(); // 必须添加,支持MapIdentityApi

3. 调整中间件顺序(关键)

中间件顺序直接影响功能生效,确保UseAuthentication在UseAuthorization和MapIdentityApi之前:

var app = builder.Build();

app.UseHttpsRedirection();
app.UseRouting();

app.UseAuthentication(); // 先执行认证
app.UseAuthorization(); // 再执行授权

// 映射Identity的API端点
app.MapIdentityApi<YourUserType>();

// 映射自定义API控制器
app.MapControllers();

app.Run();

4. 自定义登录接口返回JWT(可选)

默认/login端点返回Identity原生令牌,若要直接返回JWT,可自定义登录接口:

app.MapPost("/api/auth/login", async (LoginRequest request, SignInManager<YourUserType> signInManager, IConfiguration config) =>
{
    var user = await signInManager.UserManager.FindByEmailAsync(request.Email);
    if (user == null) return Results.Unauthorized();

    var signInResult = await signInManager.CheckPasswordSignInAsync(user, request.Password, lockoutOnFailure: false);
    if (!signInResult.Succeeded) return Results.Unauthorized();

    // 构建JWT声明
    var claims = new List<Claim>
    {
        new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
        new Claim(ClaimTypes.Email, user.Email)
        // 添加其他业务所需声明
    };

    // 生成JWT令牌
    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.AddHours(1),
        signingCredentials: creds
    );

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

// 登录请求模型
public class LoginRequest
{
    public string Email { get; set; }
    public string Password { get; set; }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 20:23:12