ASP.NET Web Api 不接受自己的 Openiddict JWT
ASP.NET Web Api not accepting own Openiddict JWT
我正在使用 Web API 项目学习 ASP.NET Core 中的 OpenID Connect 实现。我的客户目前是 Postman。
上下文(XY问题):
我希望 Sendgrid 报告带有身份验证的 Webhook 数据。 Sendgrid 使用 OAuth 2 流程。我在 Postman 上模拟了一个 Sendgrid Webhook 调用。
我按照 a few 教程设置了授权服务器,即。将向您颁发令牌的部分,特别是使用基于 EF Core 的临时 in-memory 存储。目前,这个解决方案对我来说已经足够了,在成为 production-grade 以便在未来的项目中重用之前,我必须做更多的研究和原型设计。
我可以使用硬编码凭据成功获得 Postman 的令牌。现在我希望控制器 APIs 验证同一台服务器发出的令牌。让我展示一些代码:
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
....
services.AddControllers();
....
services.AddDbContext<OpenIddictDbContext>(ef => //OpenIddictDbContext contains no set, I want the framework to handle its own entities
// Configure the context to use an in-memory store.
ef.UseInMemoryDatabase(nameof(OpenIddictDbContext))
// Register the entity sets needed by OpenIddict.
.UseOpenIddict()
);
services.AddOpenIddict(options =>
options.AddServer(server => server.AddEphemeralEncryptionKey()
.AddEphemeralSigningKey() //Not production-grade but I'll deal with this in the future
.RegisterScopes("api")
.SetTokenEndpointUris("/api/v1/Auth/token")
.SetAuthorizationEndpointUris("/api/v1/Auth/authorize")
.AllowClientCredentialsFlow()
.UseAspNetCore()
.EnableTokenEndpointPassthrough())
.AddCore(core => core.UseEntityFrameworkCore()
.UseDbContext<OpenIddictDbContext>())
.AddValidation(validation => validation.UseLocalServer()))
.AddHostedService<OpenIddictHostedService>() //Used only to create the tech-user for client, I prefer not to paste as it's mostly identical to linked tutorial
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(); //Security hardening (i.e. trusted authorities) TBD in the next future
}
AuthController.cs(路径相同)
[HttpPost("token"), Produces("application/json")]
public async Task<IActionResult> Token(CancellationToken cancellationToken = default)
{
var request = HttpContext.GetOpenIddictServerRequest();
if (!request.IsClientCredentialsGrantType())
{
throw new NotImplementedException("The specified grant is not implemented.");
}
// Note: the client credentials are automatically validated by OpenIddict:
// if client_id or client_secret are invalid, this action won't be invoked.
var application =
await _applicationManager.FindByClientIdAsync(request.ClientId, cancellationToken) ??
throw new InvalidOperationException("The application cannot be found.");
// Create a new ClaimsIdentity containing the claims that
// will be used to create an id_token, a token or a code.
var identity = new ClaimsIdentity(
TokenValidationParameters.DefaultAuthenticationType,
OpenIddictConstants.Claims.Name, OpenIddictConstants.Claims.Role);
// Use the client_id as the subject identifier.
identity.AddClaim(OpenIddictConstants.Claims.Subject,
await _applicationManager.GetClientIdAsync(application, cancellationToken),
OpenIddictConstants.Destinations.AccessToken, OpenIddictConstants.Destinations.IdentityToken);
identity.AddClaim(OpenIddictConstants.Claims.Name,
await _applicationManager.GetDisplayNameAsync(application, cancellationToken),
OpenIddictConstants.Destinations.AccessToken, OpenIddictConstants.Destinations.IdentityToken);
return SignIn(new ClaimsPrincipal(identity),
OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}
我的代码与教程中显示的代码不同,因为并非所有方法都在 OpenIddict 3.0 中找到,也许文档需要更新。
至此,我可以通过Postman获取token:take and eat; this is my ephemeral token
eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZDQkMtSFM1MTIiLCJraWQiOiJWMFZaS1pGR1c0WTJGUUpRNTNMQktaVktXSzZKS1pXV1lDMVREQUpDIiwidHlwIjoiYXQrand0In0.NNnSRN18drc3hj8PXon7bz7SyOpmhBds1fOQVnKZzD9dNSeFOQpc5MNJxkxcSSb3Z-XGGm-2z8Fw03yYJPBn2KkE9zz2udUpWNsWmToiiDOYd_5WVmm3GpZopWAusM-YhzPuavnxY1BFVsIylJs9yX0_jXs1RGIIhNvkGG_AmCBVnPdoDvu41CthRsyJc1IQCX5HuHO2XpvmnaRiVJZyaDbey4pDAs-4Fy-yDosg9w8szUWGcw-Y7e4xYKi6HqmjgDDqJQ14QDW839BOkctdRUrk3GhT7HDZN5Oaq8itPTLoxBrLgG9BE2yqfLKJibWD5MNSpj9OQu8GY-uBFsmt2A.YeBSpHgDu4291YJE-jWhOQ.WjxqjTXykAfFk7NRjXTMjPVsUFOtKK7fJxiLy0T7xiUnPSHIAI9m3UlkmsmQuK7sdxYtZnJ2gw-iJxcd1q1UYYRz5N82bc97py6bTH5cykWwMddCRMLnOFrOqKgTS8cZdgxfPwiz46SZKHYrxOtTtSU7Jy0YtNNYTBt47TXTMAjFwm81QrVmpPwEdt_wodaar7b23Wlw2SYbparw-hTdY-uaitL3olnCDzIn9Jwc7Zkqz8PwNxLYRPoUz1gpKJdO6c-azHcvYI8yY4Ty0OLdUuAZRGVwM4CRETW-Wixig37Sf6meXohJ1aS2Xe6LWAY5Db-Nyfs1H3D6tXL0hzI-q82xZO2oFNcj-yumYL14FoPlxl4TORgSh3L15rBY7e1VxzoJRPYuXAZUCOHrrdAF_DMnM7TSqe52ckMy2_aKNRsFaLZF8brwi4IDnXpe778Nw-ujTQ-djCPTZ-2gI1BP2h0NbpoSSKwI2_9eeUk_IsF5hq54En9oaQpNmz-7oPUAEa4GBmCeowrvzrtMKGMwPCsaOtYAJRnd63Y3YbTAe-NnGTH0EGvBga_9ElwDWswa-UR8CqeMLCqmDKOq9ryYbL4LWSHp7rmsykd2oHqzu_If8c5wzbLQBp3m-bt3w2EaYROXpLGlNvzVLla78Okwe9jFy8QeGVj1pCU8UsgwWCRzlwY4idV0SBSy8dPbEzCunLGbZgx2W81qXPLxWIybTGuOKyBvNffWXhnriUTjsL5khHrfArtjdbsoP93Ig0nqgOGiNXh82RVAuVVmLqOs6yohxLWUbDlxkiophBCmf8RD8h0Eak1zjmzQGTOS9D2BpWouZuEyZOHftnl07SmhXvZXLdw_gSZCoka4EY3FcIxb-9rw53wQAjn-00JLaMbIEaO79fZGJJytiNTagJJouVmvTCh-luNhJ2TULGC5nTb_szyY9yfvkfjK_tP7WIbW._42bMf01NOZ9tXyalK-ONdrPBDPqq-jL-TjWA2aHSuo
但是每次我尝试调用用 [Authorize]
注释的控制器时,我都会得到 401 header WWW-Authenticate: Bearer error="invalid_token"
.
我认为这可能与 services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer()
有关,但尝试使用 OpenIddictServerAspNetCoreDefaults.AuthenticationScheme
而不是 JwtBearerDefaults.AuthenticationScheme
会导致以下错误
The OpenIddict ASP.NET Core server handler cannot be used as the default scheme handler.
Make sure that neither DefaultAuthenticateScheme, DefaultChallengeScheme, DefaultForbidScheme, DefaultSignInScheme, DefaultSignOutScheme nor DefaultScheme point to an instance of the OpenIddict ASP.NET Core server handler.
问题
如何使用同一服务器使用 Openiddict 发布的令牌正确调用 AuthorizeAttribute
保护的方法?
请注意,我没有找到有关身份验证错误的日志。适当提高日志级别可以成为调查的有效第一步
理论
根据我对 OIDC 的了解(我的论文是关于 OAuth 2 的,所以我可以 声称 能够胜任)并且通过查看代码,这就是我可能缺少的.
我已经成功实施了 OIDC 周期的 授权服务器 部分。 IE。使用上面的代码,我终于有一个端点可以验证客户端凭据并调用 SignIn
。据我了解,ASP.NET Core 的 SignIn
方法假定调用代码已经通过匹配客户端 ID 和客户端密码对用户进行了身份验证。
据我了解 ASP.NET Core 不再关心身份验证,SignIn
释放一个令牌,稍后将在 资源服务器中使用 循环验证进一步的调用,因为 Sendgrid 会向我发送 Webhook。
所以在这种情况下,我可能应该努力正确利用 authenticationScheme
参数。
在 non-API 场景中(或者如果 资源消费者 曾经支持过)我可以使用 cookie,这样 SignIn
就会释放一个 cookie到 HttpResponse
object.
或者在这种情况下,我可以找到集成 Openiddict 的 资源服务器 流程的正确方法,或者利用 不同的 身份验证器像 ASP.NET Core 的嵌入式 JWT。
对于后者,SignIn
可以充当委托人。 “嘿,JWT 提供者先生,我在这里成功验证了 Sendrig,他正在敲我们的门 调用我们的验证 API。你介意发布一个你将在未来?”。
我没有成功使用
return SignIn(new ClaimsPrincipal(identity),
// OpenIddictServerAspNetCoreDefaults.AuthenticationScheme
JwtBearerDefaults.AuthenticationScheme
);
但是上面的代码应该告诉 JWT Provider 为声明的身份释放令牌。
关键是添加正确的身份验证方案
services
.AddAuthentication(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme)
来自example
完整代码片段
services.AddOpenIddict(options =>
options.AddServer(server => server.AddEphemeralEncryptionKey()
.AddEphemeralSigningKey()
.RegisterScopes("api")
.SetTokenEndpointUris("/api/v1/Auth/token")
.SetAuthorizationEndpointUris("/api/v1/Auth/authorize")
.AllowClientCredentialsFlow()
.UseAspNetCore()
.EnableTokenEndpointPassthrough())
.AddCore(core => core.UseEntityFrameworkCore()
.UseDbContext<OpenIddictDbContext>())
.AddValidation(validation =>
validation.UseLocalServer(_ => { })
.UseAspNetCore(_ => { })
)
)
.AddHostedService<OpenIddictHostedService>()
.AddAuthentication(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme)
;
这也需要我添加 Openiddict.Validation.AspNetCore 作为项目依赖项
我正在使用 Web API 项目学习 ASP.NET Core 中的 OpenID Connect 实现。我的客户目前是 Postman。
上下文(XY问题): 我希望 Sendgrid 报告带有身份验证的 Webhook 数据。 Sendgrid 使用 OAuth 2 流程。我在 Postman 上模拟了一个 Sendgrid Webhook 调用。
我按照 a few 教程设置了授权服务器,即。将向您颁发令牌的部分,特别是使用基于 EF Core 的临时 in-memory 存储。目前,这个解决方案对我来说已经足够了,在成为 production-grade 以便在未来的项目中重用之前,我必须做更多的研究和原型设计。
我可以使用硬编码凭据成功获得 Postman 的令牌。现在我希望控制器 APIs 验证同一台服务器发出的令牌。让我展示一些代码:
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
....
services.AddControllers();
....
services.AddDbContext<OpenIddictDbContext>(ef => //OpenIddictDbContext contains no set, I want the framework to handle its own entities
// Configure the context to use an in-memory store.
ef.UseInMemoryDatabase(nameof(OpenIddictDbContext))
// Register the entity sets needed by OpenIddict.
.UseOpenIddict()
);
services.AddOpenIddict(options =>
options.AddServer(server => server.AddEphemeralEncryptionKey()
.AddEphemeralSigningKey() //Not production-grade but I'll deal with this in the future
.RegisterScopes("api")
.SetTokenEndpointUris("/api/v1/Auth/token")
.SetAuthorizationEndpointUris("/api/v1/Auth/authorize")
.AllowClientCredentialsFlow()
.UseAspNetCore()
.EnableTokenEndpointPassthrough())
.AddCore(core => core.UseEntityFrameworkCore()
.UseDbContext<OpenIddictDbContext>())
.AddValidation(validation => validation.UseLocalServer()))
.AddHostedService<OpenIddictHostedService>() //Used only to create the tech-user for client, I prefer not to paste as it's mostly identical to linked tutorial
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(); //Security hardening (i.e. trusted authorities) TBD in the next future
}
AuthController.cs(路径相同)
[HttpPost("token"), Produces("application/json")]
public async Task<IActionResult> Token(CancellationToken cancellationToken = default)
{
var request = HttpContext.GetOpenIddictServerRequest();
if (!request.IsClientCredentialsGrantType())
{
throw new NotImplementedException("The specified grant is not implemented.");
}
// Note: the client credentials are automatically validated by OpenIddict:
// if client_id or client_secret are invalid, this action won't be invoked.
var application =
await _applicationManager.FindByClientIdAsync(request.ClientId, cancellationToken) ??
throw new InvalidOperationException("The application cannot be found.");
// Create a new ClaimsIdentity containing the claims that
// will be used to create an id_token, a token or a code.
var identity = new ClaimsIdentity(
TokenValidationParameters.DefaultAuthenticationType,
OpenIddictConstants.Claims.Name, OpenIddictConstants.Claims.Role);
// Use the client_id as the subject identifier.
identity.AddClaim(OpenIddictConstants.Claims.Subject,
await _applicationManager.GetClientIdAsync(application, cancellationToken),
OpenIddictConstants.Destinations.AccessToken, OpenIddictConstants.Destinations.IdentityToken);
identity.AddClaim(OpenIddictConstants.Claims.Name,
await _applicationManager.GetDisplayNameAsync(application, cancellationToken),
OpenIddictConstants.Destinations.AccessToken, OpenIddictConstants.Destinations.IdentityToken);
return SignIn(new ClaimsPrincipal(identity),
OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}
我的代码与教程中显示的代码不同,因为并非所有方法都在 OpenIddict 3.0 中找到,也许文档需要更新。
至此,我可以通过Postman获取token:take and eat; this is my ephemeral token
eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZDQkMtSFM1MTIiLCJraWQiOiJWMFZaS1pGR1c0WTJGUUpRNTNMQktaVktXSzZKS1pXV1lDMVREQUpDIiwidHlwIjoiYXQrand0In0.NNnSRN18drc3hj8PXon7bz7SyOpmhBds1fOQVnKZzD9dNSeFOQpc5MNJxkxcSSb3Z-XGGm-2z8Fw03yYJPBn2KkE9zz2udUpWNsWmToiiDOYd_5WVmm3GpZopWAusM-YhzPuavnxY1BFVsIylJs9yX0_jXs1RGIIhNvkGG_AmCBVnPdoDvu41CthRsyJc1IQCX5HuHO2XpvmnaRiVJZyaDbey4pDAs-4Fy-yDosg9w8szUWGcw-Y7e4xYKi6HqmjgDDqJQ14QDW839BOkctdRUrk3GhT7HDZN5Oaq8itPTLoxBrLgG9BE2yqfLKJibWD5MNSpj9OQu8GY-uBFsmt2A.YeBSpHgDu4291YJE-jWhOQ.WjxqjTXykAfFk7NRjXTMjPVsUFOtKK7fJxiLy0T7xiUnPSHIAI9m3UlkmsmQuK7sdxYtZnJ2gw-iJxcd1q1UYYRz5N82bc97py6bTH5cykWwMddCRMLnOFrOqKgTS8cZdgxfPwiz46SZKHYrxOtTtSU7Jy0YtNNYTBt47TXTMAjFwm81QrVmpPwEdt_wodaar7b23Wlw2SYbparw-hTdY-uaitL3olnCDzIn9Jwc7Zkqz8PwNxLYRPoUz1gpKJdO6c-azHcvYI8yY4Ty0OLdUuAZRGVwM4CRETW-Wixig37Sf6meXohJ1aS2Xe6LWAY5Db-Nyfs1H3D6tXL0hzI-q82xZO2oFNcj-yumYL14FoPlxl4TORgSh3L15rBY7e1VxzoJRPYuXAZUCOHrrdAF_DMnM7TSqe52ckMy2_aKNRsFaLZF8brwi4IDnXpe778Nw-ujTQ-djCPTZ-2gI1BP2h0NbpoSSKwI2_9eeUk_IsF5hq54En9oaQpNmz-7oPUAEa4GBmCeowrvzrtMKGMwPCsaOtYAJRnd63Y3YbTAe-NnGTH0EGvBga_9ElwDWswa-UR8CqeMLCqmDKOq9ryYbL4LWSHp7rmsykd2oHqzu_If8c5wzbLQBp3m-bt3w2EaYROXpLGlNvzVLla78Okwe9jFy8QeGVj1pCU8UsgwWCRzlwY4idV0SBSy8dPbEzCunLGbZgx2W81qXPLxWIybTGuOKyBvNffWXhnriUTjsL5khHrfArtjdbsoP93Ig0nqgOGiNXh82RVAuVVmLqOs6yohxLWUbDlxkiophBCmf8RD8h0Eak1zjmzQGTOS9D2BpWouZuEyZOHftnl07SmhXvZXLdw_gSZCoka4EY3FcIxb-9rw53wQAjn-00JLaMbIEaO79fZGJJytiNTagJJouVmvTCh-luNhJ2TULGC5nTb_szyY9yfvkfjK_tP7WIbW._42bMf01NOZ9tXyalK-ONdrPBDPqq-jL-TjWA2aHSuo
但是每次我尝试调用用 [Authorize]
注释的控制器时,我都会得到 401 header WWW-Authenticate: Bearer error="invalid_token"
.
我认为这可能与 services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer()
有关,但尝试使用 OpenIddictServerAspNetCoreDefaults.AuthenticationScheme
而不是 JwtBearerDefaults.AuthenticationScheme
会导致以下错误
The OpenIddict ASP.NET Core server handler cannot be used as the default scheme handler.
Make sure that neither DefaultAuthenticateScheme, DefaultChallengeScheme, DefaultForbidScheme, DefaultSignInScheme, DefaultSignOutScheme nor DefaultScheme point to an instance of the OpenIddict ASP.NET Core server handler.
问题
如何使用同一服务器使用 Openiddict 发布的令牌正确调用 AuthorizeAttribute
保护的方法?
请注意,我没有找到有关身份验证错误的日志。适当提高日志级别可以成为调查的有效第一步
理论
根据我对 OIDC 的了解(我的论文是关于 OAuth 2 的,所以我可以 声称 能够胜任)并且通过查看代码,这就是我可能缺少的.
我已经成功实施了 OIDC 周期的 授权服务器 部分。 IE。使用上面的代码,我终于有一个端点可以验证客户端凭据并调用 SignIn
。据我了解,ASP.NET Core 的 SignIn
方法假定调用代码已经通过匹配客户端 ID 和客户端密码对用户进行了身份验证。
据我了解 ASP.NET Core 不再关心身份验证,SignIn
释放一个令牌,稍后将在 资源服务器中使用 循环验证进一步的调用,因为 Sendgrid 会向我发送 Webhook。
所以在这种情况下,我可能应该努力正确利用 authenticationScheme
参数。
在 non-API 场景中(或者如果 资源消费者 曾经支持过)我可以使用 cookie,这样 SignIn
就会释放一个 cookie到 HttpResponse
object.
或者在这种情况下,我可以找到集成 Openiddict 的 资源服务器 流程的正确方法,或者利用 不同的 身份验证器像 ASP.NET Core 的嵌入式 JWT。
对于后者,SignIn
可以充当委托人。 “嘿,JWT 提供者先生,我在这里成功验证了 Sendrig,他正在敲我们的门 调用我们的验证 API。你介意发布一个你将在未来?”。
我没有成功使用
return SignIn(new ClaimsPrincipal(identity),
// OpenIddictServerAspNetCoreDefaults.AuthenticationScheme
JwtBearerDefaults.AuthenticationScheme
);
但是上面的代码应该告诉 JWT Provider 为声明的身份释放令牌。
关键是添加正确的身份验证方案
services
.AddAuthentication(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme)
来自example
完整代码片段
services.AddOpenIddict(options =>
options.AddServer(server => server.AddEphemeralEncryptionKey()
.AddEphemeralSigningKey()
.RegisterScopes("api")
.SetTokenEndpointUris("/api/v1/Auth/token")
.SetAuthorizationEndpointUris("/api/v1/Auth/authorize")
.AllowClientCredentialsFlow()
.UseAspNetCore()
.EnableTokenEndpointPassthrough())
.AddCore(core => core.UseEntityFrameworkCore()
.UseDbContext<OpenIddictDbContext>())
.AddValidation(validation =>
validation.UseLocalServer(_ => { })
.UseAspNetCore(_ => { })
)
)
.AddHostedService<OpenIddictHostedService>()
.AddAuthentication(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme)
;
这也需要我添加 Openiddict.Validation.AspNetCore 作为项目依赖项