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

ASP.NET Core 8中OIDC与JWT共存时IAuthenticateResultFeature丢失问题

解决方案:ASP.NET Core 8 同时支持OIDC与JWT登录及登出

一、核心思路

当配置多认证方案的授权策略后,IAuthenticateResultFeature不会自动填充——因为授权阶段会尝试所有指定方案,但不会记录具体哪个方案认证成功。我们需要手动追踪用户实际使用的认证方案,或通过其他方式获取有效认证方案来执行登出。

二、具体实现方案

方案1:自定义中间件追踪认证方案

在认证中间件之后、授权中间件之前,添加自定义中间件,捕获每个请求的认证结果并记录成功的方案:

app.UseAuthentication();

// 自定义中间件追踪认证方案
app.Use(async (context, next) =>
{
    // 遍历所有已认证的身份标识,提取对应的认证方案
    var authenticatedSchemes = context.User.Identities
        .Where(id => id.IsAuthenticated)
        .Select(id => id.AuthenticationType)
        .Distinct();

    if (authenticatedSchemes.Any())
    {
        // 将成功的方案存入HttpContext.Items,后续登出时直接读取
        context.Items["AuthenticatedScheme"] = authenticatedSchemes.First();
    }

    await next();
});

app.UseAuthorization();

登出时,从HttpContext.Items中获取记录的方案,执行对应逻辑:

[HttpPost("logout")]
public async Task<IActionResult> Logout()
{
    if (HttpContext.Items.TryGetValue("AuthenticatedScheme", out var schemeObj) && schemeObj is string scheme)
    {
        if (scheme == OpenIdConnectDefaults.AuthenticationScheme)
        {
            // OIDC登出:同时清除Cookie并触发身份提供商登出
            await HttpContext.SignOutAsync(OpenIdConnectDefaults.AuthenticationScheme);
            await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
            return RedirectToAction("Index", "Home");
        }
        else if (scheme == JwtBearerDefaults.AuthenticationScheme)
        {
            // JWT登出:无状态认证只需前端清除本地Token,后端返回提示即可
            return Ok(new { message = "已退出登录" });
        }
    }
    return RedirectToAction("Index", "Home");
}

方案2:利用认证事件钩子记录方案

在配置OIDC和JWT认证时,通过各自的OnTokenValidated事件,直接将当前认证方案存入HttpContext.Items:

services.AddAuthentication(options =>
{
    options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(options =>
{
    // 你的OIDC基础配置
    options.ClientId = "your-client-id";
    options.Authority = "your-authority";
    // ...其他配置

    // OIDC认证成功后记录方案
    options.Events = new OpenIdConnectEvents
    {
        OnTokenValidated = context =>
        {
            context.HttpContext.Items["AuthenticatedScheme"] = OpenIdConnectDefaults.AuthenticationScheme;
            return Task.CompletedTask;
        }
    };
})
.AddJwtBearer(options =>
{
    // 你的JWT基础配置
    options.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuer = true,
        ValidIssuer = "your-issuer",
        // ...其他配置
    };

    // JWT认证成功后记录方案
    options.Events = new JwtBearerEvents
    {
        OnTokenValidated = context =>
        {
            context.HttpContext.Items["AuthenticatedScheme"] = JwtBearerDefaults.AuthenticationScheme;
            return Task.CompletedTask;
        }
    };
});

// 配置授权策略,允许两种认证方案
services.AddAuthorization(options =>
{
    options.DefaultPolicy = new AuthorizationPolicyBuilder()
        .AddAuthenticationSchemes(OpenIdConnectDefaults.AuthenticationScheme, JwtBearerDefaults.AuthenticationScheme)
        .RequireAuthenticatedUser()
        .Build();
});

这种方式直接在认证成功的节点记录方案,逻辑更精准,后续登出时直接读取HttpContext.Items即可。

方案3:通过Claims持久化认证方案

在认证成功时,给用户添加一个记录认证方案的Claim,后续从User.Claims中读取:

OIDC配置示例:

options.Events = new OpenIdConnectEvents
{
    OnTokenValidated = context =>
    {
        context.Principal.AddIdentity(new ClaimsIdentity(new[]
        {
            new Claim("auth_scheme", OpenIdConnectDefaults.AuthenticationScheme)
        }));
        return Task.CompletedTask;
    }
};

JWT配置示例:

options.Events = new JwtBearerEvents
{
    OnTokenValidated = context =>
    {
        context.Principal.AddIdentity(new ClaimsIdentity(new[]
        {
            new Claim("auth_scheme", JwtBearerDefaults.AuthenticationScheme)
        }));
        return Task.CompletedTask;
    }
};

登出时读取Claim执行逻辑:

[HttpPost("logout")]
public async Task<IActionResult> Logout()
{
    var schemeClaim = User.Claims.FirstOrDefault(c => c.Type == "auth_scheme");
    if (schemeClaim != null)
    {
        if (schemeClaim.Value == OpenIdConnectDefaults.AuthenticationScheme)
        {
            await HttpContext.SignOutAsync(OpenIdConnectDefaults.AuthenticationScheme);
            await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
            return RedirectToAction("Index", "Home");
        }
        else if (schemeClaim.Value == JwtBearerDefaults.AuthenticationScheme)
        {
            return Ok(new { message = "已退出登录" });
        }
    }
    return RedirectToAction("Index", "Home");
}

三、注意事项

  • JWT是无状态认证,后端无需存储会话,登出核心逻辑是前端清除本地存储的Token;OIDC登出需要同时清除Cookie并通知身份提供商完成全局登出。
  • 若出现极端情况(比如请求同时携带OIDC Cookie和JWT Token),需根据业务逻辑设置优先级(比如优先处理OIDC方案)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 03:42:45