IdentityServer4与MVC客户端多角色授权失败问题求助
从你提供的Claims信息来看,问题的核心很明确:你的用户角色被打包成了一个数组形式的单个Claim(role: ["ABASTECIMENTO","CANCELAMENTO"]),而ASP.NET的角色授权系统期望的是多个独立的role类型Claim(每个角色对应一个Claim)。下面是具体的解决步骤:
1. 修复IdentityServer端的角色Claim生成
先看你的ProfileService.cs,里面有一行多余的代码会导致角色Claim异常:
// 这行代码会将user.role的原始值(可能是数组)作为单个Claim添加,要移除它 claims.Add(new Claim("role", user.role.ToString()));
你已经通过_userManager.GetRolesAsync(user)正确获取了用户的所有角色,并循环添加了独立的JwtClaimTypes.Role类型Claim(JwtClaimTypes.Role本质就是字符串"role"),所以上面那行完全是多余的,会干扰最终的Claim结构。
修改后的ProfileService.cs关键部分:
public async Task GetProfileDataAsync(ProfileDataRequestContext context) { var sub = context.Subject.GetSubjectId(); var user = await _userManager.FindByIdAsync(sub); var principal = await _claimsFactory.CreateAsync(user); var claims = principal.Claims.ToList(); claims = claims.Where(claim => context.RequestedClaimTypes.Contains(claim.Type)).ToList(); // 保留其他自定义Claim,移除多余的role添加 claims.Add(new Claim("id_user", user.Id.ToString())); claims.Add(new Claim("name", user.Name)); claims.Add(new Claim("email", user.Email.ToString())); if (_userManager.SupportsUserRole) { var roles = await _userManager.GetRolesAsync(user); foreach (var roleName in roles) { claims.Add(new Claim(JwtClaimTypes.Role, roleName)); if (_roleManager.SupportsRoleClaims) { var role = await _roleManager.FindByNameAsync(roleName); if (role != null) claims.AddRange(await _roleManager.GetClaimsAsync(role)); } } } context.IssuedClaims = claims; }
2. 修复MVC客户端的Claim映射配置
你的MVC客户端使用了MapUniqueJsonKey来处理角色Claim,这个方法的作用是将JSON数组合并为单个Claim,这正是导致问题的直接原因:
options.ClaimActions.MapUniqueJsonKey("role", "role", "role");
需要把它替换为MapJsonKey,这个方法会自动遍历JSON数组,为每个元素创建独立的Claim:
修改后的Startup.cs关键部分:
services.AddAuthentication(options => { options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; }) .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme) .AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options => { options.Authority = Configuration.GetSection("ids_URI").Value; options.ClientId = "mvc"; options.ClientSecret = "secret"; options.ResponseType = OpenIdConnectResponseType.Code; options.GetClaimsFromUserInfoEndpoint = true; options.Scope.Add("openid"); options.Scope.Add("profile"); options.Scope.Add("email"); options.Scope.Add("role"); options.SaveTokens = true; options.ClaimActions.DeleteClaim("sid"); options.ClaimActions.DeleteClaim("idp"); // 替换为MapJsonKey,拆分数组为多个role Claim options.ClaimActions.MapJsonKey("role", "role"); options.TokenValidationParameters.RoleClaimType = "role"; });
验证修改效果
完成上述修改后,重新生成Token并查看用户Claims,应该会看到两个独立的role Claim:
[8] [Claim]:{role: ABASTECIMENTO} [9] [Claim]:{role: CANCELAMENTO}
此时ASP.NET的角色授权系统就能正确识别用户的多个角色,访问CancelamentoController和AbastecimentoController都不会再出现"访问拒绝"的提示。
内容的提问来源于stack exchange,提问作者Marcelo Monteiro

