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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 00:49:50