ASP.NET Core 8中使用OpenIddict无法在中间件获取已认证用户
问题:ASP.NET Core中间件无法自动获取已认证用户声明
在ASP.NET Core应用中使用OpenIddict进行身份认证时,自定义中间件里无法自动获取context.User的已认证用户声明,必须手动调用context.AuthenticateAsync才能获取到Principal。尝试调整中间件注册顺序后问题仍未解决,需排查配置遗漏点。
Program.cs配置
var builder = WebApplication.CreateBuilder(args); if (builder.Environment.IsDevelopment()) builder.Configuration.AddUserSecrets<Program>(); var openIddictSettings = new OpenIddictSettings(); builder.Configuration.GetSection("OpenIddict").Bind(openIddictSettings); // Add services to the container. var origins = builder.Configuration.GetSection("Cors:Origins").Get<string[]>(); builder.Services.AddCors(options => { options.AddDefaultPolicy(config => { config.AllowAnyHeader(); config.AllowAnyMethod(); config.AllowCredentials(); config.WithOrigins(origins) .WithExposedHeaders("Content-Disposition"); }); }); builder.Services.AddNNTCupBusiness(builder.Configuration); builder.Services.AddDbContext<IdentityDbContext<IdentityUser>>(options => { options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")); }); builder.Services.AddIdentity<IdentityUser, IdentityRole>() .AddTokenProvider<DataProtectorTokenProvider<IdentityUser>>(TokenOptions.DefaultProvider) .AddEntityFrameworkStores<IdentityDbContext<IdentityUser>>(); builder.Services.AddControllers(options => { options.SuppressImplicitRequiredAttributeForNonNullableReferenceTypes = true; var policyBuilder = new AuthorizationPolicyBuilder() .RequireAuthenticatedUser() .AddAuthenticationSchemes(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme); var policy = policyBuilder.Build(); options.Filters.Add(new AuthorizeFilter(policy)); options.ModelValidatorProviders.Clear(); }).AddNewtonsoftJson(x => { x.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore; x.SerializerSettings.Converters.Add(new JsonStringConverter()); x.SerializerSettings.DateTimeZoneHandling = Newtonsoft.Json.DateTimeZoneHandling.Utc; x.SerializerSettings.DateFormatString = "yyyy'-'MM'-'dd'T'HH':'mm':'ssZ"; }) .AddJsonOptions(x => { x.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles; x.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); x.JsonSerializerOptions.Converters.Add(new DateTimeConverter()); } ); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddOpenApiDocument(); builder.Services.AddOpenIddict() .AddValidation(options => { // Note: the validation handler uses OpenID Connect discovery // to retrieve the address of the introspection endpoint. options.SetIssuer(openIddictSettings.StsUrl); options.AddAudiences(openIddictSettings.Audience); // Configure the validation handler to use introspection and register the client // credentials used when communicating with the remote introspection endpoint. options.SetClientId(openIddictSettings.ApiClientId); // Register the System.Net.Http integration. options.UseSystemNetHttp(); // Register the ASP.NET Core host. options.UseAspNetCore(); }); builder.Services.AddAuthentication(options => { // When targeting OpenIddict 3.0, OpenIddictValidationAspNetCoreDefaults // must be used instead of OpenIddictValidationDefaults. options.DefaultAuthenticateScheme = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme; options.DefaultChallengeScheme = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme; }); builder.Services.AddSwaggerGen(options => { options.SwaggerDoc("v1", new OpenApiInfo { Title = "NNT CUP Api", Version = "v1" }); options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme { Description = "OAuth2.0 Auth Code with PKCE", Name = "oauth2", Type = SecuritySchemeType.OAuth2, Flows = new OpenApiOAuthFlows { AuthorizationCode = new OpenApiOAuthFlow { AuthorizationUrl = new Uri(openIddictSettings.StsUrl + "/connect/authorize"), TokenUrl = new Uri(openIddictSettings.StsUrl + "/connect/token"), Scopes = new Dictionary<string, string> { { openIddictSettings.SwaggerScope, "read the api" } } } } }); options.AddSecurityRequirement(new OpenApiSecurityRequirement { { new OpenApiSecurityScheme { Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "oauth2" } }, new[] { openIddictSettings.SwaggerScope } } }); }); var app = builder.Build(); app.UseRequestLocalization(options => { var cultureService = app.Services.GetService<ICultureService>(); var cultureCodes = cultureService.GetAll().Result.Select(x => x.CultureCode).ToArray(); options.AddSupportedCultures(cultureCodes); options.AddSupportedUICultures(cultureCodes); options.DefaultRequestCulture = new RequestCulture("en-US"); }); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(options => { options.SwaggerEndpoint("/swagger/v1/swagger.json", "OpenIddict_OAuth_CUS_API v1"); options.OAuthClientId(openIddictSettings.SwaggerClientId); options.OAuthScopeSeparator(" "); options.OAuthUsePkce(); }); } app.UseDefaultFiles(); app.UseStaticFiles(); app.UseCors(); app.UseHttpsRedirection(); app.UseAuthentication(); app.UseAuthorization(); app.Use(async (context, next) => { await next(); if (context.Response.StatusCode == 404 && !Path.HasExtension(context.Request.Path.Value)) { context.Request.Path = "/index.html"; await next(); } }); app.UseMiddleware<ApplicationUserCustomerValidationMiddleware>(); app.UseMiddleware<ExportToExcelMiddleware>(); app.MapControllers(); app.Run();
自定义中间件代码
public class ApplicationUserCustomerValidationMiddleware(RequestDelegate next) { // Services can only be injected inside the InvokeAsync method public async Task InvokeAsync( HttpContext context, IHttpContextAccessor contextAccessor, IIdentityService identityService, ICacheService cacheService, IApplicationUserCustomerRepository applicationUserCustomerRepository) { context.User = (await context.AuthenticateAsync(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme)).Principal!; // Check if the request contains the session token var customerCodeHeader = context.Request.Headers["customer-code"].ToString(); var noCustomerAttribute = context.Features.Get<IEndpointFeature>()?.Endpoint?.Metadata .Any(m => m is NoCustomerCodeAttribute or AllowAnonymousAttribute) ?? false; if (context.Request.Method == HttpMethod.Options.Method || noCustomerAttribute) { await next(context); return; } if (string.IsNullOrEmpty(customerCodeHeader) || !int.TryParse(customerCodeHeader, out var customerCode)) { context.Response.StatusCode = 403; return; } var user = await identityService.Get(); if (user == null) { context.Response.StatusCode = 403; return; } MemoryCacheEntryOptions options = new(); options.SetSlidingExpiration(TimeSpan.FromHours(1)); var applicationUserCustomer = await cacheService.GetAsync( cacheService.BuildCacheKey(nameof(ApplicationUserCustomer), user.ApplicationUserId.ToString(), customerCode.ToString()), () => applicationUserCustomerRepository.Get(x => x.ApplicationUserId == user.ApplicationUserId && x.CustomerCode == customerCode), options); if (applicationUserCustomer == null) { context.Response.StatusCode = 403; return; } await next(context); } }
解决方案
1. 补充OpenIddict Introspection客户端密钥配置
当前OpenIddict验证配置仅设置了客户端ID,缺少客户端密钥,而Introspection模式需要密钥完成服务端身份校验,导致自动认证流程失败。在AddValidation配置中添加以下代码:
builder.Services.AddOpenIddict() .AddValidation(options => { options.SetIssuer(openIddictSettings.StsUrl); options.AddAudiences(openIddictSettings.Audience); options.SetClientId(openIddictSettings.ApiClientId); options.SetClientSecret(openIddictSettings.ApiClientSecret); // 新增该行 options.UseSystemNetHttp(); options.UseAspNetCore(); });
同时确保配置文件中存在OpenIddict:ApiClientSecret的正确值。
2. 确认OpenIddict服务端权限配置
在OpenIddict服务器端,需为该API客户端分配introspect权限,允许其调用令牌 introspection 接口完成令牌验证。
3. 调整中间件执行顺序
将404重定向中间件移至管道末尾(MapControllers之后),确保认证、授权及自定义业务中间件先于路由匹配执行,避免流程顺序异常:
app.UseAuthentication(); app.UseAuthorization(); // 自定义业务中间件放在授权之后、路由映射之前 app.UseMiddleware<ApplicationUserCustomerValidationMiddleware>(); app.UseMiddleware<ExportToExcelMiddleware>(); app.MapControllers(); // 404重定向中间件放在最后,处理未匹配到路由的请求 app.Use(async (context, next) => { await next(); if (context.Response.StatusCode == 404 && !Path.HasExtension(context.Request.Path.Value)) { context.Request.Path = "/index.html"; await next(); } });
4. 移除手动认证代码
完成上述配置后,可删除中间件中手动调用context.AuthenticateAsync的代码,context.User将由UseAuthentication中间件自动填充。
内容的提问来源于stack exchange,提问作者Jasper B
相关产品推荐
相关产品推荐

