使用 IdentityServer4 从单个客户端接受 Bearer 身份验证和 OpenIdConnect 时的奇怪行为

Strange behaviour when accepting Bearer authentication and OpenIdConnect from a single client with IdentityServer4

我在对 IdentityServer4 Quickstart sample solution 进行了一些调整后遇到了一些问题,特别是 8_AspNetIdentity 示例。

首先我会说我不确定我正在尝试做的事情是否不受支持,或者我是否做错了。

此示例解决方案包含以下与我的问题相关的项目:

我想做的是将 API 项目合并到 MVCClient 中,这样 MVCClient 既可以使用 OIDC 对来自其 MVC 网站的用户进行身份验证,也可以使用不记名身份验证对 ResourceOwnerClient 进行身份验证。

我对 MVCClient 的 Startup.cs 进行了以下更改:

  1. 已将 services.AddMvc(); 更改为:

    services.AddMvc(config =>
    {
        var policy = new AuthorizationPolicyBuilder(new[]
            {
                JwtBearerDefaults.AuthenticationScheme,
                CookieAuthenticationDefaults.AuthenticationScheme,
                "oidc"
            })
            .RequireAuthenticatedUser()
            .Build();
    
        config.Filters.Add(new AuthorizeFilter(policy));
    });
    
  2. services.AddAuthentication() 添加了 JWT 承载选项:

    .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
    {
        options.Authority = "http://localhost:5000";
        options.RequireHttpsMetadata = false;
        options.Audience = "api1";
    })
    

现在从技术上讲这确实有效,因为 ResourceOwnerClient 和 MVC 用户都可以成功地通过 MVCClient 进行身份验证。然而,我有一个警告:

当我从 MVC 端对用户进行身份验证时,我注意到当前用户中有两个身份。两者在声明等方面是相同的。只有当我在 MVCClient 中放置断点时才会发生这种情况,而在 IdentityServer 上只有一个身份。

在 IdentityServer 上,我注册了一个 UserClaimsPrincipalFactory,它将我自己的自定义声明添加到 ClaimsIdentity。在 IdentityServer 的两个身份中,我可以看到重复的声明。因此,我看到的不是一个身份和两个自定义声明,而是两个身份,每个身份都有 4 个自定义声明。单次登录时,我的 UserClaimsPrincipalFactory 中的 CreateAsync 方法也被命中 5 次。

虽然这种行为很奇怪,但似乎没有任何负面影响。但这只是我正在构建的更大应用程序的概念证明,恐怕我将来可能 运行 会因此而陷入问题。

如果有人以前尝试过这种事情,或者知道为什么会发生这种行为,我们将不胜感激。

虽然这个设计应该没有什么不好的地方,但我会完全重新制作它。为什么?因为您正在混合 ClientApiResource,所以它们应该在逻辑上分开。 Client 一个应用程序 ,一些用户与之交互的东西,即使它是一个无头的应用程序(即自动化服务);而 ApiResource 提供给客户端 的资源组成,因此没有用户可以直接与其交互。

您可以针对 IdentityServer 添加两个身份验证,一个作为 API(并将其添加为 JwtBearer),一个作为客户端(并将其添加为 Cookies)。然后,您可以根据 Action/Controller.

的功能使用 [Authorize(AuthenticationSchemes = "JwtBearer")]= "Cookies"

撇开这个不谈,问题是您的应用程序正在为 MVC 端获取一个标识,为 API 端获取一个标识,因为它无法告诉您想要哪一个。

只是为了让你有一个想法,这就是我的一个带有 ASP.NET Core Identtiy 的 IdentityServers 的样子,你可以在其中使用 UI 登录它,还可以点击 REST 端点一个 JwtToken:

services
    .AddAuthentication(options =>
    {
        options.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme;
        options.DefaultChallengeScheme = IdentityConstants.ApplicationScheme;
        options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
    })
    .AddIdentityServerAuthentication(JwtBearerDefaults.AuthenticationScheme, options =>
    {
        options.Authority = Configuration["IdentityServerUrl"];
        options.ApiName = Configuration["ApiName"];

        options.RequireHttpsMetadata = false;
    })
    .AddCookie(IdentityConstants.ApplicationScheme, o =>
    {
        o.LoginPath = new PathString("/Account/Login");
        o.Events = new CookieAuthenticationEvents()
        {
            OnValidatePrincipal = SecurityStampValidator.ValidatePrincipalAsync
        };
    })
    .AddCookie(IdentityConstants.ExternalScheme, o =>
    {
        o.Cookie.Name = IdentityConstants.ExternalScheme;
        o.ExpireTimeSpan = TimeSpan.FromMinutes(5.0);
    })
    .AddCookie(IdentityConstants.TwoFactorRememberMeScheme, o =>
    {
        o.Cookie.Name = IdentityConstants.TwoFactorRememberMeScheme;
    })
    .AddCookie(IdentityConstants.TwoFactorUserIdScheme, o =>
    {
        o.Cookie.Name = IdentityConstants.TwoFactorUserIdScheme;
        o.ExpireTimeSpan = TimeSpan.FromMinutes(5.0);
    });