使用 IdentityServer4 从单个客户端接受 Bearer 身份验证和 OpenIdConnect 时的奇怪行为
Strange behaviour when accepting Bearer authentication and OpenIdConnect from a single client with IdentityServer4
我在对 IdentityServer4 Quickstart sample solution 进行了一些调整后遇到了一些问题,特别是 8_AspNetIdentity 示例。
首先我会说我不确定我正在尝试做的事情是否不受支持,或者我是否做错了。
此示例解决方案包含以下与我的问题相关的项目:
- 一个身份服务器,
- 使用 OpenIdConnect 对其用户进行身份验证的 MVC 客户端(名为 MVCClient),
- 一个 Web API 客户端(名为 API)为其用户使用承载身份验证
- 一个控制台应用程序(名为 ResourceOwnerClient),旨在成为 API
的客户端
我想做的是将 API 项目合并到 MVCClient 中,这样 MVCClient 既可以使用 OIDC 对来自其 MVC 网站的用户进行身份验证,也可以使用不记名身份验证对 ResourceOwnerClient 进行身份验证。
我对 MVCClient 的 Startup.cs 进行了以下更改:
已将 services.AddMvc();
更改为:
services.AddMvc(config =>
{
var policy = new AuthorizationPolicyBuilder(new[]
{
JwtBearerDefaults.AuthenticationScheme,
CookieAuthenticationDefaults.AuthenticationScheme,
"oidc"
})
.RequireAuthenticatedUser()
.Build();
config.Filters.Add(new AuthorizeFilter(policy));
});
向 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 次。
虽然这种行为很奇怪,但似乎没有任何负面影响。但这只是我正在构建的更大应用程序的概念证明,恐怕我将来可能 运行 会因此而陷入问题。
如果有人以前尝试过这种事情,或者知道为什么会发生这种行为,我们将不胜感激。
虽然这个设计应该没有什么不好的地方,但我会完全重新制作它。为什么?因为您正在混合 Client
和 ApiResource
,所以它们应该在逻辑上分开。 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);
});
我在对 IdentityServer4 Quickstart sample solution 进行了一些调整后遇到了一些问题,特别是 8_AspNetIdentity 示例。
首先我会说我不确定我正在尝试做的事情是否不受支持,或者我是否做错了。
此示例解决方案包含以下与我的问题相关的项目:
- 一个身份服务器,
- 使用 OpenIdConnect 对其用户进行身份验证的 MVC 客户端(名为 MVCClient),
- 一个 Web API 客户端(名为 API)为其用户使用承载身份验证
- 一个控制台应用程序(名为 ResourceOwnerClient),旨在成为 API 的客户端
我想做的是将 API 项目合并到 MVCClient 中,这样 MVCClient 既可以使用 OIDC 对来自其 MVC 网站的用户进行身份验证,也可以使用不记名身份验证对 ResourceOwnerClient 进行身份验证。
我对 MVCClient 的 Startup.cs 进行了以下更改:
已将
services.AddMvc();
更改为:services.AddMvc(config => { var policy = new AuthorizationPolicyBuilder(new[] { JwtBearerDefaults.AuthenticationScheme, CookieAuthenticationDefaults.AuthenticationScheme, "oidc" }) .RequireAuthenticatedUser() .Build(); config.Filters.Add(new AuthorizeFilter(policy)); });
向
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 次。
虽然这种行为很奇怪,但似乎没有任何负面影响。但这只是我正在构建的更大应用程序的概念证明,恐怕我将来可能 运行 会因此而陷入问题。
如果有人以前尝试过这种事情,或者知道为什么会发生这种行为,我们将不胜感激。
虽然这个设计应该没有什么不好的地方,但我会完全重新制作它。为什么?因为您正在混合 Client
和 ApiResource
,所以它们应该在逻辑上分开。 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);
});