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

ASP.NET Core Minimal API自定义中间件获取用户关联机构ID问题

解决方案

一、修正中间件执行顺序

ASP.NET Core中间件的执行顺序直接决定上下文数据的可用性,必须严格遵循路由 → 认证 → 授权 → 自定义业务中间件的顺序,这是解决你当前报错和用户信息为空问题的核心:

var builder = WebApplication.CreateBuilder(args);

// 注册核心服务(EF上下文、JWT认证等)
builder.Services.AddDbContext<YourDbContext>(options => 
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        // 你的JWT配置逻辑(从配置读取密钥、Audience、Issuer等)
        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"]))
        };
    });
builder.Services.AddAuthorization();

var app = builder.Build();

// 中间件顺序严格按此排列
app.UseRouting();
app.UseAuthentication(); // 先完成JWT认证,填充ClaimsPrincipal
app.UseAuthorization(); // 执行授权逻辑,满足接口的授权元数据要求
app.UseMiddleware<InstituteIdMiddleware>(); // 自定义中间件,此时已能获取到认证后的用户信息
app.UseEndpoints(endpoints => endpoints.MapControllers());

app.Run();

二、实现自定义机构ID中间件

中间件从已认证的ClaimsPrincipal中提取用户名,通过EF查询用户关联的InstituteID,将其存入HttpContext.Items供后续接口直接使用:

public class InstituteIdMiddleware
{
    private readonly RequestDelegate _next;

    public InstituteIdMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context, YourDbContext dbContext)
    {
        // 仅处理已通过认证的请求
        if (context.User.Identity?.IsAuthenticated == true)
        {
            // 从Claims中提取用户名(需与你的JWT Token声明字段匹配)
            var username = context.User.FindFirstValue(ClaimTypes.Name) 
                          ?? context.User.FindFirstValue("username");

            if (!string.IsNullOrEmpty(username))
            {
                // 查询用户关联的机构(可添加内存缓存优化重复查询性能)
                var user = await dbContext.Users
                    .Include(u => u.Institute)
                    .FirstOrDefaultAsync(u => u.Username == username);

                if (user?.Institute != null)
                {
                    // 将机构ID存入HttpContext.Items,供接口读取
                    context.Items["InstituteId"] = user.Institute.Id;
                }
                else
                {
                    // 处理用户无关联机构的异常场景
                    context.Response.StatusCode = StatusCodes.Status403Forbidden;
                    await context.Response.WriteAsync("用户未关联有效机构");
                    return;
                }
            }
        }

        await _next(context);
    }
}

三、在Minimal API接口中使用机构ID

接口直接从HttpContext.Items中提取机构ID,用于数据过滤或资源创建:

app.MapGet("/timeslot", async (HttpContext context, YourDbContext dbContext) =>
{
    if (!context.Items.TryGetValue("InstituteId", out var instituteIdObj) || instituteIdObj == null)
    {
        return Results.Forbid();
    }

    // 根据你的Institute.ID数据类型调整转换逻辑
    var instituteId = (int)instituteIdObj;
    var timeslots = await dbContext.TimeSlots
        .Where(ts => ts.InstituteId == instituteId)
        .ToListAsync();

    return Results.Ok(timeslots);
}).RequireAuthorization(); // 确保接口启用授权校验

四、可选优化:使用ClaimsTransformation预加载机构ID

如果不想每次请求都查询数据库,可以通过IClaimsTransformation将机构ID提前加入用户的Claims集合,后续直接从ClaimsPrincipal读取:

public class InstituteClaimsTransformation : IClaimsTransformation
{
    private readonly YourDbContext _dbContext;
    private readonly IMemoryCache _cache;

    public InstituteClaimsTransformation(YourDbContext dbContext, IMemoryCache cache)
    {
        _dbContext = dbContext;
        _cache = cache;
    }

    public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
    {
        if (!principal.Identity?.IsAuthenticated == true)
        {
            return principal;
        }

        var username = principal.FindFirstValue(ClaimTypes.Name) ?? principal.FindFirstValue("username");
        if (string.IsNullOrEmpty(username))
        {
            return principal;
        }

        // 使用缓存避免重复查询数据库
        var instituteId = await _cache.GetOrCreateAsync($"InstituteId_{username}", async entry =>
        {
            entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10);
            var user = await _dbContext.Users
                .Include(u => u.Institute)
                .FirstOrDefaultAsync(u => u.Username == username);
            return user?.Institute?.Id.ToString();
        });

        if (!string.IsNullOrEmpty(instituteId))
        {
            var identity = principal.Identity as ClaimsIdentity;
            identity?.AddClaim(new Claim("institute_id", instituteId));
        }

        return principal;
    }
}

在Program.cs中注册该服务:

builder.Services.AddTransient<IClaimsTransformation, InstituteClaimsTransformation>();
builder.Services.AddMemoryCache();

此时接口可直接从Claims读取机构ID:

app.MapGet("/timeslot", async (ClaimsPrincipal user, YourDbContext dbContext) =>
{
    var instituteIdStr = user.FindFirstValue("institute_id");
    if (!int.TryParse(instituteIdStr, out var instituteId))
    {
        return Results.Forbid();
    }

    var timeslots = await dbContext.TimeSlots
        .Where(ts => ts.InstituteId == instituteId)
        .ToListAsync();

    return Results.Ok(timeslots);
}).RequireAuthorization();

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 22:13:14