IdentityServer ApiResources配置疑问及无效范围问题排查
问题背景
我正在遵循最新的Duende IdentityServer官方快速入门教程,遇到两个技术疑问:
疑问1
我的理解是否正确:ApiResource本质是ApiScope和IdentityScope的逻辑分组?比如将3个ApiScope("apiscope1"、"apiscope2"、"apiscope3")封装到名为"api"的ApiResource后,客户端只需指定"api"作为范围,即可自动获取这三个ApiScope?
疑问2
为何在MyApi的OpenIdConnect配置中添加"apiResource"作为请求范围时会出现无效范围错误,移除该范围后登录/登出流程可正常运行?
配置代码
MyApi(客户端)配置
builder.Services.AddAuthentication(options => { options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; }) .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme) .AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options => { var idsvrConfig = builder.Configuration.GetSection("IdentityServer").Get<IdentityServerConfiguration>(); options.Authority = idsvrConfig.Authority; options.ClientId = idsvrConfig.ClientId; options.ClientSecret = idsvrConfig.ClientSecret; options.ResponseType = idsvrConfig.ResponseType; options.Scope.Clear(); //options.Scope.AddRange(idsvrConfig.Scopes); options.Scope.Add("openid"); options.Scope.Add("profile"); options.Scope.Add("verification"); options.Scope.Add("apiResource"); //<-------指定此项时无法正常工作!! options.GetClaimsFromUserInfoEndpoint = idsvrConfig.GetClaimsFromUserInfoEndpoint; options.SaveTokens = idsvrConfig.SaveTokens; foreach (var claims in idsvrConfig.ClaimActionsMapJsonKey) { options.ClaimActions.MapJsonKey(claims.Key, claims.Value); } });
IdentityServerApi(授权服务器)配置
var apiResources = new List<ApiResource>() { new ApiResource() { Name = "apiResource", DisplayName ="ApiResource", Scopes = new string[] {"test" } } }; var apiScopes = new List<ApiScope>() { new ApiScope() { Name = "test", } }; var identityResources = new List<IdentityResource> { new IdentityResources.OpenId(), new IdentityResources.Profile(), new IdentityResource() { Name = "verification", UserClaims = new List<string> { JwtClaimTypes.Email, JwtClaimTypes.EmailVerified } } }; var clients = new List<Client> { // interactive ASP.NET Core Web App new Client { ClientId = "api", ClientSecrets = { new Secret(){ Value = "supersecretpass" }}, AllowedGrantTypes = GrantTypes.Code, // where to redirect after login RedirectUris = { "https://localhost:44330/signin-oidc" }, // where to redirect after logout PostLogoutRedirectUris = { "https://localhost:44330/signout-callback-oidc" }, AllowedScopes = new List<string> { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, "verification", "apiResource", "test" } } }; // Add Identity Server services builder.Services.AddIdentityServer() .AddInMemoryClients(clients) .AddInMemoryIdentityResources(identityResources) .AddInMemoryApiScopes(apiScopes) .AddInMemoryApiResources(apiResources) //.AddInMemoryClients(builder.Configuration.GetSection("IdentityServer:Clients")) //.AddInMemoryIdentityResources(builder.Configuration.GetSection("IdentityServer:IdentityResources")) //.AddInMemoryApiScopes(builder.Configuration.GetSection("IdentityServer:ApiScopes")) //.AddInMemoryApiResources(builder.Configuration.GetSection("IdentityServer:ApiResources")) .AddTestUsers(TestUsers.Users);
环境信息
- 两个项目均基于.NET 7
- MyApi使用
Microsoft.AspNetCore.Authentication.OpenIdConnect 7.0.5 - IdentityServer使用
Duende.IdentityServer 6.2.3
针对疑问1的回答
你的理解不正确。ApiResource和ApiScope的关系并非“逻辑分组”,而是:
ApiScope是客户端请求的最小权限单元,代表客户端可访问API的具体功能/数据范围ApiResource是对受保护API的元数据抽象,用于定义API的名称、显示名、颁发的Claims等,仅用于关联对应的ApiScope- 客户端不能直接请求
ApiResource的名称作为范围,必须显式请求具体的ApiScope名称
若想实现类似“分组请求”的效果,需在客户端配置中显式列出所有需要的Scope,或通过自定义逻辑在授权服务器端处理范围组合,IdentityServer本身没有原生的范围分组别名功能。
针对疑问2的回答
出现“无效范围”错误的核心原因是:apiResource是ApiResource的名称,并非有效的Scope标识。
在当前配置中,有效范围仅包括:
- 身份资源范围:
openid、profile、verification - API范围:
test
虽然你在客户端的AllowedScopes中添加了apiResource,但IdentityServer只会识别ApiScope和IdentityResource的名称为有效范围,ApiResource本身不能被当作Scope请求。
解决方法:
- 将MyApi配置中的
options.Scope.Add("apiResource")替换为options.Scope.Add("test") - 清理客户端
AllowedScopes中的无效项,仅保留有效范围名称
另外在Duende IdentityServer v6+版本中,ApiResource的核心作用是配置API的JWT颁发规则(如Claims、签名算法),客户端请求的权限始终以具体的ApiScope为单位。
内容的提问来源于stack exchange,提问作者Style

