如何让OpenIddict Server认证方案在自定义API端点生效?
基于Robin van der Knaap的指南搭建OpenIddict OAuth服务器(采用Identity而非Cookie实现),需在同一项目中运行Web API,使用Postman测试端点时遇到以下问题:
AuthorisationController中的userinfo端点使用[Authorize(AuthenticationSchemes = OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)]可正常工作- 自定义Web API控制器端点(
/api/Test/)及新增的~/connect/hello端点使用相同授权注解时触发错误 - 直接使用
[Authorize]时始终返回未授权
正常工作的Userinfo端点代码
[Authorize(AuthenticationSchemes = OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)] [HttpGet("~/connect/userinfo")] public async Task<IActionResult> Userinfo() { var claimsPrincipal = (await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)).Principal; return Ok(new { Name = claimsPrincipal.GetClaim(OpenIddictConstants.Claims.Subject), Occupation = "Developer", Age = 43 }); }
报错的自定义API端点代码
[Route("api/[controller]")] [ApiController] public class TestController : ControllerBase { [HttpGet] [Authorize(AuthenticationSchemes = OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)] public IActionResult Index() { return Ok("hello"); } }
错误信息
System.InvalidOperationException: An identity cannot be extracted from
this request. This generally indicates that the OpenIddict server
stack was asked to validate a token for an endpoint it doesn't manage.
To validate tokens received by custom API endpoints, the OpenIddict
validation handler (e.g
OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme or
OpenIddictValidationOwinDefaults.AuthenticationType) must be used
instead. at
OpenIddict.Server.OpenIddictServerHandlers.ValidateAuthenticationDemand.HandleAsync(ProcessAuthenticationContext
context) at
OpenIddict.Server.OpenIddictServerDispatcher.DispatchAsync[TContext](TContext
context) at
OpenIddict.Server.OpenIddictServerDispatcher.DispatchAsync[TContext](TContext
context) at
OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreHandler.HandleAuthenticateAsync()
at
Microsoft.AspNetCore.Authentication.AuthenticationHandler`1.AuthenticateAsync()
at
Microsoft.AspNetCore.Authentication.AuthenticationService.AuthenticateAsync(HttpContext
context, String scheme) at
Microsoft.AspNetCore.Authorization.Policy.PolicyEvaluator.AuthenticateAsync(AuthorizationPolicy
policy, HttpContext context) at
Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext
context) at
Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext
context) at
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext
context)
Program.cs核心配置
builder.Services.AddOpenIddict() .AddCore(options => { options.UseEntityFrameworkCore() .UseDbContext<ApplicationDbContext>(); }) .AddServer(options => { options .AllowClientCredentialsFlow() .AllowAuthorizationCodeFlow().RequireProofKeyForCodeExchange() .AllowRefreshTokenFlow(); options .SetTokenEndpointUris("/connect/token") .SetAuthorizationEndpointUris("/connect/authorize") .SetTokenEndpointUris("/connect/token") .SetUserinfoEndpointUris("/connect/userinfo"); options .AddEncryptionCertificate(CertificateHelper.LoadCertificateFromKeyVault(builder.Configuration["KeyVault:Name"], builder.Configuration["OAuth:EncryptionCertName"])) .AddSigningCertificate(CertificateHelper.LoadCertificateFromKeyVault(builder.Configuration["KeyVault:Name"], builder.Configuration["OAuth:EncryptionCertName"])) .DisableAccessTokenEncryption(); options.RegisterScopes("api"); options .UseAspNetCore() .EnableTokenEndpointPassthrough() .EnableAuthorizationEndpointPassthrough() .EnableUserinfoEndpointPassthrough(); }) .AddValidation(); builder.Services.AddAuthentication(options => options.DefaultScheme = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme);
核心原因
OpenIddict的Server组件仅负责处理它明确注册的端点(如/connect/userinfo、/connect/token等),自定义API端点不属于Server组件的管辖范围,必须使用OpenIddict Validation组件的认证方案来验证令牌。
具体修改
- 修改自定义端点的授权注解
将自定义API端点的Authorize注解中的认证方案替换为Validation组件的默认值:
// 自定义API端点使用Validation方案 [Authorize(AuthenticationSchemes = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme)]
- 确保直接使用
[Authorize]生效
虽然你已在Program.cs中设置默认认证方案为Validation,但需确保请求携带的令牌满足以下条件:
- 令牌包含
apiscope(你已在Server配置中注册该scope,申请令牌时需指定此scope) - 请求中正确携带令牌(通常放在
Authorization: Bearer <token>头中)
示例修改后的TestController
using OpenIddict.Validation.AspNetCore; [Route("api/[controller]")] [ApiController] public class TestController : ControllerBase { [HttpGet] [Authorize(AuthenticationSchemes = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme)] public IActionResult Index() { return Ok("hello"); } }
新增的connect/hello端点修改
using OpenIddict.Validation.AspNetCore; [Authorize(AuthenticationSchemes = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme)] [HttpGet("~/connect/hello")] public async Task<IActionResult> Hello() { return Ok("hi"); }
补充说明
userinfo端点能正常工作是因为你在Server配置中通过SetUserinfoEndpointUris("/connect/userinfo")将其注册为Server管辖的端点,因此可以使用Server的认证方案- 若希望所有API端点默认使用Validation方案,除了设置
options.DefaultScheme外,还可配置options.DefaultAuthenticateScheme和options.DefaultChallengeScheme:
builder.Services.AddAuthentication(options => { options.DefaultScheme = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme; options.DefaultAuthenticateScheme = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme; options.DefaultChallengeScheme = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme; });
这样无需在每个[Authorize]注解中指定认证方案,直接使用[Authorize]即可。
内容的提问来源于stack exchange,提问作者tappetyclick

