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

