IdentityServer:OpenID Connect认证中策略要求的Scope缺失导致授权失败的问题排查与解决
fullaccess Scope缺失的问题 咱们一步步来排查和解决这个浏览器访问时的权限拒绝问题——毕竟Postman能正常工作,说明基础配置没问题,问题大概率出在浏览器OIDC流程和客户端认证方案的细节上:
1. 先确认IdentityServer里的客户端权限配置
首先得保证你在IdentityServer的appsettings.json里,给postman这个客户端开了fullaccess的访问权限。毕竟就算你定义了这个Scope,如果客户端没被授权使用它,IdentityServer根本不会把它放进返回的令牌里。
找到IdentityServerSettings.Clients里的postman配置,确保AllowedScopes包含fullaccess:
"IdentityServerSettings": { "Clients": [ { "ClientId": "postman", "ClientName": "Postman Client", "AllowedGrantTypes": ["authorization_code"], "RedirectUris": ["https://localhost:xxxx/signin-oidc"], // 替换成你实际的客户端回调地址 "PostLogoutRedirectUris": ["https://localhost:xxxx/signout-callback-oidc"], "AllowedScopes": [ "openid", "profile", "fullaccess" // 这个必须存在! ], "AllowOfflineAccess": true, "ClientSecrets": [{ "Value": "你的客户端密钥哈希值" }] } ], "ApiScopes": [ { "Name": "fullaccess" } ] }
2. 检查客户端OIDC的Scope请求配置
你已经加了options.Scope.Add("fullaccess");,但为了避免默认配置的隐性坑,建议显式列出所有需要的Scope,包括OIDC标准的openid和profile(这两个是OIDC流程必须的基础Scope):
.AddOpenIdConnect("oidc", options => { options.Authority = "https://localhost:5001"; options.ClientId = "postman"; options.ResponseType = "code"; // 先清空默认Scope,再显式添加需要的项 options.Scope.Clear(); options.Scope.Add("openid"); options.Scope.Add("profile"); options.Scope.Add("fullaccess"); options.SaveTokens = true; // 这个一定要开启,它会把access_token保存在HttpContext中供后续使用 // 可选配置:从UserInfo端点拉取完整的用户声明,包括Scope信息 options.GetClaimsFromUserInfoEndpoint = true; });
3. 关键:让API控制器使用Access Token做认证
这是最容易踩的坑!Postman测试时你直接用access_token调用API,但浏览器的OIDC流程默认用id_token做用户认证——而fullaccess作为API专属Scope,只存在于access_token里,id_token中根本不会包含这个声明!
所以咱们需要给客户端添加JwtBearer认证方案,专门用于API的access_token验证:
首先在Startup的ConfigureServices里加上JwtBearer配置:
services.AddAuthentication() .AddOpenIdConnect("oidc", options => { // 这里放你上面的OIDC配置代码 }) .AddJwtBearer("Bearer", options => { options.Authority = "https://localhost:5001"; options.Audience = "fullaccess"; // 填写你的API Scope名称 // 配置令牌验证参数,确保令牌合法有效 options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidIssuer = "https://localhost:5001", ValidateAudience = true, ValidAudience = "fullaccess", ValidateLifetime = true }; });
然后修改BankController的Authorize属性,指定用Bearer方案来做认证:
[ApiController] [Route("[controller]")] [Authorize(Policy = "SomePolicy", AuthenticationSchemes = "Bearer")] public class BankController : ControllerBase { ... }
如果你想让所有API都默认使用Bearer方案,可以全局配置:
services.AddAuthorization(options => { options.DefaultPolicy = new AuthorizationPolicyBuilder("Bearer") .RequireAuthenticatedUser() .Build(); options.AddPolicy("SomePolicy", policy => { policy.AuthenticationSchemes.Add("Bearer"); policy.RequireClaim("scope", "fullaccess"); }); });
4. 验证Access Token里是否真的包含fullaccess
可以在客户端加个测试Action,把保存的access_token解析出来确认:
public IActionResult CheckAccessToken() { var accessToken = HttpContext.GetTokenAsync("access_token").Result; var handler = new JwtSecurityTokenHandler(); var token = handler.ReadJwtToken(accessToken); var scopes = token.Claims.Where(c => c.Type == "scope").Select(c => c.Value).ToList(); return Ok($"当前令牌包含的Scope:{string.Join(", ", scopes)}"); }
如果这里看不到fullaccess,那回到第一步重新检查IdentityServer的客户端和Scope配置。
5. 额外:如果配置了API资源,要确保关联正确
如果你在IdentityServer里定义了API资源(不是只定义ApiScope),要确保API资源包含fullaccess:
"IdentityServerSettings": { "ApiResources": [ { "Name": "bankapi", "Scopes": ["fullaccess"] } ], "ApiScopes": [ { "Name": "fullaccess" } ] }
同时客户端的AllowedScopes要包含bankapi或者fullaccess。
按照这些步骤排查下来,应该就能解决浏览器访问时跳转到AccessDenied页面的问题了。
内容的提问来源于stack exchange,提问作者yogihosting

