You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

未授权访问API返回401错误及多认证方案配置疑问

问题描述

我有一个标记了[Authorize]特性的API控制器方法,目前携带Bearer令牌访问一切正常,但无认证(比如Postman不携带授权信息)时,会抛出错误:

System.InvalidOperationException: No authenticationScheme was specified, and there was no DefaultChallengeScheme found.

我希望未认证/授权用户访问时返回401错误,同时应用需要支持多种认证方案。另外我有几个困惑:

  • 每次带options参数调用.AddAuthentication()都会向IServiceCollection添加新的AuthenticationOptions,我多次设置了DefaultChallengeScheme,但系统仍无法识别默认值,这是为什么?
  • 是否需要设置一个真正的默认AuthenticationOptions
  • 由于.AddJwtBearer等调用也会添加AuthenticationOptions,无法避免多个该选项,多AuthenticationOptions下如何设置默认挑战方案?

附上我的Startup.cs中ConfigureServices的相关代码:

//services var = IServiceCollection services

// Adding authentication
var firstUserpoolName = userpools.First().Key;
var authenticationBuilder = services.AddAuthentication(options =>
{
    options.DefaultScheme = firstUserpoolName;
    options.DefaultChallengeScheme = firstUserpoolName;
});

// Adding authentication for each userpool name
foreach (var userpool in userpools)
{
    var userpoolDetails = userpool.GetChildren().ToList();
    var userpoolAuthenticationBuilder = services.AddAuthentication();
    userpoolAuthenticationBuilder.AddJwtBearer(userpool.Key, options =>
    {
        options.Audience = userpoolDetails.FirstOrDefault(d => d.Key == "Audience")?.Value;
        // More options set here in actual code
    });
}

string prioritySystem = Configuration["Priority_System"].ToLower();
string loginPath = string.Format("/{0}/login", prioritySystem);

//Adding cookie authentication
var cookiesAuthenticationBuilder = services.AddAuthentication(options =>
{
    // Adding these options allows for the correct HttpContext to be accessible in any middleware
    options.DefaultChallengeScheme = AuthenticationSchemes.CookieAuthenticationDefault;
    options.DefaultAuthenticateScheme = AuthenticationSchemes.CookieAuthenticationDefault;
});

cookiesAuthenticationBuilder.AddCookie(options =>
{
    options.LoginPath = loginPath;
    options.EventsType = typeof(CustomCookieAuthenticationEvents);
    // More options set here in actual code
});

// Adding SSO authentication
var ssoConfig = Configuration.GetSection(AuthenticationSchemes.OIDC_SSO);

if (ssoConfig.Exists())
{
    services.AddAuthentication(options =>
    {
        options.DefaultAuthenticateScheme = AuthenticationSchemes.CookieAuthenticationDefault;
        options.DefaultSignInScheme = AuthenticationSchemes.CookieAuthenticationDefault;
        options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
    });
}
else
{
    services.AddAuthentication(options =>
    {
        options.DefaultAuthenticateScheme = AuthenticationSchemes.CookieAuthenticationDefault;
        options.DefaultSignInScheme = AuthenticationSchemes.CookieAuthenticationDefault;
    });
}

if (ssoConfig.Exists())
{
    // Setup SSO auth
    services.Configure<OpenIdConnectOptions>(builderSSOConfig);
    var serviceProvider = services.BuildServiceProvider();
    var builderAuthOptions = serviceProvider.GetService<IOptions<OpenIdConnectOptions>>();

                
    services.AddAuthentication().AddOpenIdConnect(AuthenticationSchemes.OIDC_SSO, options =>
    {
        options.CallbackPath = "/signin-oidc";
        // More options set here in actual code
    });
}
问题原因与解决方案

核心问题:重复调用AddAuthentication()覆盖了默认配置

每次调用services.AddAuthentication(options => { ... })都会重新注册全局的AuthenticationOptions,后续调用会直接覆盖之前的配置。你多次调用这个方法,导致最初设置的默认Scheme被后续的Cookie、SSO配置覆盖,最终可能因为逻辑分支问题,没有一个有效的DefaultChallengeScheme被保留,才会出现找不到默认挑战方案的错误。

而且,添加认证方案(比如AddJwtBearerAddCookie)不需要每次都重新调用AddAuthentication(),正确的做法是只调用一次AddAuthentication()设置全局默认配置,然后通过返回的AuthenticationBuilder添加所有认证方案

解决方案步骤

1. 统一认证配置入口,只调用一次AddAuthentication()

先确定你的全局默认Scheme:如果是API为主,优先JWT;如果是混合Web页面,优先Cookie。这里假设你需要API返回401,所以默认挑战方案设置为第一个用户池的JWT Scheme。

修改后的代码结构:

// 第一步:只调用一次AddAuthentication,设置全局默认配置
var firstUserpoolName = userpools.First().Key;
var authBuilder = services.AddAuthentication(options =>
{
    // 设置默认认证方案(可选,根据业务场景调整)
    options.DefaultAuthenticateScheme = firstUserpoolName;
    // 关键:设置默认挑战方案,确保未认证时触发401
    options.DefaultChallengeScheme = firstUserpoolName;
});

// 第二步:添加所有JWT方案(每个用户池)
foreach (var userpool in userpools)
{
    var userpoolDetails = userpool.GetChildren().ToList();
    authBuilder.AddJwtBearer(userpool.Key, options =>
    {
        options.Audience = userpoolDetails.FirstOrDefault(d => d.Key == "Audience")?.Value;
        // 其他JWT配置...
    });
}

// 第三步:添加Cookie认证方案
string prioritySystem = Configuration["Priority_System"].ToLower();
string loginPath = $"/{prioritySystem}/login";
authBuilder.AddCookie(AuthenticationSchemes.CookieAuthenticationDefault, options =>
{
    options.LoginPath = loginPath;
    options.EventsType = typeof(CustomCookieAuthenticationEvents);
    // 针对API场景,关闭登录跳转,返回401
    options.Events.OnRedirectToLogin = context =>
    {
        context.Response.StatusCode = StatusCodes.Status401Unauthorized;
        return Task.CompletedTask;
    };
    // 其他Cookie配置...
});

// 第四步:添加SSO(OIDC)认证方案
var ssoConfig = Configuration.GetSection(AuthenticationSchemes.OIDC_SSO);
if (ssoConfig.Exists())
{
    authBuilder.AddOpenIdConnect(AuthenticationSchemes.OIDC_SSO, options =>
    {
        options.CallbackPath = "/signin-oidc";
        // 其他OIDC配置...
    });
    // 如果需要针对SSO场景调整默认Scheme,用Configure修改全局配置,而非重新调用AddAuthentication
    services.Configure<AuthenticationOptions>(options =>
    {
        options.DefaultSignInScheme = AuthenticationSchemes.CookieAuthenticationDefault;
    });
}

2. 让[Authorize]支持所有认证方案

默认[Authorize]会使用DefaultAuthenticateScheme验证身份,要让它接受所有配置的认证方案,可自定义全局授权策略:

services.AddAuthorization(options =>
{
    // 收集所有已配置的认证方案
    var allSchemes = new List<string>();
    allSchemes.AddRange(userpools.Select(u => u.Key));
    allSchemes.Add(AuthenticationSchemes.CookieAuthenticationDefault);
    if (ssoConfig.Exists())
    {
        allSchemes.Add(AuthenticationSchemes.OIDC_SSO);
    }

    // 设置默认授权策略,包含所有认证方案
    options.DefaultPolicy = new AuthorizationPolicyBuilder()
        .AddAuthenticationSchemes(allSchemes.ToArray())
        .RequireAuthenticatedUser()
        .Build();
});

这样所有标记[Authorize]的方法会自动尝试所有配置的认证方案,只要其中一个验证通过就允许访问。

3. 确保未认证时返回401

只要正确设置DefaultChallengeScheme为JWT方案,未认证时JWT中间件会自动返回401响应。如果是Cookie方案,通过上面配置的OnRedirectToLogin事件,也会在API场景下返回401而非跳转登录页。

关于多AuthenticationOptions的疑问

  • 你不需要多个AuthenticationOptions,它是全局配置,整个应用应该只有一个。重复调用AddAuthentication(options => {})会覆盖之前的配置,这就是你多次设置DefaultChallengeScheme却无效的原因。
  • 添加认证方案(AddJwtBearerAddCookie等)不会新增AuthenticationOptions,它们只是注册对应的认证处理程序。全局配置由第一次AddAuthentication()设置,后续可通过services.Configure<AuthenticationOptions>(...)修改,无需重新调用AddAuthentication()

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

火山引擎 最新活动